ソースを参照

* Added fcl-report

git-svn-id: trunk@36962 -
michael 8 年 前
コミット
6672e77222
89 ファイル変更37653 行追加0 行削除
  1. 86 0
      .gitattributes
  2. 2882 0
      packages/fcl-report/Makefile
  3. 102 0
      packages/fcl-report/Makefile.fpc
  4. BIN
      packages/fcl-report/demos/company-logo.png
  5. 20 0
      packages/fcl-report/demos/compiling.txt
  6. 159 0
      packages/fcl-report/demos/countries.inc
  7. 632 0
      packages/fcl-report/demos/countries.json
  8. 35 0
      packages/fcl-report/demos/demos.inc
  9. 130 0
      packages/fcl-report/demos/fcldemo.lpi
  10. 18 0
      packages/fcl-report/demos/fcldemo.pp
  11. BIN
      packages/fcl-report/demos/fonts/LiberationSans-Bold.ttf
  12. BIN
      packages/fcl-report/demos/fonts/LiberationSans-BoldItalic.ttf
  13. BIN
      packages/fcl-report/demos/fonts/LiberationSans-Italic.ttf
  14. BIN
      packages/fcl-report/demos/fonts/LiberationSans-Regular.ttf
  15. BIN
      packages/fcl-report/demos/fonts/LiberationSerif-Bold.ttf
  16. BIN
      packages/fcl-report/demos/fonts/LiberationSerif-BoldItalic.ttf
  17. BIN
      packages/fcl-report/demos/fonts/LiberationSerif-Italic.ttf
  18. BIN
      packages/fcl-report/demos/fonts/LiberationSerif-Regular.ttf
  19. BIN
      packages/fcl-report/demos/pictures/man01.png
  20. BIN
      packages/fcl-report/demos/pictures/man02.png
  21. BIN
      packages/fcl-report/demos/pictures/man03.png
  22. BIN
      packages/fcl-report/demos/pictures/man04.png
  23. BIN
      packages/fcl-report/demos/pictures/man05.png
  24. BIN
      packages/fcl-report/demos/pictures/woman01.png
  25. BIN
      packages/fcl-report/demos/pictures/woman02.png
  26. BIN
      packages/fcl-report/demos/pictures/woman03.png
  27. BIN
      packages/fcl-report/demos/pictures/woman04.png
  28. BIN
      packages/fcl-report/demos/pictures/woman05.png
  29. 94 0
      packages/fcl-report/demos/polygon/frmmain.lfm
  30. 111 0
      packages/fcl-report/demos/polygon/frmmain.pas
  31. 428 0
      packages/fcl-report/demos/polygon/reportpolygon.pas
  32. BIN
      packages/fcl-report/demos/polygon/testpolygon.ico
  33. 89 0
      packages/fcl-report/demos/polygon/testpolygon.lpi
  34. 21 0
      packages/fcl-report/demos/polygon/testpolygon.lpr
  35. BIN
      packages/fcl-report/demos/polygon/testpolygon.res
  36. 63 0
      packages/fcl-report/demos/regreports.pp
  37. 378 0
      packages/fcl-report/demos/rptcolumns.pp
  38. 278 0
      packages/fcl-report/demos/rptcontnr.pp
  39. 198 0
      packages/fcl-report/demos/rptdataset.pp
  40. 259 0
      packages/fcl-report/demos/rptexpressions.pp
  41. 297 0
      packages/fcl-report/demos/rptframes.pp
  42. 242 0
      packages/fcl-report/demos/rptgrouping.pp
  43. 245 0
      packages/fcl-report/demos/rptimages.pp
  44. 191 0
      packages/fcl-report/demos/rptjson.pp
  45. 320 0
      packages/fcl-report/demos/rptmasterdetail.pp
  46. 346 0
      packages/fcl-report/demos/rptmasterdetaildataset.pp
  47. 228 0
      packages/fcl-report/demos/rptshapes.pp
  48. 190 0
      packages/fcl-report/demos/rptsimplelist.pp
  49. 235 0
      packages/fcl-report/demos/rptttf.pp
  50. BIN
      packages/fcl-report/demos/test.dbf
  51. BIN
      packages/fcl-report/demos/test.dbt
  52. 458 0
      packages/fcl-report/demos/udapp.pp
  53. 77 0
      packages/fcl-report/demos/webdemo.lpi
  54. 16 0
      packages/fcl-report/demos/webdemo.pp
  55. 121 0
      packages/fcl-report/demos/wmreports.pp
  56. 120 0
      packages/fcl-report/fpmake.pp
  57. 190 0
      packages/fcl-report/src/fpextfuncs.pp
  58. 205 0
      packages/fcl-report/src/fpjsonreport.pp
  59. 3966 0
      packages/fcl-report/src/fprepexprpars.pp
  60. 9517 0
      packages/fcl-report/src/fpreport.pp
  61. 360 0
      packages/fcl-report/src/fpreportcanvashelper.pp
  62. 280 0
      packages/fcl-report/src/fpreportcheckbox.inc
  63. 270 0
      packages/fcl-report/src/fpreportcontnr.pp
  64. 186 0
      packages/fcl-report/src/fpreportdb.pp
  65. 566 0
      packages/fcl-report/src/fpreportdom.pp
  66. 888 0
      packages/fcl-report/src/fpreportfpimageexport.pp
  67. 1293 0
      packages/fcl-report/src/fpreporthtmlexport.pp
  68. 436 0
      packages/fcl-report/src/fpreporthtmlparser.pp
  69. 897 0
      packages/fcl-report/src/fpreporthtmlutil.pp
  70. 289 0
      packages/fcl-report/src/fpreportjson.pp
  71. 771 0
      packages/fcl-report/src/fpreportpdfexport.pp
  72. 595 0
      packages/fcl-report/src/fpreportstreamer.pp
  73. BIN
      packages/fcl-report/test/fonts/LiberationSerif-Regular.ttf
  74. BIN
      packages/fcl-report/test/fonts/calibri.ttf
  75. BIN
      packages/fcl-report/test/fonts/calibrib.ttf
  76. BIN
      packages/fcl-report/test/fonts/calibrii.ttf
  77. BIN
      packages/fcl-report/test/fonts/calibriz.ttf
  78. 100 0
      packages/fcl-report/test/guitestfpreport.lpi
  79. 26 0
      packages/fcl-report/test/guitestfpreport.lpr
  80. 15 0
      packages/fcl-report/test/regtests.pp
  81. 3705 0
      packages/fcl-report/test/tcbasereport.pp
  82. 171 0
      packages/fcl-report/test/tchtmlparser.pas
  83. 2085 0
      packages/fcl-report/test/tcreportdom.pp
  84. 1618 0
      packages/fcl-report/test/tcreportstreamer.pp
  85. 111 0
      packages/fcl-report/test/testfpreport.lpi
  86. 25 0
      packages/fcl-report/test/testfpreport.lpr
  87. 12 0
      packages/fcl-report/todo.txt
  88. 1 0
      packages/fpmake_add.inc
  89. 6 0
      packages/fpmake_proc.inc

+ 86 - 0
.gitattributes

@@ -2720,6 +2720,92 @@ packages/fcl-registry/tests/regtestframework.pp -text
 packages/fcl-registry/tests/tcxmlreg.pp svneol=native#text/plain
 packages/fcl-registry/tests/testbasics.pp svneol=native#text/plain
 packages/fcl-registry/tests/tregistry2.pp svneol=native#text/plain
+packages/fcl-report/Makefile svneol=native#text/plain
+packages/fcl-report/Makefile.fpc svneol=native#text/plain
+packages/fcl-report/demos/company-logo.png -text svneol=unset#image/png
+packages/fcl-report/demos/compiling.txt svneol=native#text/plain
+packages/fcl-report/demos/countries.inc svneol=native#text/plain
+packages/fcl-report/demos/countries.json 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/LiberationSans-Bold.ttf -text
+packages/fcl-report/demos/fonts/LiberationSans-BoldItalic.ttf -text
+packages/fcl-report/demos/fonts/LiberationSans-Italic.ttf -text
+packages/fcl-report/demos/fonts/LiberationSans-Regular.ttf -text
+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/pictures/man01.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/man02.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/man03.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/man04.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/man05.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/woman01.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/woman02.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/woman03.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/woman04.png -text svneol=unset#image/png
+packages/fcl-report/demos/pictures/woman05.png -text svneol=unset#image/png
+packages/fcl-report/demos/polygon/frmmain.lfm svneol=native#text/plain
+packages/fcl-report/demos/polygon/frmmain.pas svneol=native#text/plain
+packages/fcl-report/demos/polygon/reportpolygon.pas svneol=native#text/plain
+packages/fcl-report/demos/polygon/testpolygon.ico -text
+packages/fcl-report/demos/polygon/testpolygon.lpi svneol=native#text/plain
+packages/fcl-report/demos/polygon/testpolygon.lpr svneol=native#text/plain
+packages/fcl-report/demos/polygon/testpolygon.res -text
+packages/fcl-report/demos/regreports.pp svneol=native#text/plain
+packages/fcl-report/demos/rptcolumns.pp svneol=native#text/plain
+packages/fcl-report/demos/rptcontnr.pp svneol=native#text/plain
+packages/fcl-report/demos/rptdataset.pp svneol=native#text/plain
+packages/fcl-report/demos/rptexpressions.pp svneol=native#text/plain
+packages/fcl-report/demos/rptframes.pp svneol=native#text/plain
+packages/fcl-report/demos/rptgrouping.pp svneol=native#text/plain
+packages/fcl-report/demos/rptimages.pp svneol=native#text/plain
+packages/fcl-report/demos/rptjson.pp svneol=native#text/plain
+packages/fcl-report/demos/rptmasterdetail.pp svneol=native#text/plain
+packages/fcl-report/demos/rptmasterdetaildataset.pp svneol=native#text/plain
+packages/fcl-report/demos/rptshapes.pp svneol=native#text/plain
+packages/fcl-report/demos/rptsimplelist.pp svneol=native#text/plain
+packages/fcl-report/demos/rptttf.pp svneol=native#text/plain
+packages/fcl-report/demos/test.dbf -text
+packages/fcl-report/demos/test.dbt -text
+packages/fcl-report/demos/udapp.pp svneol=native#text/plain
+packages/fcl-report/demos/webdemo.lpi svneol=native#text/plain
+packages/fcl-report/demos/webdemo.pp svneol=native#text/plain
+packages/fcl-report/demos/wmreports.pp svneol=native#text/plain
+packages/fcl-report/fpmake.pp svneol=native#text/plain
+packages/fcl-report/src/fpextfuncs.pp svneol=native#text/plain
+packages/fcl-report/src/fpjsonreport.pp svneol=native#text/plain
+packages/fcl-report/src/fprepexprpars.pp svneol=native#text/plain
+packages/fcl-report/src/fpreport.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportcanvashelper.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportcheckbox.inc svneol=native#text/plain
+packages/fcl-report/src/fpreportcontnr.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportdb.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportdom.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportfpimageexport.pp svneol=native#text/plain
+packages/fcl-report/src/fpreporthtmlexport.pp svneol=native#text/plain
+packages/fcl-report/src/fpreporthtmlparser.pp svneol=native#text/plain
+packages/fcl-report/src/fpreporthtmlutil.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportjson.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportpdfexport.pp svneol=native#text/plain
+packages/fcl-report/src/fpreportstreamer.pp 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
+packages/fcl-report/test/fonts/calibrii.ttf -text
+packages/fcl-report/test/fonts/calibriz.ttf -text
+packages/fcl-report/test/guitestfpreport.lpi svneol=native#text/plain
+packages/fcl-report/test/guitestfpreport.lpr svneol=native#text/plain
+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/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
+packages/fcl-report/todo.txt svneol=native#text/plain
 packages/fcl-res/Makefile svneol=native#text/plain
 packages/fcl-res/Makefile.fpc svneol=native#text/plain
 packages/fcl-res/Makefile.fpc.fpcmake svneol=native#text/plain

+ 2882 - 0
packages/fcl-report/Makefile

@@ -0,0 +1,2882 @@
+#
+# Don't edit, this file is generated by FPCMake Version 2.0.0 [2017-05-30 rev 36373]
+#
+default: all
+MAKEFILETARGETS=i386-linux i386-go32v2 i386-win32 i386-os2 i386-freebsd i386-beos i386-haiku i386-netbsd i386-solaris i386-qnx i386-netware i386-openbsd i386-wdosx i386-darwin i386-emx i386-watcom i386-netwlibc i386-wince i386-embedded i386-symbian i386-nativent i386-iphonesim i386-android i386-aros m68k-linux m68k-freebsd m68k-netbsd m68k-amiga m68k-atari m68k-openbsd m68k-palmos m68k-embedded powerpc-linux powerpc-netbsd powerpc-amiga powerpc-macos powerpc-darwin powerpc-morphos powerpc-embedded powerpc-wii powerpc-aix sparc-linux sparc-netbsd sparc-solaris sparc-embedded x86_64-linux x86_64-freebsd x86_64-netbsd x86_64-solaris x86_64-openbsd x86_64-darwin x86_64-win64 x86_64-embedded x86_64-iphonesim x86_64-aros x86_64-dragonfly arm-linux arm-palmos arm-darwin arm-wince arm-gba arm-nds arm-embedded arm-symbian arm-android arm-aros powerpc64-linux powerpc64-darwin powerpc64-embedded powerpc64-aix avr-embedded armeb-linux armeb-embedded mips-linux mipsel-linux mipsel-embedded mipsel-android jvm-java jvm-android i8086-embedded i8086-msdos i8086-win16 aarch64-linux aarch64-darwin wasm-wasm sparc64-linux
+BSDs = freebsd netbsd openbsd darwin dragonfly
+UNIXs = linux $(BSDs) solaris qnx haiku aix
+LIMIT83fs = go32v2 os2 emx watcom msdos win16
+OSNeedsComspecToRunBatch = go32v2 watcom
+FORCE:
+.PHONY: FORCE
+override PATH:=$(patsubst %/,%,$(subst \,/,$(PATH)))
+ifneq ($(findstring darwin,$(OSTYPE)),)
+inUnix=1 #darwin
+SEARCHPATH:=$(filter-out .,$(subst :, ,$(PATH)))
+else
+ifeq ($(findstring ;,$(PATH)),)
+inUnix=1
+SEARCHPATH:=$(filter-out .,$(subst :, ,$(PATH)))
+else
+SEARCHPATH:=$(subst ;, ,$(PATH))
+endif
+endif
+SEARCHPATH+=$(patsubst %/,%,$(subst \,/,$(dir $(MAKE))))
+PWD:=$(strip $(wildcard $(addsuffix /pwd.exe,$(SEARCHPATH))))
+ifeq ($(PWD),)
+PWD:=$(strip $(wildcard $(addsuffix /pwd,$(SEARCHPATH))))
+ifeq ($(PWD),)
+$(error You need the GNU utils package to use this Makefile)
+else
+PWD:=$(firstword $(PWD))
+SRCEXEEXT=
+endif
+else
+PWD:=$(firstword $(PWD))
+SRCEXEEXT=.exe
+endif
+ifndef inUnix
+ifeq ($(OS),Windows_NT)
+inWinNT=1
+else
+ifdef OS2_SHELL
+inOS2=1
+endif
+endif
+else
+ifneq ($(findstring cygdrive,$(PATH)),)
+inCygWin=1
+endif
+endif
+ifdef inUnix
+SRCBATCHEXT=.sh
+else
+ifdef inOS2
+SRCBATCHEXT=.cmd
+else
+SRCBATCHEXT=.bat
+endif
+endif
+ifdef COMSPEC
+ifneq ($(findstring $(OS_SOURCE),$(OSNeedsComspecToRunBatch)),)
+ifndef RUNBATCH
+RUNBATCH=$(COMSPEC) /C
+endif
+endif
+endif
+ifdef inUnix
+PATHSEP=/
+else
+PATHSEP:=$(subst /,\,/)
+ifdef inCygWin
+PATHSEP=/
+endif
+endif
+ifdef PWD
+BASEDIR:=$(subst \,/,$(shell $(PWD)))
+ifdef inCygWin
+ifneq ($(findstring /cygdrive/,$(BASEDIR)),)
+BASENODIR:=$(patsubst /cygdrive%,%,$(BASEDIR))
+BASEDRIVE:=$(firstword $(subst /, ,$(BASENODIR)))
+BASEDIR:=$(subst /cygdrive/$(BASEDRIVE)/,$(BASEDRIVE):/,$(BASEDIR))
+endif
+endif
+else
+BASEDIR=.
+endif
+ifdef inOS2
+ifndef ECHO
+ECHO:=$(strip $(wildcard $(addsuffix /gecho$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO:=$(strip $(wildcard $(addsuffix /echo$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO=echo
+else
+ECHO:=$(firstword $(ECHO))
+endif
+else
+ECHO:=$(firstword $(ECHO))
+endif
+endif
+export ECHO
+endif
+override DEFAULT_FPCDIR=../..
+ifndef FPC
+ifdef PP
+FPC=$(PP)
+endif
+endif
+ifndef FPC
+FPCPROG:=$(strip $(wildcard $(addsuffix /fpc$(SRCEXEEXT),$(SEARCHPATH))))
+ifneq ($(FPCPROG),)
+FPCPROG:=$(firstword $(FPCPROG))
+ifneq ($(CPU_TARGET),)
+FPC:=$(shell $(FPCPROG) -P$(CPU_TARGET) -PB)
+else
+FPC:=$(shell $(FPCPROG) -PB)
+endif
+ifneq ($(findstring Error,$(FPC)),)
+override FPC=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+else
+ifeq ($(strip $(wildcard $(FPC))),)
+FPC:=$(firstword $(FPCPROG))
+endif
+endif
+else
+override FPC=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+endif
+endif
+override FPC:=$(subst $(SRCEXEEXT),,$(FPC))
+override FPC:=$(subst \,/,$(FPC))$(SRCEXEEXT)
+FOUNDFPC:=$(strip $(wildcard $(FPC)))
+ifeq ($(FOUNDFPC),)
+FOUNDFPC=$(strip $(wildcard $(addsuffix /$(FPC),$(SEARCHPATH))))
+ifeq ($(FOUNDFPC),)
+$(error Compiler $(FPC) not found)
+endif
+endif
+ifndef FPC_COMPILERINFO
+FPC_COMPILERINFO:=$(shell $(FPC) -iVSPTPSOTO)
+endif
+ifndef FPC_VERSION
+FPC_VERSION:=$(word 1,$(FPC_COMPILERINFO))
+endif
+export FPC FPC_VERSION FPC_COMPILERINFO
+unexport CHECKDEPEND ALLDEPENDENCIES
+ifndef CPU_TARGET
+ifdef CPU_TARGET_DEFAULT
+CPU_TARGET=$(CPU_TARGET_DEFAULT)
+endif
+endif
+ifndef OS_TARGET
+ifdef OS_TARGET_DEFAULT
+OS_TARGET=$(OS_TARGET_DEFAULT)
+endif
+endif
+ifndef CPU_SOURCE
+CPU_SOURCE:=$(word 2,$(FPC_COMPILERINFO))
+endif
+ifndef CPU_TARGET
+CPU_TARGET:=$(word 3,$(FPC_COMPILERINFO))
+endif
+ifndef OS_SOURCE
+OS_SOURCE:=$(word 4,$(FPC_COMPILERINFO))
+endif
+ifndef OS_TARGET
+OS_TARGET:=$(word 5,$(FPC_COMPILERINFO))
+endif
+FULL_TARGET=$(CPU_TARGET)-$(OS_TARGET)
+FULL_SOURCE=$(CPU_SOURCE)-$(OS_SOURCE)
+ifeq ($(CPU_TARGET),armeb)
+ARCH=arm
+override FPCOPT+=-Cb
+else
+ifeq ($(CPU_TARGET),armel)
+ARCH=arm
+override FPCOPT+=-CaEABI
+else
+ARCH=$(CPU_TARGET)
+endif
+endif
+ifeq ($(FULL_TARGET),arm-embedded)
+ifeq ($(SUBARCH),)
+$(error When compiling for arm-embedded, a sub-architecture (e.g. SUBARCH=armv4t or SUBARCH=armv7m) must be defined)
+endif
+override FPCOPT+=-Cp$(SUBARCH)
+endif
+ifeq ($(FULL_TARGET),avr-embedded)
+ifeq ($(SUBARCH),)
+$(error When compiling for avr-embedded, a sub-architecture (e.g. SUBARCH=avr25 or SUBARCH=avr35) must be defined)
+endif
+override FPCOPT+=-Cp$(SUBARCH)
+endif
+ifeq ($(FULL_TARGET),mipsel-embedded)
+ifeq ($(SUBARCH),)
+$(error When compiling for mipsel-embedded, a sub-architecture (e.g. SUBARCH=pic32mx) must be defined)
+endif
+override FPCOPT+=-Cp$(SUBARCH)
+endif
+ifneq ($(findstring $(OS_SOURCE),$(LIMIT83fs)),)
+TARGETSUFFIX=$(OS_TARGET)
+SOURCESUFFIX=$(OS_SOURCE)
+else
+ifneq ($(findstring $(OS_TARGET),$(LIMIT83fs)),)
+TARGETSUFFIX=$(OS_TARGET)
+else
+TARGETSUFFIX=$(FULL_TARGET)
+endif
+SOURCESUFFIX=$(FULL_SOURCE)
+endif
+ifneq ($(FULL_TARGET),$(FULL_SOURCE))
+CROSSCOMPILE=1
+endif
+ifeq ($(findstring makefile,$(MAKECMDGOALS)),)
+ifeq ($(findstring $(FULL_TARGET),$(MAKEFILETARGETS)),)
+$(error The Makefile doesn't support target $(FULL_TARGET), please run fpcmake first)
+endif
+endif
+ifneq ($(findstring $(OS_TARGET),$(BSDs)),)
+BSDhier=1
+endif
+ifeq ($(OS_TARGET),linux)
+linuxHier=1
+endif
+ifndef CROSSCOMPILE
+BUILDFULLNATIVE=1
+export BUILDFULLNATIVE
+endif
+ifdef BUILDFULLNATIVE
+BUILDNATIVE=1
+export BUILDNATIVE
+endif
+export OS_TARGET OS_SOURCE ARCH CPU_TARGET CPU_SOURCE FULL_TARGET FULL_SOURCE TARGETSUFFIX SOURCESUFFIX CROSSCOMPILE
+ifdef FPCDIR
+override FPCDIR:=$(subst \,/,$(FPCDIR))
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl)),)
+override FPCDIR=wrong
+endif
+else
+override FPCDIR=wrong
+endif
+ifdef DEFAULT_FPCDIR
+ifeq ($(FPCDIR),wrong)
+override FPCDIR:=$(subst \,/,$(DEFAULT_FPCDIR))
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl)),)
+override FPCDIR=wrong
+endif
+endif
+endif
+ifeq ($(FPCDIR),wrong)
+ifdef inUnix
+override FPCDIR=/usr/local/lib/fpc/$(FPC_VERSION)
+ifeq ($(wildcard $(FPCDIR)/units),)
+override FPCDIR=/usr/lib/fpc/$(FPC_VERSION)
+endif
+else
+override FPCDIR:=$(subst /$(FPC),,$(firstword $(strip $(wildcard $(addsuffix /$(FPC),$(SEARCHPATH))))))
+override FPCDIR:=$(FPCDIR)/..
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl)),)
+override FPCDIR:=$(FPCDIR)/..
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl)),)
+override FPCDIR:=$(BASEDIR)
+ifeq ($(wildcard $(addprefix $(FPCDIR)/,rtl)),)
+override FPCDIR=c:/pp
+endif
+endif
+endif
+endif
+endif
+ifndef CROSSBINDIR
+CROSSBINDIR:=$(wildcard $(FPCDIR)/bin/$(TARGETSUFFIX))
+endif
+ifneq ($(findstring $(OS_TARGET),darwin iphonesim),)
+ifeq ($(OS_SOURCE),darwin)
+DARWIN2DARWIN=1
+endif
+endif
+ifndef BINUTILSPREFIX
+ifndef CROSSBINDIR
+ifdef CROSSCOMPILE
+ifneq ($(OS_TARGET),msdos)
+ifndef DARWIN2DARWIN
+ifneq ($(CPU_TARGET),jvm)
+BINUTILSPREFIX=$(CPU_TARGET)-$(OS_TARGET)-
+ifeq ($(OS_TARGET),android)
+ifeq ($(CPU_TARGET),arm)
+BINUTILSPREFIX=arm-linux-androideabi-
+else
+ifeq ($(CPU_TARGET),i386)
+BINUTILSPREFIX=i686-linux-android-
+else
+ifeq ($(CPU_TARGET),mipsel)
+BINUTILSPREFIX=mipsel-linux-android-
+endif
+endif
+endif
+endif
+endif
+endif
+else
+BINUTILSPREFIX=$(OS_TARGET)-
+endif
+endif
+endif
+endif
+UNITSDIR:=$(wildcard $(FPCDIR)/units/$(TARGETSUFFIX))
+ifeq ($(UNITSDIR),)
+UNITSDIR:=$(wildcard $(FPCDIR)/units/$(OS_TARGET))
+endif
+PACKAGESDIR:=$(wildcard $(FPCDIR) $(FPCDIR)/packages)
+ifndef FPCFPMAKE
+ifdef CROSSCOMPILE
+ifeq ($(strip $(wildcard $(addsuffix /compiler/ppc$(SRCEXEEXT),$(FPCDIR)))),)
+FPCPROG:=$(strip $(wildcard $(addsuffix /fpc$(SRCEXEEXT),$(SEARCHPATH))))
+ifneq ($(FPCPROG),)
+FPCPROG:=$(firstword $(FPCPROG))
+FPCFPMAKE:=$(shell $(FPCPROG) -PB)
+ifeq ($(strip $(wildcard $(FPCFPMAKE))),)
+FPCFPMAKE:=$(firstword $(FPCPROG))
+endif
+else
+override FPCFPMAKE=$(firstword $(strip $(wildcard $(addsuffix /ppc386$(SRCEXEEXT),$(SEARCHPATH)))))
+endif
+else
+FPCFPMAKE=$(strip $(wildcard $(addsuffix /compiler/ppc$(SRCEXEEXT),$(FPCDIR))))
+FPMAKE_SKIP_CONFIG=-n
+export FPCFPMAKE
+export FPMAKE_SKIP_CONFIG
+endif
+else
+FPMAKE_SKIP_CONFIG=-n
+FPCFPMAKE=$(FPC)
+endif
+endif
+override PACKAGE_NAME=fcl-report
+override PACKAGE_VERSION=3.1.1
+FPMAKE_BIN_CLEAN=$(wildcard ./fpmake$(SRCEXEEXT))
+ifdef OS_TARGET
+FPC_TARGETOPT+=--os=$(OS_TARGET)
+endif
+ifdef CPU_TARGET
+FPC_TARGETOPT+=--cpu=$(CPU_TARGET)
+endif
+LOCALFPMAKE=./fpmake$(SRCEXEEXT)
+override INSTALL_FPCPACKAGE=y
+ifdef REQUIRE_UNITSDIR
+override UNITSDIR+=$(REQUIRE_UNITSDIR)
+endif
+ifdef REQUIRE_PACKAGESDIR
+override PACKAGESDIR+=$(REQUIRE_PACKAGESDIR)
+endif
+ifdef ZIPINSTALL
+ifneq ($(findstring $(OS_TARGET),$(UNIXs)),)
+UNIXHier=1
+endif
+else
+ifneq ($(findstring $(OS_SOURCE),$(UNIXs)),)
+UNIXHier=1
+endif
+endif
+ifndef INSTALL_PREFIX
+ifdef PREFIX
+INSTALL_PREFIX=$(PREFIX)
+endif
+endif
+ifndef INSTALL_PREFIX
+ifdef UNIXHier
+INSTALL_PREFIX=/usr/local
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_BASEDIR:=/pp
+else
+INSTALL_BASEDIR:=/$(PACKAGE_NAME)
+endif
+endif
+endif
+export INSTALL_PREFIX
+ifdef INSTALL_FPCSUBDIR
+export INSTALL_FPCSUBDIR
+endif
+ifndef DIST_DESTDIR
+DIST_DESTDIR:=$(BASEDIR)
+endif
+export DIST_DESTDIR
+ifndef COMPILER_UNITTARGETDIR
+ifdef PACKAGEDIR_MAIN
+COMPILER_UNITTARGETDIR=$(PACKAGEDIR_MAIN)/units/$(TARGETSUFFIX)
+else
+COMPILER_UNITTARGETDIR=units/$(TARGETSUFFIX)
+endif
+endif
+ifndef COMPILER_TARGETDIR
+COMPILER_TARGETDIR=.
+endif
+ifndef INSTALL_BASEDIR
+ifdef UNIXHier
+ifdef INSTALL_FPCPACKAGE
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)/lib/fpc/$(FPC_VERSION)
+else
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)/lib/$(PACKAGE_NAME)
+endif
+else
+INSTALL_BASEDIR:=$(INSTALL_PREFIX)
+endif
+endif
+ifndef INSTALL_BINDIR
+ifdef UNIXHier
+INSTALL_BINDIR:=$(INSTALL_PREFIX)/bin
+else
+INSTALL_BINDIR:=$(INSTALL_BASEDIR)/bin
+ifdef INSTALL_FPCPACKAGE
+ifdef CROSSCOMPILE
+ifdef CROSSINSTALL
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(SOURCESUFFIX)
+else
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(TARGETSUFFIX)
+endif
+else
+INSTALL_BINDIR:=$(INSTALL_BINDIR)/$(TARGETSUFFIX)
+endif
+endif
+endif
+endif
+ifndef INSTALL_UNITDIR
+INSTALL_UNITDIR:=$(INSTALL_BASEDIR)/units/$(TARGETSUFFIX)
+ifdef INSTALL_FPCPACKAGE
+ifdef PACKAGE_NAME
+INSTALL_UNITDIR:=$(INSTALL_UNITDIR)/$(PACKAGE_NAME)
+endif
+endif
+endif
+ifndef INSTALL_LIBDIR
+ifdef UNIXHier
+INSTALL_LIBDIR:=$(INSTALL_PREFIX)/lib
+else
+INSTALL_LIBDIR:=$(INSTALL_UNITDIR)
+endif
+endif
+ifndef INSTALL_SOURCEDIR
+ifdef UNIXHier
+ifdef BSDhier
+SRCPREFIXDIR=share/src
+else
+ifdef linuxHier
+SRCPREFIXDIR=share/src
+else
+SRCPREFIXDIR=src
+endif
+endif
+ifdef INSTALL_FPCPACKAGE
+ifdef INSTALL_FPCSUBDIR
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/fpc-$(FPC_VERSION)/$(INSTALL_FPCSUBDIR)/$(PACKAGE_NAME)
+else
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+endif
+else
+INSTALL_SOURCEDIR:=$(INSTALL_PREFIX)/$(SRCPREFIXDIR)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+ifdef INSTALL_FPCSUBDIR
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source/$(INSTALL_FPCSUBDIR)/$(PACKAGE_NAME)
+else
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source/$(PACKAGE_NAME)
+endif
+else
+INSTALL_SOURCEDIR:=$(INSTALL_BASEDIR)/source
+endif
+endif
+endif
+ifndef INSTALL_DOCDIR
+ifdef UNIXHier
+ifdef BSDhier
+DOCPREFIXDIR=share/doc
+else
+ifdef linuxHier
+DOCPREFIXDIR=share/doc
+else
+DOCPREFIXDIR=doc
+endif
+endif
+ifdef INSTALL_FPCPACKAGE
+INSTALL_DOCDIR:=$(INSTALL_PREFIX)/$(DOCPREFIXDIR)/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+else
+INSTALL_DOCDIR:=$(INSTALL_PREFIX)/$(DOCPREFIXDIR)/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_DOCDIR:=$(INSTALL_BASEDIR)/doc/$(PACKAGE_NAME)
+else
+INSTALL_DOCDIR:=$(INSTALL_BASEDIR)/doc
+endif
+endif
+endif
+ifndef INSTALL_EXAMPLEDIR
+ifdef UNIXHier
+ifdef INSTALL_FPCPACKAGE
+ifdef BSDhier
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/share/examples/fpc-$(FPC_VERSION)/$(PACKAGE_NAME)
+else
+ifdef linuxHier
+INSTALL_EXAMPLEDIR:=$(INSTALL_DOCDIR)/examples
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/doc/fpc-$(FPC_VERSION)/examples/$(PACKAGE_NAME)
+endif
+endif
+else
+ifdef BSDhier
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/share/examples/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+else
+ifdef linuxHier
+INSTALL_EXAMPLEDIR:=$(INSTALL_DOCDIR)/examples/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_PREFIX)/doc/$(PACKAGE_NAME)-$(PACKAGE_VERSION)
+endif
+endif
+endif
+else
+ifdef INSTALL_FPCPACKAGE
+INSTALL_EXAMPLEDIR:=$(INSTALL_BASEDIR)/examples/$(PACKAGE_NAME)
+else
+INSTALL_EXAMPLEDIR:=$(INSTALL_BASEDIR)/examples
+endif
+endif
+endif
+ifndef INSTALL_DATADIR
+INSTALL_DATADIR=$(INSTALL_BASEDIR)
+endif
+ifndef INSTALL_SHAREDDIR
+INSTALL_SHAREDDIR=$(INSTALL_PREFIX)/lib
+endif
+ifdef CROSSCOMPILE
+ifndef CROSSBINDIR
+CROSSBINDIR:=$(wildcard $(CROSSTARGETDIR)/bin/$(SOURCESUFFIX))
+ifeq ($(CROSSBINDIR),)
+CROSSBINDIR:=$(wildcard $(INSTALL_BASEDIR)/cross/$(TARGETSUFFIX)/bin/$(FULL_SOURCE))
+endif
+endif
+else
+CROSSBINDIR=
+endif
+BATCHEXT=.bat
+LOADEREXT=.as
+EXEEXT=.exe
+PPLEXT=.ppl
+PPUEXT=.ppu
+OEXT=.o
+ASMEXT=.s
+SMARTEXT=.sl
+STATICLIBEXT=.a
+SHAREDLIBEXT=.so
+SHAREDLIBPREFIX=libfp
+STATICLIBPREFIX=libp
+IMPORTLIBPREFIX=libimp
+RSTEXT=.rst
+EXEDBGEXT=.dbg
+ifeq ($(OS_TARGET),go32v1)
+STATICLIBPREFIX=
+SHORTSUFFIX=v1
+endif
+ifeq ($(OS_TARGET),go32v2)
+STATICLIBPREFIX=
+SHORTSUFFIX=dos
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),watcom)
+STATICLIBPREFIX=
+OEXT=.obj
+ASMEXT=.asm
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=wat
+IMPORTLIBPREFIX=
+endif
+ifneq ($(CPU_TARGET),jvm)
+ifeq ($(OS_TARGET),android)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=lnx
+endif
+endif
+ifeq ($(OS_TARGET),linux)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=lnx
+endif
+ifeq ($(OS_TARGET),dragonfly)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=df
+endif
+ifeq ($(OS_TARGET),freebsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=fbs
+endif
+ifeq ($(OS_TARGET),netbsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=nbs
+endif
+ifeq ($(OS_TARGET),openbsd)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=obs
+endif
+ifeq ($(OS_TARGET),win32)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=w32
+endif
+ifeq ($(OS_TARGET),os2)
+BATCHEXT=.cmd
+AOUTEXT=.out
+STATICLIBPREFIX=
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=os2
+ECHO=echo
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),emx)
+BATCHEXT=.cmd
+AOUTEXT=.out
+STATICLIBPREFIX=
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=emx
+ECHO=echo
+IMPORTLIBPREFIX=
+endif
+ifeq ($(OS_TARGET),amiga)
+EXEEXT=
+SHAREDLIBEXT=.library
+SHORTSUFFIX=amg
+endif
+ifeq ($(OS_TARGET),aros)
+EXEEXT=
+SHAREDLIBEXT=.library
+SHORTSUFFIX=aros
+endif
+ifeq ($(OS_TARGET),morphos)
+EXEEXT=
+SHAREDLIBEXT=.library
+SHORTSUFFIX=mos
+endif
+ifeq ($(OS_TARGET),atari)
+EXEEXT=.ttp
+SHORTSUFFIX=ata
+endif
+ifeq ($(OS_TARGET),beos)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=be
+endif
+ifeq ($(OS_TARGET),haiku)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=hai
+endif
+ifeq ($(OS_TARGET),solaris)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=sun
+endif
+ifeq ($(OS_TARGET),qnx)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=qnx
+endif
+ifeq ($(OS_TARGET),netware)
+EXEEXT=.nlm
+STATICLIBPREFIX=
+SHORTSUFFIX=nw
+IMPORTLIBPREFIX=imp
+endif
+ifeq ($(OS_TARGET),netwlibc)
+EXEEXT=.nlm
+STATICLIBPREFIX=
+SHORTSUFFIX=nwl
+IMPORTLIBPREFIX=imp
+endif
+ifeq ($(OS_TARGET),macos)
+BATCHEXT=
+EXEEXT=
+DEBUGSYMEXT=.xcoff
+SHORTSUFFIX=mac
+IMPORTLIBPREFIX=imp
+endif
+ifneq ($(findstring $(OS_TARGET),darwin iphonesim),)
+BATCHEXT=.sh
+EXEEXT=
+HASSHAREDLIB=1
+SHORTSUFFIX=dwn
+EXEDBGEXT=.dSYM
+endif
+ifeq ($(OS_TARGET),gba)
+EXEEXT=.gba
+SHAREDLIBEXT=.so
+SHORTSUFFIX=gba
+endif
+ifeq ($(OS_TARGET),symbian)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=symbian
+endif
+ifeq ($(OS_TARGET),NativeNT)
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=nativent
+endif
+ifeq ($(OS_TARGET),wii)
+EXEEXT=.dol
+SHAREDLIBEXT=.so
+SHORTSUFFIX=wii
+endif
+ifeq ($(OS_TARGET),aix)
+BATCHEXT=.sh
+EXEEXT=
+SHORTSUFFIX=aix
+endif
+ifeq ($(OS_TARGET),java)
+OEXT=.class
+ASMEXT=.j
+SHAREDLIBEXT=.jar
+SHORTSUFFIX=java
+endif
+ifeq ($(CPU_TARGET),jvm)
+ifeq ($(OS_TARGET),android)
+OEXT=.class
+ASMEXT=.j
+SHAREDLIBEXT=.jar
+SHORTSUFFIX=android
+endif
+endif
+ifeq ($(OS_TARGET),msdos)
+STATICLIBPREFIX=
+STATICLIBEXT=.a
+SHORTSUFFIX=d16
+endif
+ifeq ($(OS_TARGET),embedded)
+ifeq ($(CPU_TARGET),i8086)
+STATICLIBPREFIX=
+STATICLIBEXT=.a
+else
+EXEEXT=.bin
+endif
+SHORTSUFFIX=emb
+endif
+ifeq ($(OS_TARGET),win16)
+STATICLIBPREFIX=
+STATICLIBEXT=.a
+SHAREDLIBEXT=.dll
+SHORTSUFFIX=w16
+endif
+ifneq ($(findstring $(OS_SOURCE),$(LIMIT83fs)),)
+FPCMADE=fpcmade.$(SHORTSUFFIX)
+ZIPSUFFIX=$(SHORTSUFFIX)
+ZIPCROSSPREFIX=
+ZIPSOURCESUFFIX=src
+ZIPEXAMPLESUFFIX=exm
+else
+FPCMADE=fpcmade.$(TARGETSUFFIX)
+ZIPSOURCESUFFIX=.source
+ZIPEXAMPLESUFFIX=.examples
+ifdef CROSSCOMPILE
+ZIPSUFFIX=.$(SOURCESUFFIX)
+ZIPCROSSPREFIX=$(TARGETSUFFIX)-
+else
+ZIPSUFFIX=.$(TARGETSUFFIX)
+ZIPCROSSPREFIX=
+endif
+endif
+ifndef ECHO
+ECHO:=$(strip $(wildcard $(addsuffix /gecho$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO:=$(strip $(wildcard $(addsuffix /echo$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ECHO),)
+ECHO= __missing_command_ECHO
+else
+ECHO:=$(firstword $(ECHO))
+endif
+else
+ECHO:=$(firstword $(ECHO))
+endif
+endif
+export ECHO
+ifndef DATE
+DATE:=$(strip $(wildcard $(addsuffix /gdate$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(DATE),)
+DATE:=$(strip $(wildcard $(addsuffix /date$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(DATE),)
+DATE= __missing_command_DATE
+else
+DATE:=$(firstword $(DATE))
+endif
+else
+DATE:=$(firstword $(DATE))
+endif
+endif
+export DATE
+ifndef GINSTALL
+GINSTALL:=$(strip $(wildcard $(addsuffix /ginstall$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(GINSTALL),)
+GINSTALL:=$(strip $(wildcard $(addsuffix /install$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(GINSTALL),)
+GINSTALL= __missing_command_GINSTALL
+else
+GINSTALL:=$(firstword $(GINSTALL))
+endif
+else
+GINSTALL:=$(firstword $(GINSTALL))
+endif
+endif
+export GINSTALL
+ifndef CPPROG
+CPPROG:=$(strip $(wildcard $(addsuffix /cp$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(CPPROG),)
+CPPROG= __missing_command_CPPROG
+else
+CPPROG:=$(firstword $(CPPROG))
+endif
+endif
+export CPPROG
+ifndef RMPROG
+RMPROG:=$(strip $(wildcard $(addsuffix /rm$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(RMPROG),)
+RMPROG= __missing_command_RMPROG
+else
+RMPROG:=$(firstword $(RMPROG))
+endif
+endif
+export RMPROG
+ifndef MVPROG
+MVPROG:=$(strip $(wildcard $(addsuffix /mv$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MVPROG),)
+MVPROG= __missing_command_MVPROG
+else
+MVPROG:=$(firstword $(MVPROG))
+endif
+endif
+export MVPROG
+ifndef MKDIRPROG
+MKDIRPROG:=$(strip $(wildcard $(addsuffix /gmkdir$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MKDIRPROG),)
+MKDIRPROG:=$(strip $(wildcard $(addsuffix /mkdir$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(MKDIRPROG),)
+MKDIRPROG= __missing_command_MKDIRPROG
+else
+MKDIRPROG:=$(firstword $(MKDIRPROG))
+endif
+else
+MKDIRPROG:=$(firstword $(MKDIRPROG))
+endif
+endif
+export MKDIRPROG
+ifndef ECHOREDIR
+ifndef inUnix
+ECHOREDIR=echo
+else
+ECHOREDIR=$(ECHO)
+endif
+endif
+ifndef COPY
+COPY:=$(CPPROG) -fp
+endif
+ifndef COPYTREE
+COPYTREE:=$(CPPROG) -Rfp
+endif
+ifndef MKDIRTREE
+MKDIRTREE:=$(MKDIRPROG) -p
+endif
+ifndef MOVE
+MOVE:=$(MVPROG) -f
+endif
+ifndef DEL
+DEL:=$(RMPROG) -f
+endif
+ifndef DELTREE
+DELTREE:=$(RMPROG) -rf
+endif
+ifndef INSTALL
+ifdef inUnix
+INSTALL:=$(GINSTALL) -c -m 644
+else
+INSTALL:=$(COPY)
+endif
+endif
+ifndef INSTALLEXE
+ifdef inUnix
+INSTALLEXE:=$(GINSTALL) -c -m 755
+else
+INSTALLEXE:=$(COPY)
+endif
+endif
+ifndef MKDIR
+MKDIR:=$(GINSTALL) -m 755 -d
+endif
+export ECHOREDIR COPY COPYTREE MOVE DEL DELTREE INSTALL INSTALLEXE MKDIR
+ifndef PPUMOVE
+PPUMOVE:=$(strip $(wildcard $(addsuffix /ppumove$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(PPUMOVE),)
+PPUMOVE= __missing_command_PPUMOVE
+else
+PPUMOVE:=$(firstword $(PPUMOVE))
+endif
+endif
+export PPUMOVE
+ifndef FPCMAKE
+FPCMAKE:=$(strip $(wildcard $(addsuffix /fpcmake$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(FPCMAKE),)
+FPCMAKE= __missing_command_FPCMAKE
+else
+FPCMAKE:=$(firstword $(FPCMAKE))
+endif
+endif
+export FPCMAKE
+ifndef ZIPPROG
+ZIPPROG:=$(strip $(wildcard $(addsuffix /zip$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(ZIPPROG),)
+ZIPPROG= __missing_command_ZIPPROG
+else
+ZIPPROG:=$(firstword $(ZIPPROG))
+endif
+endif
+export ZIPPROG
+ifndef TARPROG
+TARPROG:=$(strip $(wildcard $(addsuffix /gtar$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(TARPROG),)
+TARPROG:=$(strip $(wildcard $(addsuffix /tar$(SRCEXEEXT),$(SEARCHPATH))))
+ifeq ($(TARPROG),)
+TARPROG= __missing_command_TARPROG
+else
+TARPROG:=$(firstword $(TARPROG))
+endif
+else
+TARPROG:=$(firstword $(TARPROG))
+endif
+endif
+export TARPROG
+ASNAME=$(BINUTILSPREFIX)as
+LDNAME=$(BINUTILSPREFIX)ld
+ARNAME=$(BINUTILSPREFIX)ar
+RCNAME=$(BINUTILSPREFIX)rc
+NASMNAME=$(BINUTILSPREFIX)nasm
+ifndef ASPROG
+ifdef CROSSBINDIR
+ASPROG=$(CROSSBINDIR)/$(ASNAME)$(SRCEXEEXT)
+else
+ASPROG=$(ASNAME)
+endif
+endif
+ifndef LDPROG
+ifdef CROSSBINDIR
+LDPROG=$(CROSSBINDIR)/$(LDNAME)$(SRCEXEEXT)
+else
+LDPROG=$(LDNAME)
+endif
+endif
+ifndef RCPROG
+ifdef CROSSBINDIR
+RCPROG=$(CROSSBINDIR)/$(RCNAME)$(SRCEXEEXT)
+else
+RCPROG=$(RCNAME)
+endif
+endif
+ifndef ARPROG
+ifdef CROSSBINDIR
+ARPROG=$(CROSSBINDIR)/$(ARNAME)$(SRCEXEEXT)
+else
+ARPROG=$(ARNAME)
+endif
+endif
+ifndef NASMPROG
+ifdef CROSSBINDIR
+NASMPROG=$(CROSSBINDIR)/$(NASMNAME)$(SRCEXEEXT)
+else
+NASMPROG=$(NASMNAME)
+endif
+endif
+AS=$(ASPROG)
+LD=$(LDPROG)
+RC=$(RCPROG)
+AR=$(ARPROG)
+NASM=$(NASMPROG)
+ifdef inUnix
+PPAS=./ppas$(SRCBATCHEXT)
+else
+PPAS=ppas$(SRCBATCHEXT)
+endif
+ifdef inUnix
+LDCONFIG=ldconfig
+else
+LDCONFIG=
+endif
+ifdef DATE
+DATESTR:=$(shell $(DATE) +%Y%m%d)
+else
+DATESTR=
+endif
+ZIPOPT=-9
+ZIPEXT=.zip
+ifeq ($(USETAR),bz2)
+TAROPT=vj
+TAREXT=.tar.bz2
+else
+TAROPT=vz
+TAREXT=.tar.gz
+endif
+override REQUIRE_PACKAGES=rtl fcl-base fcl-xml fcl-image
+ifeq ($(FULL_TARGET),i386-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-go32v2)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-win32)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-os2)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-beos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-haiku)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-qnx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-netware)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-openbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-wdosx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-emx)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-watcom)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-netwlibc)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-wince)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-symbian)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-nativent)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-iphonesim)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-android)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i386-aros)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-amiga)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-atari)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-openbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-palmos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),m68k-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-amiga)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-macos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-morphos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-wii)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc-aix)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),sparc-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),sparc-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),sparc-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),sparc-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-freebsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-netbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-solaris)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-openbsd)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-win64)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-iphonesim)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-aros)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),x86_64-dragonfly)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-palmos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-wince)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-gba)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-nds)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-symbian)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-android)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),arm-aros)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),powerpc64-aix)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),avr-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),armeb-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),armeb-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),mips-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),mipsel-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),mipsel-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),mipsel-android)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),jvm-java)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),jvm-android)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i8086-embedded)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i8086-msdos)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),i8086-win16)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),aarch64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),aarch64-darwin)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),wasm-wasm)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifeq ($(FULL_TARGET),sparc64-linux)
+REQUIRE_PACKAGES_RTL=1
+REQUIRE_PACKAGES_PASZLIB=1
+REQUIRE_PACKAGES_FCL-PROCESS=1
+REQUIRE_PACKAGES_HASH=1
+REQUIRE_PACKAGES_LIBTAR=1
+REQUIRE_PACKAGES_FPMKUNIT=1
+REQUIRE_PACKAGES_FCL-BASE=1
+REQUIRE_PACKAGES_FCL-XML=1
+REQUIRE_PACKAGES_FCL-IMAGE=1
+endif
+ifdef REQUIRE_PACKAGES_RTL
+PACKAGEDIR_RTL:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /rtl/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_RTL),)
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units/$(TARGETSUFFIX)),)
+UNITDIR_RTL=$(PACKAGEDIR_RTL)/units/$(TARGETSUFFIX)
+else
+UNITDIR_RTL=$(PACKAGEDIR_RTL)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_RTL)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_RTL=$(PACKAGEDIR_RTL)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_RTL)/$(OS_TARGET)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_RTL)/$(OS_TARGET) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_RTL)/$(OS_TARGET)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_RTL=
+UNITDIR_RTL:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /rtl/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_RTL),)
+UNITDIR_RTL:=$(firstword $(UNITDIR_RTL))
+else
+UNITDIR_RTL=
+endif
+endif
+ifdef UNITDIR_RTL
+override COMPILER_UNITDIR+=$(UNITDIR_RTL)
+endif
+ifdef UNITDIR_FPMAKE_RTL
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_RTL)
+endif
+endif
+ifdef REQUIRE_PACKAGES_PASZLIB
+PACKAGEDIR_PASZLIB:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /paszlib/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_PASZLIB),)
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units/$(TARGETSUFFIX)),)
+UNITDIR_PASZLIB=$(PACKAGEDIR_PASZLIB)/units/$(TARGETSUFFIX)
+else
+UNITDIR_PASZLIB=$(PACKAGEDIR_PASZLIB)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_PASZLIB)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_PASZLIB=$(PACKAGEDIR_PASZLIB)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_PASZLIB)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_PASZLIB) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_PASZLIB)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_PASZLIB=
+UNITDIR_PASZLIB:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /paszlib/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_PASZLIB),)
+UNITDIR_PASZLIB:=$(firstword $(UNITDIR_PASZLIB))
+else
+UNITDIR_PASZLIB=
+endif
+endif
+ifdef UNITDIR_PASZLIB
+override COMPILER_UNITDIR+=$(UNITDIR_PASZLIB)
+endif
+ifdef UNITDIR_FPMAKE_PASZLIB
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_PASZLIB)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FCL-PROCESS
+PACKAGEDIR_FCL-PROCESS:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fcl-process/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FCL-PROCESS),)
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units/$(TARGETSUFFIX)),)
+UNITDIR_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FCL-PROCESS)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FCL-PROCESS=$(PACKAGEDIR_FCL-PROCESS)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FCL-PROCESS)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FCL-PROCESS) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FCL-PROCESS)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FCL-PROCESS=
+UNITDIR_FCL-PROCESS:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fcl-process/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FCL-PROCESS),)
+UNITDIR_FCL-PROCESS:=$(firstword $(UNITDIR_FCL-PROCESS))
+else
+UNITDIR_FCL-PROCESS=
+endif
+endif
+ifdef UNITDIR_FCL-PROCESS
+override COMPILER_UNITDIR+=$(UNITDIR_FCL-PROCESS)
+endif
+ifdef UNITDIR_FPMAKE_FCL-PROCESS
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FCL-PROCESS)
+endif
+endif
+ifdef REQUIRE_PACKAGES_HASH
+PACKAGEDIR_HASH:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /hash/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_HASH),)
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units/$(TARGETSUFFIX)),)
+UNITDIR_HASH=$(PACKAGEDIR_HASH)/units/$(TARGETSUFFIX)
+else
+UNITDIR_HASH=$(PACKAGEDIR_HASH)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_HASH)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_HASH=$(PACKAGEDIR_HASH)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_HASH)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_HASH) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_HASH)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_HASH=
+UNITDIR_HASH:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /hash/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_HASH),)
+UNITDIR_HASH:=$(firstword $(UNITDIR_HASH))
+else
+UNITDIR_HASH=
+endif
+endif
+ifdef UNITDIR_HASH
+override COMPILER_UNITDIR+=$(UNITDIR_HASH)
+endif
+ifdef UNITDIR_FPMAKE_HASH
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_HASH)
+endif
+endif
+ifdef REQUIRE_PACKAGES_LIBTAR
+PACKAGEDIR_LIBTAR:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /libtar/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_LIBTAR),)
+ifneq ($(wildcard $(PACKAGEDIR_LIBTAR)/units/$(TARGETSUFFIX)),)
+UNITDIR_LIBTAR=$(PACKAGEDIR_LIBTAR)/units/$(TARGETSUFFIX)
+else
+UNITDIR_LIBTAR=$(PACKAGEDIR_LIBTAR)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_LIBTAR)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_LIBTAR=$(PACKAGEDIR_LIBTAR)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_LIBTAR)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_LIBTAR=$(PACKAGEDIR_LIBTAR)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_LIBTAR=$(PACKAGEDIR_LIBTAR)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_LIBTAR)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_LIBTAR) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_LIBTAR)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_LIBTAR=
+UNITDIR_LIBTAR:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /libtar/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_LIBTAR),)
+UNITDIR_LIBTAR:=$(firstword $(UNITDIR_LIBTAR))
+else
+UNITDIR_LIBTAR=
+endif
+endif
+ifdef UNITDIR_LIBTAR
+override COMPILER_UNITDIR+=$(UNITDIR_LIBTAR)
+endif
+ifdef UNITDIR_FPMAKE_LIBTAR
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_LIBTAR)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FPMKUNIT
+PACKAGEDIR_FPMKUNIT:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fpmkunit/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FPMKUNIT),)
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units/$(TARGETSUFFIX)),)
+UNITDIR_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FPMKUNIT)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FPMKUNIT=$(PACKAGEDIR_FPMKUNIT)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FPMKUNIT)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FPMKUNIT) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FPMKUNIT)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FPMKUNIT=
+UNITDIR_FPMKUNIT:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fpmkunit/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FPMKUNIT),)
+UNITDIR_FPMKUNIT:=$(firstword $(UNITDIR_FPMKUNIT))
+else
+UNITDIR_FPMKUNIT=
+endif
+endif
+ifdef UNITDIR_FPMKUNIT
+override COMPILER_UNITDIR+=$(UNITDIR_FPMKUNIT)
+endif
+ifdef UNITDIR_FPMAKE_FPMKUNIT
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FPMKUNIT)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FCL-BASE
+PACKAGEDIR_FCL-BASE:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fcl-base/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FCL-BASE),)
+ifneq ($(wildcard $(PACKAGEDIR_FCL-BASE)/units/$(TARGETSUFFIX)),)
+UNITDIR_FCL-BASE=$(PACKAGEDIR_FCL-BASE)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FCL-BASE=$(PACKAGEDIR_FCL-BASE)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FCL-BASE)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-BASE=$(PACKAGEDIR_FCL-BASE)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FCL-BASE)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-BASE=$(PACKAGEDIR_FCL-BASE)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FCL-BASE=$(PACKAGEDIR_FCL-BASE)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FCL-BASE)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FCL-BASE) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FCL-BASE)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FCL-BASE=
+UNITDIR_FCL-BASE:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fcl-base/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FCL-BASE),)
+UNITDIR_FCL-BASE:=$(firstword $(UNITDIR_FCL-BASE))
+else
+UNITDIR_FCL-BASE=
+endif
+endif
+ifdef UNITDIR_FCL-BASE
+override COMPILER_UNITDIR+=$(UNITDIR_FCL-BASE)
+endif
+ifdef UNITDIR_FPMAKE_FCL-BASE
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FCL-BASE)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FCL-XML
+PACKAGEDIR_FCL-XML:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fcl-xml/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FCL-XML),)
+ifneq ($(wildcard $(PACKAGEDIR_FCL-XML)/units/$(TARGETSUFFIX)),)
+UNITDIR_FCL-XML=$(PACKAGEDIR_FCL-XML)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FCL-XML=$(PACKAGEDIR_FCL-XML)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FCL-XML)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-XML=$(PACKAGEDIR_FCL-XML)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FCL-XML)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-XML=$(PACKAGEDIR_FCL-XML)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FCL-XML=$(PACKAGEDIR_FCL-XML)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FCL-XML)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FCL-XML) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FCL-XML)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FCL-XML=
+UNITDIR_FCL-XML:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fcl-xml/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FCL-XML),)
+UNITDIR_FCL-XML:=$(firstword $(UNITDIR_FCL-XML))
+else
+UNITDIR_FCL-XML=
+endif
+endif
+ifdef UNITDIR_FCL-XML
+override COMPILER_UNITDIR+=$(UNITDIR_FCL-XML)
+endif
+ifdef UNITDIR_FPMAKE_FCL-XML
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FCL-XML)
+endif
+endif
+ifdef REQUIRE_PACKAGES_FCL-IMAGE
+PACKAGEDIR_FCL-IMAGE:=$(firstword $(subst /Makefile.fpc,,$(strip $(wildcard $(addsuffix /fcl-image/Makefile.fpc,$(PACKAGESDIR))))))
+ifneq ($(PACKAGEDIR_FCL-IMAGE),)
+ifneq ($(wildcard $(PACKAGEDIR_FCL-IMAGE)/units/$(TARGETSUFFIX)),)
+UNITDIR_FCL-IMAGE=$(PACKAGEDIR_FCL-IMAGE)/units/$(TARGETSUFFIX)
+else
+UNITDIR_FCL-IMAGE=$(PACKAGEDIR_FCL-IMAGE)
+endif
+ifneq ($(wildcard $(PACKAGEDIR_FCL-IMAGE)/units/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-IMAGE=$(PACKAGEDIR_FCL-IMAGE)/units/$(SOURCESUFFIX)
+else
+ifneq ($(wildcard $(PACKAGEDIR_FCL-IMAGE)/units_bs/$(SOURCESUFFIX)),)
+UNITDIR_FPMAKE_FCL-IMAGE=$(PACKAGEDIR_FCL-IMAGE)/units_bs/$(SOURCESUFFIX)
+else
+UNITDIR_FPMAKE_FCL-IMAGE=$(PACKAGEDIR_FCL-IMAGE)
+endif
+endif
+ifdef CHECKDEPEND
+$(PACKAGEDIR_FCL-IMAGE)/$(FPCMADE):
+	$(MAKE) -C $(PACKAGEDIR_FCL-IMAGE) $(FPCMADE)
+override ALLDEPENDENCIES+=$(PACKAGEDIR_FCL-IMAGE)/$(FPCMADE)
+endif
+else
+PACKAGEDIR_FCL-IMAGE=
+UNITDIR_FCL-IMAGE:=$(subst /Package.fpc,,$(strip $(wildcard $(addsuffix /fcl-image/Package.fpc,$(UNITSDIR)))))
+ifneq ($(UNITDIR_FCL-IMAGE),)
+UNITDIR_FCL-IMAGE:=$(firstword $(UNITDIR_FCL-IMAGE))
+else
+UNITDIR_FCL-IMAGE=
+endif
+endif
+ifdef UNITDIR_FCL-IMAGE
+override COMPILER_UNITDIR+=$(UNITDIR_FCL-IMAGE)
+endif
+ifdef UNITDIR_FPMAKE_FCL-IMAGE
+override COMPILER_FPMAKE_UNITDIR+=$(UNITDIR_FPMAKE_FCL-IMAGE)
+endif
+endif
+ifndef NOCPUDEF
+override FPCOPTDEF=$(ARCH)
+endif
+ifneq ($(OS_TARGET),$(OS_SOURCE))
+override FPCOPT+=-T$(OS_TARGET)
+endif
+ifneq ($(CPU_TARGET),$(CPU_SOURCE))
+override FPCOPT+=-P$(ARCH)
+endif
+ifeq ($(OS_SOURCE),openbsd)
+override FPCOPT+=-FD$(NEW_BINUTILS_PATH)
+override FPCMAKEOPT+=-FD$(NEW_BINUTILS_PATH)
+override FPMAKE_BUILD_OPT+=-FD$(NEW_BINUTILS_PATH)
+endif
+ifndef CROSSBOOTSTRAP
+ifneq ($(BINUTILSPREFIX),)
+override FPCOPT+=-XP$(BINUTILSPREFIX)
+endif
+ifneq ($(BINUTILSPREFIX),)
+override FPCOPT+=-Xr$(RLINKPATH)
+endif
+endif
+ifndef CROSSCOMPILE
+ifneq ($(BINUTILSPREFIX),)
+override FPCMAKEOPT+=-XP$(BINUTILSPREFIX)
+override FPMAKE_BUILD_OPT+=-XP$(BINUTILSPREFIX)
+endif
+endif
+ifdef UNITDIR
+override FPCOPT+=$(addprefix -Fu,$(UNITDIR))
+endif
+ifdef LIBDIR
+override FPCOPT+=$(addprefix -Fl,$(LIBDIR))
+endif
+ifdef OBJDIR
+override FPCOPT+=$(addprefix -Fo,$(OBJDIR))
+endif
+ifdef INCDIR
+override FPCOPT+=$(addprefix -Fi,$(INCDIR))
+endif
+ifdef LINKSMART
+override FPCOPT+=-XX
+endif
+ifdef CREATESMART
+override FPCOPT+=-CX
+endif
+ifdef DEBUG
+override FPCOPT+=-gl
+override FPCOPTDEF+=DEBUG
+endif
+ifdef RELEASE
+ifneq ($(findstring 2.0.,$(FPC_VERSION)),)
+ifeq ($(CPU_TARGET),i386)
+FPCCPUOPT:=-OG2p3
+endif
+ifeq ($(CPU_TARGET),powerpc)
+FPCCPUOPT:=-O1r
+endif
+else
+FPCCPUOPT:=-O2
+endif
+override FPCOPT+=-Ur -Xs $(FPCCPUOPT) -n
+override FPCOPTDEF+=RELEASE
+endif
+ifdef STRIP
+override FPCOPT+=-Xs
+endif
+ifdef OPTIMIZE
+override FPCOPT+=-O2
+endif
+ifdef VERBOSE
+override FPCOPT+=-vwni
+endif
+ifdef COMPILER_OPTIONS
+override FPCOPT+=$(COMPILER_OPTIONS)
+endif
+ifdef COMPILER_UNITDIR
+override FPCOPT+=$(addprefix -Fu,$(COMPILER_UNITDIR))
+endif
+ifdef COMPILER_LIBRARYDIR
+override FPCOPT+=$(addprefix -Fl,$(COMPILER_LIBRARYDIR))
+endif
+ifdef COMPILER_OBJECTDIR
+override FPCOPT+=$(addprefix -Fo,$(COMPILER_OBJECTDIR))
+endif
+ifdef COMPILER_INCLUDEDIR
+override FPCOPT+=$(addprefix -Fi,$(COMPILER_INCLUDEDIR))
+endif
+ifdef CROSSBINDIR
+override FPCOPT+=-FD$(CROSSBINDIR)
+endif
+ifdef COMPILER_TARGETDIR
+override FPCOPT+=-FE$(COMPILER_TARGETDIR)
+ifeq ($(COMPILER_TARGETDIR),.)
+override TARGETDIRPREFIX=
+else
+override TARGETDIRPREFIX=$(COMPILER_TARGETDIR)/
+endif
+endif
+ifdef COMPILER_UNITTARGETDIR
+override FPCOPT+=-FU$(COMPILER_UNITTARGETDIR)
+ifeq ($(COMPILER_UNITTARGETDIR),.)
+override UNITTARGETDIRPREFIX=
+else
+override UNITTARGETDIRPREFIX=$(COMPILER_UNITTARGETDIR)/
+endif
+else
+ifdef COMPILER_TARGETDIR
+override COMPILER_UNITTARGETDIR=$(COMPILER_TARGETDIR)
+override UNITTARGETDIRPREFIX=$(TARGETDIRPREFIX)
+endif
+endif
+ifdef CREATESHARED
+override FPCOPT+=-Cg
+endif
+ifneq ($(findstring $(OS_TARGET),dragonfly freebsd openbsd netbsd linux solaris),)
+ifneq ($(findstring $(CPU_TARGET),x86_64 mips mipsel),)
+override FPCOPT+=-Cg
+endif
+endif
+ifdef LINKSHARED
+endif
+ifdef OPT
+override FPCOPT+=$(OPT)
+endif
+ifdef FPMAKEBUILDOPT
+override FPMAKE_BUILD_OPT+=$(FPMAKEBUILDOPT)
+endif
+ifdef FPCOPTDEF
+override FPCOPT+=$(addprefix -d,$(FPCOPTDEF))
+endif
+ifdef CFGFILE
+override FPCOPT+=@$(CFGFILE)
+endif
+ifdef USEENV
+override FPCEXTCMD:=$(FPCOPT)
+override FPCOPT:=!FPCEXTCMD
+export FPCEXTCMD
+endif
+override AFULL_TARGET=$(CPU_TARGET)-$(OS_TARGET)
+override AFULL_SOURCE=$(CPU_SOURCE)-$(OS_SOURCE)
+ifneq ($(AFULL_TARGET),$(AFULL_SOURCE))
+override ACROSSCOMPILE=1
+endif
+ifdef ACROSSCOMPILE
+override FPCOPT+=$(CROSSOPT)
+endif
+override COMPILER:=$(strip $(FPC) $(FPCOPT))
+ifneq (,$(findstring -sh ,$(COMPILER)))
+UseEXECPPAS=1
+endif
+ifneq (,$(findstring -s ,$(COMPILER)))
+ifeq ($(FULL_SOURCE),$(FULL_TARGET))
+UseEXECPPAS=1
+endif
+endif
+ifneq ($(UseEXECPPAS),1)
+EXECPPAS=
+else
+ifdef RUNBATCH
+EXECPPAS:=@$(RUNBATCH) $(PPAS)
+else
+EXECPPAS:=@$(PPAS)
+endif
+endif
+ifdef TARGET_RSTS
+override RSTFILES=$(addsuffix $(RSTEXT),$(TARGET_RSTS))
+override CLEANRSTFILES+=$(RSTFILES)
+endif
+.PHONY: fpc_install fpc_sourceinstall fpc_exampleinstall
+ifdef INSTALL_UNITS
+override INSTALLPPUFILES+=$(addsuffix $(PPUEXT),$(INSTALL_UNITS))
+endif
+ifdef INSTALL_BUILDUNIT
+override INSTALLPPUFILES:=$(filter-out $(INSTALL_BUILDUNIT)$(PPUEXT),$(INSTALLPPUFILES))
+endif
+ifdef INSTALLPPUFILES
+override INSTALLPPULINKFILES:=$(subst $(PPUEXT),$(OEXT),$(INSTALLPPUFILES)) $(addprefix $(STATICLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(INSTALLPPUFILES))) $(addprefix $(IMPORTLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(INSTALLPPUFILES)))
+ifneq ($(UNITTARGETDIRPREFIX),)
+override INSTALLPPUFILES:=$(addprefix $(UNITTARGETDIRPREFIX),$(notdir $(INSTALLPPUFILES)))
+override INSTALLPPULINKFILES:=$(wildcard $(addprefix $(UNITTARGETDIRPREFIX),$(notdir $(INSTALLPPULINKFILES))))
+endif
+override INSTALL_CREATEPACKAGEFPC=1
+endif
+ifdef INSTALLEXEFILES
+ifneq ($(TARGETDIRPREFIX),)
+override INSTALLEXEFILES:=$(addprefix $(TARGETDIRPREFIX),$(notdir $(INSTALLEXEFILES)))
+endif
+endif
+fpc_install: all $(INSTALLTARGET)
+ifdef INSTALLEXEFILES
+	$(MKDIR) $(INSTALL_BINDIR)
+	$(INSTALLEXE) $(INSTALLEXEFILES) $(INSTALL_BINDIR)
+endif
+ifdef INSTALL_CREATEPACKAGEFPC
+ifdef FPCMAKE
+ifdef PACKAGE_VERSION
+ifneq ($(wildcard Makefile.fpc),)
+	$(FPCMAKE) -p -T$(CPU_TARGET)-$(OS_TARGET) Makefile.fpc
+	$(MKDIR) $(INSTALL_UNITDIR)
+	$(INSTALL) Package.fpc $(INSTALL_UNITDIR)
+endif
+endif
+endif
+endif
+ifdef INSTALLPPUFILES
+	$(MKDIR) $(INSTALL_UNITDIR)
+	$(INSTALL) $(INSTALLPPUFILES) $(INSTALL_UNITDIR)
+ifneq ($(INSTALLPPULINKFILES),)
+	$(INSTALL) $(INSTALLPPULINKFILES) $(INSTALL_UNITDIR)
+endif
+ifneq ($(wildcard $(LIB_FULLNAME)),)
+	$(MKDIR) $(INSTALL_LIBDIR)
+	$(INSTALL) $(LIB_FULLNAME) $(INSTALL_LIBDIR)
+ifdef inUnix
+	ln -sf $(LIB_FULLNAME) $(INSTALL_LIBDIR)/$(LIB_NAME)
+endif
+endif
+endif
+ifdef INSTALL_FILES
+	$(MKDIR) $(INSTALL_DATADIR)
+	$(INSTALL) $(INSTALL_FILES) $(INSTALL_DATADIR)
+endif
+fpc_sourceinstall: distclean
+	$(MKDIR) $(INSTALL_SOURCEDIR)
+	$(COPYTREE) $(BASEDIR)/* $(INSTALL_SOURCEDIR)
+fpc_exampleinstall: $(EXAMPLEINSTALLTARGET) $(addsuffix _distclean,$(TARGET_EXAMPLEDIRS))
+ifdef HASEXAMPLES
+	$(MKDIR) $(INSTALL_EXAMPLEDIR)
+endif
+ifdef EXAMPLESOURCEFILES
+	$(COPY) $(EXAMPLESOURCEFILES) $(INSTALL_EXAMPLEDIR)
+endif
+ifdef TARGET_EXAMPLEDIRS
+	$(COPYTREE) $(addsuffix /*,$(TARGET_EXAMPLEDIRS)) $(INSTALL_EXAMPLEDIR)
+endif
+.PHONY: fpc_distinstall
+fpc_distinstall: install exampleinstall
+.PHONY: fpc_zipinstall fpc_zipsourceinstall fpc_zipexampleinstall
+ifndef PACKDIR
+ifndef inUnix
+PACKDIR=$(BASEDIR)/../fpc-pack
+else
+PACKDIR=/tmp/fpc-pack
+endif
+endif
+ifndef ZIPNAME
+ifdef DIST_ZIPNAME
+ZIPNAME=$(DIST_ZIPNAME)
+else
+ZIPNAME=$(PACKAGE_NAME)
+endif
+endif
+ifndef FULLZIPNAME
+FULLZIPNAME=$(ZIPCROSSPREFIX)$(ZIPPREFIX)$(ZIPNAME)$(ZIPSUFFIX)
+endif
+ifndef ZIPTARGET
+ifdef DIST_ZIPTARGET
+ZIPTARGET=DIST_ZIPTARGET
+else
+ZIPTARGET=install
+endif
+endif
+ifndef USEZIP
+ifdef inUnix
+USETAR=1
+endif
+endif
+ifndef inUnix
+USEZIPWRAPPER=1
+endif
+ifdef USEZIPWRAPPER
+ZIPPATHSEP=$(PATHSEP)
+ZIPWRAPPER=$(subst /,$(PATHSEP),$(DIST_DESTDIR)/fpczip$(SRCBATCHEXT))
+else
+ZIPPATHSEP=/
+endif
+ZIPCMD_CDPACK:=cd $(subst /,$(ZIPPATHSEP),$(PACKDIR))
+ZIPCMD_CDBASE:=cd $(subst /,$(ZIPPATHSEP),$(BASEDIR))
+ifdef USETAR
+ZIPDESTFILE:=$(DIST_DESTDIR)/$(FULLZIPNAME)$(TAREXT)
+ZIPCMD_ZIP:=$(TARPROG) c$(TAROPT)f $(ZIPDESTFILE) *
+else
+ZIPDESTFILE:=$(DIST_DESTDIR)/$(FULLZIPNAME)$(ZIPEXT)
+ZIPCMD_ZIP:=$(subst /,$(ZIPPATHSEP),$(ZIPPROG)) -Dr $(ZIPOPT) $(ZIPDESTFILE) *
+endif
+fpc_zipinstall:
+	$(MAKE) $(ZIPTARGET) INSTALL_PREFIX=$(PACKDIR) ZIPINSTALL=1
+	$(MKDIR) $(DIST_DESTDIR)
+	$(DEL) $(ZIPDESTFILE)
+ifdef USEZIPWRAPPER
+ifneq ($(ECHOREDIR),echo)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_CDPACK))" > $(ZIPWRAPPER)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_ZIP))" >> $(ZIPWRAPPER)
+	$(ECHOREDIR) -e "$(subst \,\\,$(ZIPCMD_CDBASE))" >> $(ZIPWRAPPER)
+else
+	echo $(ZIPCMD_CDPACK) > $(ZIPWRAPPER)
+	echo $(ZIPCMD_ZIP) >> $(ZIPWRAPPER)
+	echo $(ZIPCMD_CDBASE) >> $(ZIPWRAPPER)
+endif
+ifdef inUnix
+	/bin/sh $(ZIPWRAPPER)
+else
+ifdef RUNBATCH
+	$(RUNBATCH) $(ZIPWRAPPER)
+else
+	$(ZIPWRAPPER)
+endif
+endif
+	$(DEL) $(ZIPWRAPPER)
+else
+	$(ZIPCMD_CDPACK) ; $(ZIPCMD_ZIP) ; $(ZIPCMD_CDBASE)
+endif
+	$(DELTREE) $(PACKDIR)
+fpc_zipsourceinstall:
+	$(MAKE) fpc_zipinstall ZIPTARGET=sourceinstall ZIPSUFFIX=$(ZIPSOURCESUFFIX)
+fpc_zipexampleinstall:
+ifdef HASEXAMPLES
+	$(MAKE) fpc_zipinstall ZIPTARGET=exampleinstall ZIPSUFFIX=$(ZIPEXAMPLESUFFIX)
+endif
+fpc_zipdistinstall:
+	$(MAKE) fpc_zipinstall ZIPTARGET=distinstall
+.PHONY: fpc_clean fpc_cleanall fpc_distclean
+ifdef EXEFILES
+override CLEANEXEFILES:=$(addprefix $(TARGETDIRPREFIX),$(CLEANEXEFILES))
+override CLEANEXEDBGFILES:=$(addprefix $(TARGETDIRPREFIX),$(CLEANEXEDBGFILES))
+endif
+ifdef CLEAN_PROGRAMS
+override CLEANEXEFILES+=$(addprefix $(TARGETDIRPREFIX),$(addsuffix $(EXEEXT), $(CLEAN_PROGRAMS)))
+override CLEANEXEDBGFILES+=$(addprefix $(TARGETDIRPREFIX),$(addsuffix $(EXEDBGEXT), $(CLEAN_PROGRAMS)))
+endif
+ifdef CLEAN_UNITS
+override CLEANPPUFILES+=$(addsuffix $(PPUEXT),$(CLEAN_UNITS))
+endif
+ifdef CLEANPPUFILES
+override CLEANPPULINKFILES:=$(subst $(PPUEXT),$(OEXT),$(CLEANPPUFILES)) $(addprefix $(STATICLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(CLEANPPUFILES))) $(addprefix $(IMPORTLIBPREFIX),$(subst $(PPUEXT),$(STATICLIBEXT),$(CLEANPPUFILES)))
+ifdef DEBUGSYMEXT
+override CLEANPPULINKFILES+=$(subst $(PPUEXT),$(DEBUGSYMEXT),$(CLEANPPUFILES))
+endif
+override CLEANPPUFILES:=$(addprefix $(UNITTARGETDIRPREFIX),$(CLEANPPUFILES))
+override CLEANPPULINKFILES:=$(wildcard $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANPPULINKFILES)))
+endif
+fpc_clean: $(CLEANTARGET)
+ifdef CLEANEXEFILES
+	-$(DEL) $(CLEANEXEFILES)
+endif
+ifdef CLEANEXEDBGFILES
+	-$(DELTREE) $(CLEANEXEDBGFILES)
+endif
+ifdef CLEANPPUFILES
+	-$(DEL) $(CLEANPPUFILES)
+endif
+ifneq ($(CLEANPPULINKFILES),)
+	-$(DEL) $(CLEANPPULINKFILES)
+endif
+ifdef CLEANRSTFILES
+	-$(DEL) $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANRSTFILES))
+endif
+ifdef CLEAN_FILES
+	-$(DEL) $(CLEAN_FILES)
+endif
+ifdef LIB_NAME
+	-$(DEL) $(LIB_NAME) $(LIB_FULLNAME)
+endif
+	-$(DEL) $(FPCMADE) Package.fpc $(PPAS) script.res link.res $(FPCEXTFILE) $(REDIRFILE)
+	-$(DEL) *$(ASMEXT) *_ppas$(BATCHEXT)
+fpc_cleanall: $(CLEANTARGET)
+ifdef CLEANEXEFILES
+	-$(DEL) $(CLEANEXEFILES)
+endif
+ifdef COMPILER_UNITTARGETDIR
+ifdef CLEANPPUFILES
+	-$(DEL) $(CLEANPPUFILES)
+endif
+ifneq ($(CLEANPPULINKFILES),)
+	-$(DEL) $(CLEANPPULINKFILES)
+endif
+ifdef CLEANRSTFILES
+	-$(DEL) $(addprefix $(UNITTARGETDIRPREFIX),$(CLEANRSTFILES))
+endif
+endif
+ifdef CLEAN_FILES
+	-$(DEL) $(CLEAN_FILES)
+endif
+	-$(DELTREE) units
+	-$(DEL) *$(OEXT) *$(PPUEXT) *$(RSTEXT) *$(ASMEXT) *$(STATICLIBEXT) *$(SHAREDLIBEXT) *$(PPLEXT)
+ifneq ($(PPUEXT),.ppu)
+	-$(DEL) *.o *.ppu *.a
+endif
+	-$(DELTREE) *$(SMARTEXT)
+	-$(DEL) fpcmade.* Package.fpc $(PPAS) script.res link.res $(FPCEXTFILE) $(REDIRFILE)
+	-$(DEL) *_ppas$(BATCHEXT)
+ifdef AOUTEXT
+	-$(DEL) *$(AOUTEXT)
+endif
+ifdef DEBUGSYMEXT
+	-$(DEL) *$(DEBUGSYMEXT)
+endif
+ifdef LOCALFPMAKEBIN
+	-$(DEL) $(LOCALFPMAKEBIN)
+	-$(DEL) $(FPMAKEBINOBJ)
+endif
+fpc_distclean: cleanall
+.PHONY: fpc_baseinfo
+override INFORULES+=fpc_baseinfo
+fpc_baseinfo:
+	@$(ECHO)
+	@$(ECHO)  == Package info ==
+	@$(ECHO)  Package Name..... $(PACKAGE_NAME)
+	@$(ECHO)  Package Version.. $(PACKAGE_VERSION)
+	@$(ECHO)
+	@$(ECHO)  == Configuration info ==
+	@$(ECHO)
+	@$(ECHO)  FPC.......... $(FPC)
+	@$(ECHO)  FPC Version.. $(FPC_VERSION)
+	@$(ECHO)  Source CPU... $(CPU_SOURCE)
+	@$(ECHO)  Target CPU... $(CPU_TARGET)
+	@$(ECHO)  Source OS.... $(OS_SOURCE)
+	@$(ECHO)  Target OS.... $(OS_TARGET)
+	@$(ECHO)  Full Source.. $(FULL_SOURCE)
+	@$(ECHO)  Full Target.. $(FULL_TARGET)
+	@$(ECHO)  SourceSuffix. $(SOURCESUFFIX)
+	@$(ECHO)  TargetSuffix. $(TARGETSUFFIX)
+	@$(ECHO)  FPC fpmake... $(FPCFPMAKE)
+	@$(ECHO)
+	@$(ECHO)  == Directory info ==
+	@$(ECHO)
+	@$(ECHO)  Required pkgs... $(REQUIRE_PACKAGES)
+	@$(ECHO)
+	@$(ECHO)  Basedir......... $(BASEDIR)
+	@$(ECHO)  FPCDir.......... $(FPCDIR)
+	@$(ECHO)  CrossBinDir..... $(CROSSBINDIR)
+	@$(ECHO)  UnitsDir........ $(UNITSDIR)
+	@$(ECHO)  PackagesDir..... $(PACKAGESDIR)
+	@$(ECHO)
+	@$(ECHO)  GCC library..... $(GCCLIBDIR)
+	@$(ECHO)  Other library... $(OTHERLIBDIR)
+	@$(ECHO)
+	@$(ECHO)  == Tools info ==
+	@$(ECHO)
+	@$(ECHO)  As........ $(AS)
+	@$(ECHO)  Ld........ $(LD)
+	@$(ECHO)  Ar........ $(AR)
+	@$(ECHO)  Rc........ $(RC)
+	@$(ECHO)
+	@$(ECHO)  Mv........ $(MVPROG)
+	@$(ECHO)  Cp........ $(CPPROG)
+	@$(ECHO)  Rm........ $(RMPROG)
+	@$(ECHO)  GInstall.. $(GINSTALL)
+	@$(ECHO)  Echo...... $(ECHO)
+	@$(ECHO)  Shell..... $(SHELL)
+	@$(ECHO)  Date...... $(DATE)
+	@$(ECHO)  FPCMake... $(FPCMAKE)
+	@$(ECHO)  PPUMove... $(PPUMOVE)
+	@$(ECHO)  Zip....... $(ZIPPROG)
+	@$(ECHO)
+	@$(ECHO)  == Object info ==
+	@$(ECHO)
+	@$(ECHO)  Target Loaders........ $(TARGET_LOADERS)
+	@$(ECHO)  Target Units.......... $(TARGET_UNITS)
+	@$(ECHO)  Target Implicit Units. $(TARGET_IMPLICITUNITS)
+	@$(ECHO)  Target Programs....... $(TARGET_PROGRAMS)
+	@$(ECHO)  Target Dirs........... $(TARGET_DIRS)
+	@$(ECHO)  Target Examples....... $(TARGET_EXAMPLES)
+	@$(ECHO)  Target ExampleDirs.... $(TARGET_EXAMPLEDIRS)
+	@$(ECHO)
+	@$(ECHO)  Clean Units......... $(CLEAN_UNITS)
+	@$(ECHO)  Clean Files......... $(CLEAN_FILES)
+	@$(ECHO)
+	@$(ECHO)  Install Units....... $(INSTALL_UNITS)
+	@$(ECHO)  Install Files....... $(INSTALL_FILES)
+	@$(ECHO)
+	@$(ECHO)  == Install info ==
+	@$(ECHO)
+	@$(ECHO)  DateStr.............. $(DATESTR)
+	@$(ECHO)  ZipName.............. $(ZIPNAME)
+	@$(ECHO)  ZipPrefix............ $(ZIPPREFIX)
+	@$(ECHO)  ZipCrossPrefix....... $(ZIPCROSSPREFIX)
+	@$(ECHO)  ZipSuffix............ $(ZIPSUFFIX)
+	@$(ECHO)  FullZipName.......... $(FULLZIPNAME)
+	@$(ECHO)  Install FPC Package.. $(INSTALL_FPCPACKAGE)
+	@$(ECHO)
+	@$(ECHO)  Install base dir..... $(INSTALL_BASEDIR)
+	@$(ECHO)  Install binary dir... $(INSTALL_BINDIR)
+	@$(ECHO)  Install library dir.. $(INSTALL_LIBDIR)
+	@$(ECHO)  Install units dir.... $(INSTALL_UNITDIR)
+	@$(ECHO)  Install source dir... $(INSTALL_SOURCEDIR)
+	@$(ECHO)  Install doc dir...... $(INSTALL_DOCDIR)
+	@$(ECHO)  Install example dir.. $(INSTALL_EXAMPLEDIR)
+	@$(ECHO)  Install data dir..... $(INSTALL_DATADIR)
+	@$(ECHO)
+	@$(ECHO)  Dist destination dir. $(DIST_DESTDIR)
+	@$(ECHO)  Dist zip name........ $(DIST_ZIPNAME)
+	@$(ECHO)
+.PHONY: fpc_info
+fpc_info: $(INFORULES)
+.PHONY: fpc_makefile fpc_makefiles fpc_makefile_sub1 fpc_makefile_sub2 \
+	fpc_makefile_dirs
+fpc_makefile:
+	$(FPCMAKE) -w -T$(OS_TARGET) Makefile.fpc
+fpc_makefile_sub1:
+ifdef TARGET_DIRS
+	$(FPCMAKE) -w -T$(OS_TARGET) $(addsuffix /Makefile.fpc,$(TARGET_DIRS))
+endif
+ifdef TARGET_EXAMPLEDIRS
+	$(FPCMAKE) -w -T$(OS_TARGET) $(addsuffix /Makefile.fpc,$(TARGET_EXAMPLEDIRS))
+endif
+fpc_makefile_sub2: $(addsuffix _makefile_dirs,$(TARGET_DIRS) $(TARGET_EXAMPLEDIRS))
+fpc_makefile_dirs: fpc_makefile_sub1 fpc_makefile_sub2
+fpc_makefiles: fpc_makefile fpc_makefile_dirs
+units:
+examples:
+shared:
+sourceinstall: fpc_sourceinstall
+exampleinstall: fpc_exampleinstall
+zipexampleinstall: fpc_zipexampleinstall
+info: fpc_info
+makefiles: fpc_makefiles
+.PHONY: units examples shared sourceinstall exampleinstall zipexampleinstall info makefiles
+ifneq ($(wildcard fpcmake.loc),)
+include fpcmake.loc
+endif
+override FPCOPT:=$(filter-out -FU%,$(FPCOPT))
+override FPCOPT:=$(filter-out -FE%,$(FPCOPT))
+override FPCOPT:=$(filter-out $(addprefix -Fu,$(COMPILER_UNITDIR)),$(FPCOPT))# Compose general fpmake-parameters
+ifdef FPMAKEOPT
+FPMAKE_OPT+=$(FPMAKEOPT)
+endif
+FPMAKE_OPT+=--localunitdir=../..
+FPMAKE_OPT+=--globalunitdir=..
+FPMAKE_OPT+=$(FPC_TARGETOPT)
+FPMAKE_OPT+=$(addprefix -o ,$(FPCOPT))
+FPMAKE_OPT+=--compiler=$(FPC)
+FPMAKE_OPT+=-bu
+.NOTPARALLEL:
+fpmake$(SRCEXEEXT): fpmake.pp
+	$(FPCFPMAKE) fpmake.pp $(FPMAKE_SKIP_CONFIG) $(addprefix -Fu,$(COMPILER_FPMAKE_UNITDIR)) $(FPCMAKEOPT) $(OPT)
+all:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT)
+smart:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -XX -o -CX
+release:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -dRELEASE
+debug:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -dDEBUG
+ifeq ($(FPMAKE_BIN_CLEAN),)
+clean:
+else
+clean:
+	$(FPMAKE_BIN_CLEAN) clean $(FPMAKE_OPT)
+endif
+ifeq ($(FPMAKE_BIN_CLEAN),)
+distclean:	$(addsuffix _distclean,$(TARGET_DIRS)) fpc_cleanall
+else
+distclean:
+ifdef inUnix
+	{ $(FPMAKE_BIN_CLEAN) distclean $(FPMAKE_OPT); if [ $$? != "0" ]; then { echo Something wrong with fpmake exectable. Remove the executable and call make recursively to recover.; $(DEL) $(FPMAKE_BIN_CLEAN); $(MAKE) fpc_cleanall; }; fi;  }
+else
+	$(FPMAKE_BIN_CLEAN) distclean $(FPMAKE_OPT)
+endif
+	-$(DEL) $(LOCALFPMAKE)
+endif
+cleanall: distclean
+install:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR)
+else
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_BASEDIR) --baseinstalldir=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR)
+endif
+distinstall:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR) -ie -fsp 0
+else
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_BASEDIR) --baseinstalldir=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR) -ie -fsp 0
+endif
+zipinstall:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) zipinstall $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX)
+zipdistinstall:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) zipinstall $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) -ie -fsp 0
+zipsourceinstall:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) archive $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) --prefix=share/src/fpc-\$$\(PACKAGEVERSION\)/$(INSTALL_FPCSUBDIR)/\$$\(PACKAGEDIRECTORY\)
+else
+	$(LOCALFPMAKE) archive $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) --prefix=source\\$(INSTALL_FPCSUBDIR)\\\$$\(PACKAGEDIRECTORY\)
+endif

+ 102 - 0
packages/fcl-report/Makefile.fpc

@@ -0,0 +1,102 @@
+#
+#   Makefile.fpc for running fpmake
+#
+
+[package]
+name=fcl-report
+version=3.1.1
+
+[require]
+packages=rtl fcl-base fcl-xml fcl-image
+
+[install]
+fpcpackage=y
+
+[default]
+fpcdir=../..
+
+[prerules]
+FPMAKE_BIN_CLEAN=$(wildcard ./fpmake$(SRCEXEEXT))
+ifdef OS_TARGET
+FPC_TARGETOPT+=--os=$(OS_TARGET)
+endif
+ifdef CPU_TARGET
+FPC_TARGETOPT+=--cpu=$(CPU_TARGET)
+endif
+LOCALFPMAKE=./fpmake$(SRCEXEEXT)
+
+[rules]
+# Do not pass the Makefile's unit and binary target locations. Fpmake uses it's own.
+override FPCOPT:=$(filter-out -FU%,$(FPCOPT))
+override FPCOPT:=$(filter-out -FE%,$(FPCOPT))
+# Do not pass the package-unitdirectories. Fpmake adds those and this way they don't apear in the .fpm
+override FPCOPT:=$(filter-out $(addprefix -Fu,$(COMPILER_UNITDIR)),$(FPCOPT))# Compose general fpmake-parameters
+# Compose general fpmake-parameters
+ifdef FPMAKEOPT
+FPMAKE_OPT+=$(FPMAKEOPT)
+endif
+FPMAKE_OPT+=--localunitdir=../..
+FPMAKE_OPT+=--globalunitdir=..
+FPMAKE_OPT+=$(FPC_TARGETOPT)
+FPMAKE_OPT+=$(addprefix -o ,$(FPCOPT))
+FPMAKE_OPT+=--compiler=$(FPC)
+FPMAKE_OPT+=-bu
+.NOTPARALLEL:
+
+fpmake$(SRCEXEEXT): fpmake.pp
+	$(FPCFPMAKE) fpmake.pp $(FPMAKE_SKIP_CONFIG) $(addprefix -Fu,$(COMPILER_FPMAKE_UNITDIR)) $(FPCMAKEOPT) $(OPT)
+all:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT)
+smart:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -XX -o -CX
+release:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -dRELEASE
+debug:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) compile $(FPMAKE_OPT) -o -dDEBUG
+# If no fpmake exists and (dist)clean is called, do not try to build fpmake, it will
+# most often fail because the dependencies are cleared.
+# In case of a clean, simply do nothing
+ifeq ($(FPMAKE_BIN_CLEAN),)
+clean:
+else
+clean:
+	$(FPMAKE_BIN_CLEAN) clean $(FPMAKE_OPT)
+endif
+# In case of a distclean, perform an 'old'-style distclean. This to avoid problems
+# when the package is compiled using fpcmake prior to running this clean using fpmake
+ifeq ($(FPMAKE_BIN_CLEAN),)
+distclean:	$(addsuffix _distclean,$(TARGET_DIRS)) fpc_cleanall
+else
+distclean:
+ifdef inUnix
+        { $(FPMAKE_BIN_CLEAN) distclean $(FPMAKE_OPT); if [ $$? != "0" ]; then { echo Something wrong with fpmake exectable. Remove the executable and call make recursively to recover.; $(DEL) $(FPMAKE_BIN_CLEAN); $(MAKE) fpc_cleanall; }; fi;  }
+else
+        $(FPMAKE_BIN_CLEAN) distclean $(FPMAKE_OPT)
+endif
+	-$(DEL) $(LOCALFPMAKE)
+endif
+cleanall: distclean
+install:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR)
+else
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_BASEDIR) --baseinstalldir=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR)
+endif
+# distinstall also installs the example-sources and omits the location of the source-
+# files from the fpunits.cfg files.
+distinstall:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_PREFIX) --baseinstalldir=$(INSTALL_LIBDIR)/fpc/$(FPC_VERSION) --unitinstalldir=$(INSTALL_UNITDIR) -ie -fsp 0
+else
+	$(LOCALFPMAKE) install $(FPMAKE_OPT) --prefix=$(INSTALL_BASEDIR) --baseinstalldir=$(INSTALL_BASEDIR) --unitinstalldir=$(INSTALL_UNITDIR) -ie -fsp 0
+endif
+zipinstall:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) zipinstall $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX)
+zipdistinstall:	fpmake$(SRCEXEEXT)
+	$(LOCALFPMAKE) zipinstall $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) -ie -fsp 0
+zipsourceinstall:	fpmake$(SRCEXEEXT)
+ifdef UNIXHier
+	$(LOCALFPMAKE) archive $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) --prefix=share/src/fpc-\$$\(PACKAGEVERSION\)/$(INSTALL_FPCSUBDIR)/\$$\(PACKAGEDIRECTORY\)
+else
+	$(LOCALFPMAKE) archive $(FPMAKE_OPT) --zipprefix=$(DIST_DESTDIR)/$(ZIPPREFIX) --prefix=source\\$(INSTALL_FPCSUBDIR)\\\$$\(PACKAGEDIRECTORY\)
+endif

BIN
packages/fcl-report/demos/company-logo.png


+ 20 - 0
packages/fcl-report/demos/compiling.txt

@@ -0,0 +1,20 @@
+Before compiling:
+
+Check the various defines in udapp.pp and demos.inc:
+
+EXPORTPDF - enable export to PDF
+EXPORTFPIMAGE - enable export to image
+EXPORTHTML - enable export to html
+
+To enable the following defines, check the lcldemo and fpguidemo projects in Lazarus/FPGUI:
+EXPORTLCL - enable export to LCL (preview)
+EXPORTAGGPAS - enable export to aggpas
+EXPORTFPGUI - enable export to fpgui (preview)
+
+Bare-bones use (only fcl needed):
+
+- compile fcldemo.pp
+- compile webdemo.pp
+
+run without cmd-line params to see usage.
+

+ 159 - 0
packages/fcl-report/demos/countries.inc

@@ -0,0 +1,159 @@
+  { country and population as of 2014 }
+  sl.Add('China=1364270000');
+  sl.Add('India=1295292000');
+  sl.Add('United States=318857000');
+  sl.Add('Indonesia=254455000');
+  sl.Add('Brazil=206078000');
+  sl.Add('Pakistan=185044000');
+  sl.Add('Nigeria=177476000');
+  sl.Add('Bangladesh=159078000');
+  sl.Add('Russian Federation=143820000');
+  sl.Add('Japan=127132000');
+  sl.Add('Mexico=125386000');
+  sl.Add('Philippines=99139000');
+  sl.Add('Ethiopia=96959000');
+  sl.Add('Vietnam=90730000');
+  sl.Add('Egypt Arab Rep.=89580000');
+  sl.Add('Germany=80890000');
+  sl.Add('Iran Islamic Rep.=78144000');
+  sl.Add('Turkey=75932000');
+  sl.Add('Congo Dem. Rep.=74877000');
+  sl.Add('Thailand=67726000');
+  sl.Add('France=66207000');
+  sl.Add('United Kingdom=64510000');
+  sl.Add('Italy=61336000');
+  sl.Add('South Africa=54002000');
+  sl.Add('Myanmar=53437000');
+  sl.Add('Tanzania=51823000');
+  sl.Add('Korea Rep.=50424000');
+  sl.Add('Colombia=47791000');
+  sl.Add('Spain=46405000');
+  sl.Add('Ukraine=45363000');
+  sl.Add('Kenya=44864000');
+  sl.Add('Argentina=42980000');
+  sl.Add('Sudan=39350000');
+  sl.Add('Algeria=38934000');
+  sl.Add('Poland=37996000');
+  sl.Add('Uganda=37783000');
+  sl.Add('Canada=35540000');
+  sl.Add('Iraq=34812000');
+  sl.Add('Morocco=33921000');
+  sl.Add('Afghanistan=31628000');
+  sl.Add('Peru=30973000');
+  sl.Add('Saudi Arabia=30887000');
+  sl.Add('Uzbekistan=30743000');
+  sl.Add('Venezuela RB=30694000');
+  sl.Add('Malaysia=29902000');
+  sl.Add('Nepal=28175000');
+  sl.Add('Mozambique=27216000');
+  sl.Add('Ghana=26787000');
+  sl.Add('Yemen Rep.=26184000');
+  sl.Add('Korea Dem. Rep.=25027000');
+  sl.Add('Angola=24228000');
+  sl.Add('Madagascar=23572000');
+  sl.Add('Australia=23491000');
+  sl.Add('Cameroon=22773000');
+  sl.Add('Syrian Arab Republic=22158000');
+  sl.Add('Sri Lanka=20639000');
+  sl.Add('Romania=19911000');
+  sl.Add('Niger=19114000');
+  sl.Add('Chile=17763000');
+  sl.Add('Burkina Faso=17589000');
+  sl.Add('Kazakhstan=17289000');
+  sl.Add('Mali=17086000');
+  sl.Add('Netherlands=16854000');
+  sl.Add('Malawi=16695000');
+  sl.Add('Guatemala=16015000');
+  sl.Add('Ecuador=15903000');
+  sl.Add('Zambia=15721000');
+  sl.Add('Cambodia=15328000');
+  sl.Add('Zimbabwe=15246000');
+  sl.Add('Senegal=14673000');
+  sl.Add('Chad=13587000');
+  sl.Add('Guinea=12276000');
+  sl.Add('South Sudan=11911000');
+  sl.Add('Cuba=11379000');
+  sl.Add('Rwanda=11342000');
+  sl.Add('Belgium=11225000');
+  sl.Add('Tunisia=10997000');
+  sl.Add('Greece=10958000');
+  sl.Add('Burundi=10817000');
+  sl.Add('Benin=10598000');
+  sl.Add('Haiti=10572000');
+  sl.Add('Bolivia=10562000');
+  sl.Add('Somalia=10518000');
+  sl.Add('Czech Republic=10511000');
+  sl.Add('Dominican Republic=10406000');
+  sl.Add('Portugal=10397000');
+  sl.Add('Hungary=9862000');
+  sl.Add('Sweden=9690000');
+  sl.Add('Azerbaijan=9538000');
+  sl.Add('Belarus=9470000');
+  sl.Add('United Arab Emirates=9086000');
+  sl.Add('Austria=8534000');
+  sl.Add('Tajikistan=8296000');
+  sl.Add('Israel=8215000');
+  sl.Add('Switzerland=8190000');
+  sl.Add('Honduras=7962000');
+  sl.Add('Papua New Guinea=7464000');
+  sl.Add('Hong Kong SAR China=7242000');
+  sl.Add('Bulgaria=7226000');
+  sl.Add('Serbia=7129000');
+  sl.Add('Togo=7115000');
+  sl.Add('Lao PDR=6689000');
+  sl.Add('Jordan=6607000');
+  sl.Add('Paraguay=6553000');
+  sl.Add('Sierra Leone=6316000');
+  sl.Add('Libya=6259000');
+  sl.Add('El Salvador=6108000');
+  sl.Add('Nicaragua=6014000');
+  sl.Add('Kyrgyz Republic=5834000');
+  sl.Add('Denmark=5640000');
+  sl.Add('Singapore=5470000');
+  sl.Add('Finland=5464000');
+  sl.Add('Slovak Republic=5419000');
+  sl.Add('Turkmenistan=5307000');
+  sl.Add('Norway=5136000');
+  sl.Add('Eritrea=5110000');
+  sl.Add('Central African Republic=4804000');
+  sl.Add('Costa Rica=4758000');
+  sl.Add('Ireland=4613000');
+  sl.Add('Lebanon=4547000');
+  sl.Add('New Zealand=4510000');
+  sl.Add('Congo Rep.=4505000');
+  sl.Add('Georgia=4504000');
+  sl.Add('Liberia=4397000');
+  sl.Add('West Bank and Gaza=4295000');
+  sl.Add('Croatia=4236000');
+  sl.Add('Oman=4236000');
+  sl.Add('Mauritania=3970000');
+  sl.Add('Panama=3868000');
+  sl.Add('Bosnia and Herzegovina=3818000');
+  sl.Add('Kuwait=3753000');
+  sl.Add('Moldova=3556000');
+  sl.Add('Puerto Rico=3548000');
+  sl.Add('Uruguay=3420000');
+  sl.Add('Armenia=3006000');
+  sl.Add('Lithuania=2929000');
+  sl.Add('Mongolia=2910000');
+  sl.Add('Albania=2894000');
+  sl.Add('Jamaica=2721000');
+  sl.Add('Namibia=2403000');
+  sl.Add('Botswana=2220000');
+  sl.Add('Qatar=2172000');
+  sl.Add('Lesotho=2109000');
+  sl.Add('Macedonia FYR=2076000');
+  sl.Add('Slovenia=2062000');
+  sl.Add('Latvia=1990000');
+  sl.Add('Gambia The=1928000');
+  sl.Add('Kosovo=1823000');
+  sl.Add('Guinea-Bissau=1801000');
+  sl.Add('Gabon=1688000');
+  sl.Add('Bahrain=1362000');
+  sl.Add('Trinidad and Tobago=1354000');
+  sl.Add('Estonia=1314000');
+  sl.Add('Swaziland=1269000');
+  sl.Add('Mauritius=1261000');
+  sl.Add('Timor-Leste=1212000');
+  sl.Add('Cyprus=1154000');
+

+ 632 - 0
packages/fcl-report/demos/countries.json

@@ -0,0 +1,632 @@
+{
+  "Data" : [
+    {
+      "Name" : "Afghanistan",
+      "Population" : 31628000
+    },
+    {
+      "Name" : "Albania",
+      "Population" : 2894000
+    },
+    {
+      "Name" : "Algeria",
+      "Population" : 38934000
+    },
+    {
+      "Name" : "Angola",
+      "Population" : 24228000
+    },
+    {
+      "Name" : "Argentina",
+      "Population" : 42980000
+    },
+    {
+      "Name" : "Armenia",
+      "Population" : 3006000
+    },
+    {
+      "Name" : "Australia",
+      "Population" : 23491000
+    },
+    {
+      "Name" : "Austria",
+      "Population" : 8534000
+    },
+    {
+      "Name" : "Azerbaijan",
+      "Population" : 9538000
+    },
+    {
+      "Name" : "Bahrain",
+      "Population" : 1362000
+    },
+    {
+      "Name" : "Bangladesh",
+      "Population" : 159078000
+    },
+    {
+      "Name" : "Belarus",
+      "Population" : 9470000
+    },
+    {
+      "Name" : "Belgium",
+      "Population" : 11225000
+    },
+    {
+      "Name" : "Benin",
+      "Population" : 10598000
+    },
+    {
+      "Name" : "Bolivia",
+      "Population" : 10562000
+    },
+    {
+      "Name" : "Bosnia and Herzegovina",
+      "Population" : 3818000
+    },
+    {
+      "Name" : "Botswana",
+      "Population" : 2220000
+    },
+    {
+      "Name" : "Brazil",
+      "Population" : 206078000
+    },
+    {
+      "Name" : "Bulgaria",
+      "Population" : 7226000
+    },
+    {
+      "Name" : "Burkina Faso",
+      "Population" : 17589000
+    },
+    {
+      "Name" : "Burundi",
+      "Population" : 10817000
+    },
+    {
+      "Name" : "Cambodia",
+      "Population" : 15328000
+    },
+    {
+      "Name" : "Cameroon",
+      "Population" : 22773000
+    },
+    {
+      "Name" : "Canada",
+      "Population" : 35540000
+    },
+    {
+      "Name" : "Central African Republic",
+      "Population" : 4804000
+    },
+    {
+      "Name" : "Chad",
+      "Population" : 13587000
+    },
+    {
+      "Name" : "Chile",
+      "Population" : 17763000
+    },
+    {
+      "Name" : "China",
+      "Population" : 1364270000
+    },
+    {
+      "Name" : "Colombia",
+      "Population" : 47791000
+    },
+    {
+      "Name" : "Congo Dem. Rep.",
+      "Population" : 74877000
+    },
+    {
+      "Name" : "Congo Rep.",
+      "Population" : 4505000
+    },
+    {
+      "Name" : "Costa Rica",
+      "Population" : 4758000
+    },
+    {
+      "Name" : "Croatia",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Cuba",
+      "Population" : 11379000
+    },
+    {
+      "Name" : "Cyprus",
+      "Population" : 1154000
+    },
+    {
+      "Name" : "Czech Republic",
+      "Population" : 10511000
+    },
+    {
+      "Name" : "Denmark",
+      "Population" : 5640000
+    },
+    {
+      "Name" : "Dominican Republic",
+      "Population" : 10406000
+    },
+    {
+      "Name" : "Ecuador",
+      "Population" : 15903000
+    },
+    {
+      "Name" : "Egypt Arab Rep.",
+      "Population" : 89580000
+    },
+    {
+      "Name" : "El Salvador",
+      "Population" : 6108000
+    },
+    {
+      "Name" : "Eritrea",
+      "Population" : 5110000
+    },
+    {
+      "Name" : "Estonia",
+      "Population" : 1314000
+    },
+    {
+      "Name" : "Ethiopia",
+      "Population" : 96959000
+    },
+    {
+      "Name" : "Finland",
+      "Population" : 5464000
+    },
+    {
+      "Name" : "France",
+      "Population" : 66207000
+    },
+    {
+      "Name" : "Gabon",
+      "Population" : 1688000
+    },
+    {
+      "Name" : "Gambia The",
+      "Population" : 1928000
+    },
+    {
+      "Name" : "Georgia",
+      "Population" : 4504000
+    },
+    {
+      "Name" : "Germany",
+      "Population" : 80890000
+    },
+    {
+      "Name" : "Ghana",
+      "Population" : 26787000
+    },
+    {
+      "Name" : "Greece",
+      "Population" : 10958000
+    },
+    {
+      "Name" : "Guatemala",
+      "Population" : 16015000
+    },
+    {
+      "Name" : "Guinea-Bissau",
+      "Population" : 1801000
+    },
+    {
+      "Name" : "Guinea",
+      "Population" : 12276000
+    },
+    {
+      "Name" : "Haiti",
+      "Population" : 10572000
+    },
+    {
+      "Name" : "Honduras",
+      "Population" : 7962000
+    },
+    {
+      "Name" : "Hong Kong SAR China",
+      "Population" : 7242000
+    },
+    {
+      "Name" : "Hungary",
+      "Population" : 9862000
+    },
+    {
+      "Name" : "India",
+      "Population" : 1295292000
+    },
+    {
+      "Name" : "Indonesia",
+      "Population" : 254455000
+    },
+    {
+      "Name" : "Iran Islamic Rep.",
+      "Population" : 78144000
+    },
+    {
+      "Name" : "Iraq",
+      "Population" : 34812000
+    },
+    {
+      "Name" : "Ireland",
+      "Population" : 4613000
+    },
+    {
+      "Name" : "Israel",
+      "Population" : 8215000
+    },
+    {
+      "Name" : "Italy",
+      "Population" : 61336000
+    },
+    {
+      "Name" : "Jamaica",
+      "Population" : 2721000
+    },
+    {
+      "Name" : "Japan",
+      "Population" : 127132000
+    },
+    {
+      "Name" : "Jordan",
+      "Population" : 6607000
+    },
+    {
+      "Name" : "Kazakhstan",
+      "Population" : 17289000
+    },
+    {
+      "Name" : "Kenya",
+      "Population" : 44864000
+    },
+    {
+      "Name" : "Korea Dem. Rep.",
+      "Population" : 25027000
+    },
+    {
+      "Name" : "Korea Rep.",
+      "Population" : 50424000
+    },
+    {
+      "Name" : "Kosovo",
+      "Population" : 1823000
+    },
+    {
+      "Name" : "Kuwait",
+      "Population" : 3753000
+    },
+    {
+      "Name" : "Kyrgyz Republic",
+      "Population" : 5834000
+    },
+    {
+      "Name" : "Lao PDR",
+      "Population" : 6689000
+    },
+    {
+      "Name" : "Latvia",
+      "Population" : 1990000
+    },
+    {
+      "Name" : "Lebanon",
+      "Population" : 4547000
+    },
+    {
+      "Name" : "Lesotho",
+      "Population" : 2109000
+    },
+    {
+      "Name" : "Liberia",
+      "Population" : 4397000
+    },
+    {
+      "Name" : "Libya",
+      "Population" : 6259000
+    },
+    {
+      "Name" : "Lithuania",
+      "Population" : 2929000
+    },
+    {
+      "Name" : "Macedonia FYR",
+      "Population" : 2076000
+    },
+    {
+      "Name" : "Madagascar",
+      "Population" : 23572000
+    },
+    {
+      "Name" : "Malawi",
+      "Population" : 16695000
+    },
+    {
+      "Name" : "Malaysia",
+      "Population" : 29902000
+    },
+    {
+      "Name" : "Mali",
+      "Population" : 17086000
+    },
+    {
+      "Name" : "Mauritania",
+      "Population" : 3970000
+    },
+    {
+      "Name" : "Mauritius",
+      "Population" : 1261000
+    },
+    {
+      "Name" : "Mexico",
+      "Population" : 125386000
+    },
+    {
+      "Name" : "Moldova",
+      "Population" : 3556000
+    },
+    {
+      "Name" : "Mongolia",
+      "Population" : 2910000
+    },
+    {
+      "Name" : "Morocco",
+      "Population" : 33921000
+    },
+    {
+      "Name" : "Mozambique",
+      "Population" : 27216000
+    },
+    {
+      "Name" : "Myanmar",
+      "Population" : 53437000
+    },
+    {
+      "Name" : "Namibia",
+      "Population" : 2403000
+    },
+    {
+      "Name" : "Nepal",
+      "Population" : 28175000
+    },
+    {
+      "Name" : "Netherlands",
+      "Population" : 16854000
+    },
+    {
+      "Name" : "New Zealand",
+      "Population" : 4510000
+    },
+    {
+      "Name" : "Nicaragua",
+      "Population" : 6014000
+    },
+    {
+      "Name" : "Niger",
+      "Population" : 19114000
+    },
+    {
+      "Name" : "Nigeria",
+      "Population" : 177476000
+    },
+    {
+      "Name" : "Norway",
+      "Population" : 5136000
+    },
+    {
+      "Name" : "Oman",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Pakistan",
+      "Population" : 185044000
+    },
+    {
+      "Name" : "Panama",
+      "Population" : 3868000
+    },
+    {
+      "Name" : "Papua New Guinea",
+      "Population" : 7464000
+    },
+    {
+      "Name" : "Paraguay",
+      "Population" : 6553000
+    },
+    {
+      "Name" : "Peru",
+      "Population" : 30973000
+    },
+    {
+      "Name" : "Philippines",
+      "Population" : 99139000
+    },
+    {
+      "Name" : "Poland",
+      "Population" : 37996000
+    },
+    {
+      "Name" : "Portugal",
+      "Population" : 10397000
+    },
+    {
+      "Name" : "Puerto Rico",
+      "Population" : 3548000
+    },
+    {
+      "Name" : "Qatar",
+      "Population" : 2172000
+    },
+    {
+      "Name" : "Romania",
+      "Population" : 19911000
+    },
+    {
+      "Name" : "Russian Federation",
+      "Population" : 143820000
+    },
+    {
+      "Name" : "Rwanda",
+      "Population" : 11342000
+    },
+    {
+      "Name" : "Saudi Arabia",
+      "Population" : 30887000
+    },
+    {
+      "Name" : "Senegal",
+      "Population" : 14673000
+    },
+    {
+      "Name" : "Serbia",
+      "Population" : 7129000
+    },
+    {
+      "Name" : "Sierra Leone",
+      "Population" : 6316000
+    },
+    {
+      "Name" : "Singapore",
+      "Population" : 5470000
+    },
+    {
+      "Name" : "Slovak Republic",
+      "Population" : 5419000
+    },
+    {
+      "Name" : "Slovenia",
+      "Population" : 2062000
+    },
+    {
+      "Name" : "Somalia",
+      "Population" : 10518000
+    },
+    {
+      "Name" : "South Africa",
+      "Population" : 54002000
+    },
+    {
+      "Name" : "South Sudan",
+      "Population" : 11911000
+    },
+    {
+      "Name" : "Spain",
+      "Population" : 46405000
+    },
+    {
+      "Name" : "Sri Lanka",
+      "Population" : 20639000
+    },
+    {
+      "Name" : "Sudan",
+      "Population" : 39350000
+    },
+    {
+      "Name" : "Swaziland",
+      "Population" : 1269000
+    },
+    {
+      "Name" : "Sweden",
+      "Population" : 9690000
+    },
+    {
+      "Name" : "Switzerland",
+      "Population" : 8190000
+    },
+    {
+      "Name" : "Syrian Arab Republic",
+      "Population" : 22158000
+    },
+    {
+      "Name" : "Tajikistan",
+      "Population" : 8296000
+    },
+    {
+      "Name" : "Tanzania",
+      "Population" : 51823000
+    },
+    {
+      "Name" : "Thailand",
+      "Population" : 67726000
+    },
+    {
+      "Name" : "Timor-Leste",
+      "Population" : 1212000
+    },
+    {
+      "Name" : "Togo",
+      "Population" : 7115000
+    },
+    {
+      "Name" : "Trinidad and Tobago",
+      "Population" : 1354000
+    },
+    {
+      "Name" : "Tunisia",
+      "Population" : 10997000
+    },
+    {
+      "Name" : "Turkey",
+      "Population" : 75932000
+    },
+    {
+      "Name" : "Turkmenistan",
+      "Population" : 5307000
+    },
+    {
+      "Name" : "Uganda",
+      "Population" : 37783000
+    },
+    {
+      "Name" : "Ukraine",
+      "Population" : 45363000
+    },
+    {
+      "Name" : "United Arab Emirates",
+      "Population" : 9086000
+    },
+    {
+      "Name" : "United Kingdom",
+      "Population" : 64510000
+    },
+    {
+      "Name" : "United States",
+      "Population" : 318857000
+    },
+    {
+      "Name" : "Uruguay",
+      "Population" : 3420000
+    },
+    {
+      "Name" : "Uzbekistan",
+      "Population" : 30743000
+    },
+    {
+      "Name" : "Venezuela RB",
+      "Population" : 30694000
+    },
+    {
+      "Name" : "Vietnam",
+      "Population" : 90730000
+    },
+    {
+      "Name" : "West Bank and Gaza",
+      "Population" : 4295000
+    },
+    {
+      "Name" : "Yemen Rep.",
+      "Population" : 26184000
+    },
+    {
+      "Name" : "Zambia",
+      "Population" : 15721000
+    },
+    {
+      "Name" : "Zimbabwe",
+      "Population" : 15246000
+    }
+  ]
+}

+ 35 - 0
packages/fcl-report/demos/demos.inc

@@ -0,0 +1,35 @@
+
+{ the default report export filters }
+{$define ExportPDF}
+{$define ExportFPImage}
+{$define ExportHTML}
+{$define USEFIREBIRD}
+
+{$DEFINE USEPOLYGON}
+
+{ Enable if you want LCL preview window support. To compile you will also
+  have to add the LCL packages to your project dependencies. }
+
+{.$define ExportLCL}
+
+
+{ Enable if you want LCL preview window support. To compile you will also
+  have to add the LCL packages to your project dependencies. }
+
+{.$define ExportFPGui}
+
+
+{ Enable if you want PNG (AggPas) exporting. Remember to set the "aggpas"
+  project macro to point to the correct AggPas source location.  }
+
+{.$define ExportAggPas}
+
+
+{ Colour code the bands for debugging purposes and to better visualise the
+  report design. }
+{$define ColorBands}
+
+{ Enable this define to get more verbose debug output to the console. }
+
+{.$define gDebug}
+

+ 130 - 0
packages/fcl-report/demos/fcldemo.lpi

@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <SaveOnlyProjectUnits Value="True"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <SaveJumpHistory Value="False"/>
+        <SaveFoldState Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="FPReport plain FCL demo"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <MacroValues Count="1">
+      <Macro1 Name="fpgui" Value="/data/devel/fpgui"/>
+    </MacroValues>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+      <SharedMatrixOptions Count="1">
+        <Item1 ID="615255761581" Modes="Default" Type="IDEMacro" MacroName="fpgui" Value="/data/devel/fpgui"/>
+      </SharedMatrixOptions>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <CommandLineParams Value="-d simplelist -f fpimage"/>
+      </local>
+    </RunParams>
+    <Units Count="15">
+      <Unit0>
+        <Filename Value="fcldemo.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="regreports.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="rptsimplelist.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="rptexpressions.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="rptgrouping.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit4>
+      <Unit5>
+        <Filename Value="rptframes.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit5>
+      <Unit6>
+        <Filename Value="rptimages.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit6>
+      <Unit7>
+        <Filename Value="rptdataset.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit7>
+      <Unit8>
+        <Filename Value="rptshapes.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit8>
+      <Unit9>
+        <Filename Value="rptttf.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit9>
+      <Unit10>
+        <Filename Value="rptcolumns.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit10>
+      <Unit11>
+        <Filename Value="rptmasterdetail.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit11>
+      <Unit12>
+        <Filename Value="rptmasterdetaildataset.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit12>
+      <Unit13>
+        <Filename Value="rptjson.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit13>
+      <Unit14>
+        <Filename Value="rptcontnr.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit14>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="fcldemo"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="polygon;../units/$(TargetCPU)-$(TargetOS)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 18 - 0
packages/fcl-report/demos/fcldemo.pp

@@ -0,0 +1,18 @@
+program fcldemo;
+
+uses
+  udapp, regreports, rptcolumns, rptdataset, rptexpressions, rptframes,
+  rptgrouping, rptimages, rptmasterdetail, rptmasterdetaildataset, rptshapes,
+  rptsimplelist, rptttf;
+
+Var
+  Application : TReportDemoApplication;
+
+begin
+  Application:=TReportDemoApplication.Create(Nil);
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+                   s
+

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


BIN
packages/fcl-report/demos/fonts/LiberationSans-BoldItalic.ttf


BIN
packages/fcl-report/demos/fonts/LiberationSans-Italic.ttf


BIN
packages/fcl-report/demos/fonts/LiberationSans-Regular.ttf


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


BIN
packages/fcl-report/demos/fonts/LiberationSerif-BoldItalic.ttf


BIN
packages/fcl-report/demos/fonts/LiberationSerif-Italic.ttf


BIN
packages/fcl-report/demos/fonts/LiberationSerif-Regular.ttf


BIN
packages/fcl-report/demos/pictures/man01.png


BIN
packages/fcl-report/demos/pictures/man02.png


BIN
packages/fcl-report/demos/pictures/man03.png


BIN
packages/fcl-report/demos/pictures/man04.png


BIN
packages/fcl-report/demos/pictures/man05.png


BIN
packages/fcl-report/demos/pictures/woman01.png


BIN
packages/fcl-report/demos/pictures/woman02.png


BIN
packages/fcl-report/demos/pictures/woman03.png


BIN
packages/fcl-report/demos/pictures/woman04.png


BIN
packages/fcl-report/demos/pictures/woman05.png


+ 94 - 0
packages/fcl-report/demos/polygon/frmmain.lfm

@@ -0,0 +1,94 @@
+object Form1: TForm1
+  Left = 269
+  Height = 240
+  Top = 140
+  Width = 320
+  Caption = 'Form1'
+  ClientHeight = 240
+  ClientWidth = 320
+  LCLVersion = '1.6.4.0'
+  object PaintBox1: TPaintBox
+    Left = 89
+    Height = 105
+    Top = 104
+    Width = 105
+    Anchors = [akTop, akLeft, akBottom]
+    OnClick = PaintBox1Click
+    OnPaint = PaintBox1Paint
+  end
+  object SECorners: TSpinEdit
+    Left = 88
+    Height = 16
+    Top = 16
+    Width = 57
+    MinValue = 3
+    OnChange = SECornersChange
+    TabOrder = 0
+    Value = 5
+  end
+  object Label1: TLabel
+    Left = 25
+    Height = 16
+    Top = 16
+    Width = 50
+    Caption = 'Corners'
+    ParentColor = False
+  end
+  object FEAngle: TFloatSpinEdit
+    Left = 88
+    Height = 16
+    Top = 48
+    Width = 82
+    Increment = 1
+    MaxValue = 100
+    MinValue = 0
+    OnChange = FEAngleChange
+    TabOrder = 1
+    Value = 45
+  end
+  object Label2: TLabel
+    Left = 28
+    Height = 16
+    Top = 44
+    Width = 40
+    Caption = 'Rotate'
+    ParentColor = False
+  end
+  object Label3: TLabel
+    Left = 32
+    Height = 16
+    Top = 80
+    Width = 36
+    Caption = 'Width'
+    ParentColor = False
+  end
+  object SEWidth: TSpinEdit
+    Left = 89
+    Height = 16
+    Top = 80
+    Width = 50
+    MaxValue = 10
+    MinValue = 1
+    OnChange = SEWidthChange
+    TabOrder = 2
+    Value = 1
+  end
+  object BColor: TColorButton
+    Left = 216
+    Height = 25
+    Top = 16
+    Width = 75
+    BorderWidth = 2
+    ButtonColorSize = 16
+    ButtonColor = clBlack
+    OnColorChanged = BColorColorChanged
+  end
+  object Label4: TLabel
+    Left = 168
+    Height = 16
+    Top = 16
+    Width = 34
+    Caption = 'Color'
+    ParentColor = False
+  end
+end

+ 111 - 0
packages/fcl-report/demos/polygon/frmmain.pas

@@ -0,0 +1,111 @@
+unit frmmain;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
+  Spin, StdCtrls;
+
+type
+
+  { TForm1 }
+
+  TForm1 = class(TForm)
+    BColor: TColorButton;
+    FEAngle: TFloatSpinEdit;
+    Label1: TLabel;
+    Label2: TLabel;
+    Label3: TLabel;
+    Label4: TLabel;
+    PaintBox1: TPaintBox;
+    SECorners: TSpinEdit;
+    SEWidth: TSpinEdit;
+    procedure BColorColorChanged(Sender: TObject);
+    procedure FEAngleChange(Sender: TObject);
+    procedure PaintBox1Click(Sender: TObject);
+    procedure PaintBox1Paint(Sender: TObject);
+    procedure SECornersChange(Sender: TObject);
+    procedure SEWidthChange(Sender: TObject);
+  private
+    { private declarations }
+  public
+    { public declarations }
+  end;
+
+var
+  Form1: TForm1;
+
+implementation
+
+{$R *.lfm}
+
+{ TForm1 }
+
+procedure TForm1.PaintBox1Click(Sender: TObject);
+begin
+
+end;
+
+procedure TForm1.FEAngleChange(Sender: TObject);
+begin
+  PaintBox1.Invalidate;
+end;
+
+procedure TForm1.BColorColorChanged(Sender: TObject);
+begin
+  Paintbox1.Invalidate;
+end;
+
+
+Procedure PaintPolygon(Canvas : TCanvas; AWidth,AHeight : Integer; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TColor);
+
+Var
+  CX,CY,R,I : Integer;
+  P : Array of TPoint;
+  A,Step : Double;
+
+begin
+  Canvas.Pen.Color:=AColor;
+  Canvas.Pen.Width:=aLineWidth;
+  if ANumber<3 then
+    exit;
+  CX:=AWidth div 2;
+  CY:=AHeight div 2;
+  if aWidth<aHeight then
+    R:=AWidth div 2
+  else
+    R:=AHeight div 2;
+  SetLength(P,ANumber-1);
+  A:=AStartAngle;
+  Step:=(2*Pi)/ANumber;
+  For I:=0 to ANumber-1 do
+    begin
+    P[i].X:=CX+Round(R*Cos(a));
+    P[i].Y:=CY-Round(R*Sin(a));
+    A:=A+Step;
+    end;
+  For I:=0 to ANumber-2 do
+    Canvas.Line(P[I],P[I+1]);
+  Canvas.Line(P[ANumber-1],P[0]);
+end;
+
+procedure TForm1.PaintBox1Paint(Sender: TObject);
+begin
+  With PaintBox1 do
+    PaintPolygon(Canvas,Width-1,Height-1,SECorners.Value,FEANgle.Value*Pi/180, SEWidth.Value, BColor.ButtonColor);
+end;
+
+procedure TForm1.SECornersChange(Sender: TObject);
+begin
+  Paintbox1.INvalidate ;
+end;
+
+procedure TForm1.SEWidthChange(Sender: TObject);
+begin
+  Paintbox1.INvalidate;
+end;
+
+end.
+

+ 428 - 0
packages/fcl-report/demos/polygon/reportpolygon.pas

@@ -0,0 +1,428 @@
+unit reportpolygon;
+
+{$mode objfpc}{$H+}
+
+{$DEFINE NATIVERENDERER}
+{ $DEFINE EXPORTFPIMAGE}
+{ $DEFINE EXPORTPDF}
+{ $DEFINE EXPORTLCL}
+{ $DEFINE EXPORTAGGPAS}
+{ $DEFINE EXPORTFPGUI}
+
+interface
+
+uses
+  Classes,  fpReport, fpReportStreamer;
+
+ Type
+
+   { TReportPolygon }
+
+   TReportPolygon = class(TFPReportElement)
+   private
+     FColor: TFPReportColor;
+     FCorners: Cardinal;
+     FLineWidth: Cardinal;
+     FRotateAngle: Double;
+     procedure SetCLineWidth(AValue: Cardinal);
+     procedure SetCorners(AValue: Cardinal);
+   Protected
+     Procedure   RecalcLayout; override;
+     procedure DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+   public
+     procedure ReadElement(AReader: TFPReportStreamer); override;
+     Procedure Assign(Source: TPersistent); override;
+   Published
+     Property Corners : Cardinal Read FCorners Write SetCorners;
+     Property LineWidth : Cardinal Read FLineWidth Write SetCLineWidth;
+     Property Color : TFPReportColor Read FColor Write FColor;
+     // In degrees
+     Property RotateAngle : Double Read FRotateAngle Write FRotateAngle;
+   end;
+
+implementation
+
+{$IF DEFINED(EXPORTFPGUI) or DEFINED(EXPORTAGGPAS)}
+{$DEFINE NEEDAGGPAS}
+{$ENDIF}
+
+uses
+{$IFDEF EXPORTPDF}
+   fppdf,
+   fpreportpdfexport,
+{$ENDIF}
+{$IFDEF EXPORTLCL}
+   graphics,
+   fpreportlclexport,
+{$ENDIF}
+{$IFDEF EXPORTFPIMAGE}
+   fpreportfpimageexport,
+{$ENDIF}
+{$IFDEF EXPORTFPGUI}
+  fpreport_export_fpgui,
+{$ENDIF}
+{$IFDEF NEEDAGGPAS}
+  fpreportaggpasexport,
+  agg_2D,
+{$ENDIF}
+   sysutils, fpimage, fpcanvas, FPImgCanv;
+
+// Angle in degrees.
+
+{$IFDEF EXPORTLCL}
+Procedure LCLPaintPolygon(Canvas : TCanvas; ARect : Trect; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TColor);
+
+Var
+  CX,CY,R,I : Integer;
+  P : Array of TPoint;
+  A,Step : Double;
+  AWidth,AHeight : Integer;
+
+begin
+  AWidth:=ARect.Right-ARect.Left+1;
+  AHeight:=ARect.Bottom-ARect.Top+1;
+  Canvas.Pen.Color:=AColor;
+  Canvas.Pen.style:=psSolid;
+  Canvas.Pen.Width:=aLineWidth;
+  if ANumber<3 then
+    exit;
+  CX:=ARect.Left+AWidth div 2;
+  CY:=ARect.Top+AHeight div 2;
+  if aWidth<aHeight then
+    R:=AWidth div 2
+  else
+    R:=AHeight div 2;
+  SetLength(P,ANumber);
+  A:=AStartAngle;
+  Step:=(2*Pi)/ANumber;
+  For I:=0 to ANumber-1 do
+    begin
+    P[i].X:=CX+Round(R*Cos(a));
+    P[i].Y:=CY-Round(R*Sin(a));
+    A:=A+Step;
+    end;
+  For I:=0 to ANumber-2 do
+    Canvas.Line(P[I],P[I+1]);
+  Canvas.Line(P[ANumber-1],P[0]);
+end;
+
+Procedure RenderPolygonInLCL(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
+var
+  PR : TFPReportExportCanvas;
+  PG : TReportPolygon;
+  R :  Trect;
+  rPt : TFPReportPoint;
+  pt : TPoint;
+
+begin
+  PR:=RE as TFPReportExportCanvas;
+  PG:=E as TReportPolygon;
+  rpt.Left:=AOffset.Left+E.RTLayout.Left;
+  rpt.Top:=AOffset.Top+E.RTLayout.top;
+  Pt:=PR.CoordToPoint(rpt,0,0);
+  R.TopLeft:=pt;
+  R.Right:=R.Left+PR.HmmToPixels(E.RTLayout.Width);
+  R.Bottom:= R.Top+PR.VmmToPixels(E.RTLayout.Height);
+  PR.Canvas.Brush.Color:=e.Frame.BackgroundColor;
+  PR.Canvas.Brush.Style:=bsSolid;
+  PR.Canvas.FillRect(R);
+  LCLPaintPolygon(PR.Canvas,R,PG.Corners,PG.RotateAngle,PG.LineWidth,PR.RGBToBGR(PG.Color));
+end;
+{$ENDIF}
+
+{$IFDEF EXPORTPDF}
+Procedure RenderPolygonInPDF(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
+
+Var
+  PR : TFPReportExportPDF;
+  PG : TReportPolygon;
+  C : TPDFCoord;
+  I,ANumber : Integer;
+  P : Array of TPDFCoord;
+  R,A,Step : Double;
+  APage: TPDFPage;
+
+begin
+  PR:=RE as TFPReportExportPDF;
+  PG:=E as TReportPolygon;
+  APage:=PR.CurrentPage;
+  ANumber:=PG.Corners;
+  if ANumber<3 then
+    exit;
+  C.X:=AOffset.Left+E.RTLayout.Left+E.RTLayout.Width / 2;
+  C.Y:=AOffset.Top+E.RTLayout.Top+E.RTLayout.Height / 2;
+  if E.RTLayout.Width<E.RTLayout.Height then
+    R:=E.RTLayout.Width / 2
+  else
+    R:=E.RTLayout.Height / 2;
+  SetLength(P,ANumber);
+  A:=PG.RotateAngle;
+  Step:=(2*Pi)/ANumber;
+  For I:=0 to ANumber-1 do
+    begin
+    P[i].X:=C.X+R*Cos(a);
+    P[i].Y:=C.Y-R*Sin(a);
+    A:=A+Step;
+    end;
+  APage.SetColor(PG.Color,True);
+//  For I:=0 to ANumber-2 do
+//    APage.DrawLine(P[I],P[I+1],PG.LineWidth,True);
+//  aPage.DrawLine(P[ANumber-1],P[0],PG.LineWidth,True);
+  APage.DrawPolyGon(P,PG.LineWidth);
+  APage.StrokePath;
+end;
+{$ENDIF}
+
+function GetColorComponent(Var AColor: UInt32): Word;
+begin
+  Result:=AColor and $FF;
+  Result:=Result or (Result shl 8);
+  AColor:=AColor shr 8;
+end;
+
+function ColorToRGBTriple(const AColor: UInt32): TFPColor;
+
+Var
+  C : UInt32;
+
+begin
+  C:=AColor;
+  with Result do
+    begin
+    Blue  := GetColorComponent(C);
+    Green := GetColorComponent(C);
+    Red   := GetColorComponent(C);
+    Alpha := GetColorComponent(C);
+    end
+end;
+
+
+Procedure PaintPolygon(Canvas : TFPCustomCanvas; AOffset : TPoint; AWidth,AHeight : Integer; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TFPColor);
+
+Var
+  CX,CY,R,I : Integer;
+  P : Array of TPoint;
+  A,Step : Double;
+
+begin
+  Canvas.Pen.FPColor:=AColor;
+  Canvas.Pen.Width:=aLineWidth;
+  Canvas.Pen.Style:=psSolid;
+  if ANumber<3 then
+    exit;
+  CX:=AOffset.x+AWidth div 2;
+  CY:=AOffset.y+AHeight div 2;
+  if aWidth<aHeight then
+    R:=AWidth div 2
+  else
+    R:=AHeight div 2;
+  SetLength(P,ANumber);
+  A:=AStartAngle;
+  Step:=(2*Pi)/ANumber;
+  For I:=0 to ANumber-1 do
+    begin
+    P[i].X:=CX+Round(R*Cos(a));
+    P[i].Y:=CY-Round(R*Sin(a));
+    A:=A+Step;
+    end;
+  For I:=0 to ANumber-2 do
+    Canvas.Line(P[I],P[I+1]);
+  Canvas.Line(P[ANumber-1],P[0]);
+  SetLength(P,0);
+end;
+
+Procedure RenderPolygonToImage(aElement : TFPReportElement;AImage : TFPCustomImage);
+
+Var
+  C : TFPImageCanvas;
+  P : TReportPolygon;
+
+begin
+  P:=AElement as TReportPolygon;
+  C:=TFPImageCanvas.Create(AImage);
+  try
+    if (AElement.Frame.BackgroundColor<>fpreport.clNone) then
+      begin
+      C.Brush.FPColor:=ColorToRGBTriple(AElement.Frame.BackgroundColor);
+      C.Brush.Style:=bsSolid;
+      C.FillRect(0,0,AImage.Width-1,AImage.Height-1);
+      end;
+    PaintPolygon(C,Point(0,0),AImage.Width,AImage.Height,P.Corners,P.RotateAngle,P.linewidth,ColorToRGBTriple(P.Color));
+  finally
+    C.Free;
+  end;
+end;
+
+{$IFDEF EXPORTFPIMAGE}
+Procedure RenderPolygonInFPImage(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
+
+Var
+  PR : TFPReportExportFPImage;
+  PG : TReportPolygon;
+  R :  TPoint;
+  AWidth,AHeight : Integer;
+
+begin
+  PR:=RE as TFPReportExportFPImage;
+  PG:=E as TReportPolygon;
+  R.X:=PR.mmToPixels(AOffset.Left+E.RTLayout.Left);
+  R.Y:=PR.mmToPixels(AOffset.Top+E.RTLayout.Top);
+  AWidth:=PR.mmToPixels(E.RTLayout.Width);
+  AHeight:=PR.mmToPixels(E.RTLayout.Height);
+  PaintPolygon(PR.Canvas,R,AWidth,AHeight, PG.Corners,PG.RotateAngle,PG.LineWidth,PR.ColorToRGBTriple(PG.Color));
+end;
+{$ENDIF}
+
+{ TReportPolygon }
+
+procedure TReportPolygon.SetCLineWidth(AValue: Cardinal);
+begin
+  If AValue<1 then
+    AValue:=1;
+  if FLineWidth=AValue then Exit;
+  FLineWidth:=AValue;
+end;
+
+procedure TReportPolygon.SetCorners(AValue: Cardinal);
+begin
+  If AValue<3 then
+    AValue:=3;
+  if FCorners=AValue then Exit;
+  FCorners:=AValue;
+end;
+
+procedure TReportPolygon.RecalcLayout;
+begin
+  // Do nothing
+end;
+
+procedure TReportPolygon.DoWriteLocalProperties(AWriter: TFPReportStreamer;
+  AOriginal: TFPReportElement);
+
+Var
+  P : TReportPolygon;
+
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  if AOriginal is TReportPolygon then
+    begin
+    P:=AOriginal as TReportPolygon;
+    AWriter.WriteIntegerDiff('Color', Color, P.Color);
+    AWriter.WriteIntegerDiff('Corners',Corners,P.Color);
+    AWriter.WriteIntegerDiff('LineWidth',LineWidth,P.LineWidth);
+    AWriter.WriteFloatDiff('RotateAngle',RotateAngle,P.RotateAngle);
+    end
+  else
+    begin
+    AWriter.WriteInteger('Color', Color);
+    AWriter.WriteInteger('Corners',Corners);
+    AWriter.WriteInteger('LineWidth',LineWidth);
+    AWriter.WriteFloat('RotateAngle',RotateAngle);
+    end;
+end;
+
+procedure TReportPolygon.ReadElement(AReader: TFPReportStreamer);
+begin
+  inherited ReadElement(AReader);
+  Color:= AReader.ReadInteger('Color', clBlack);
+  Corners:=AReader.ReadInteger('Corners',3);
+  LineWidth:=AReader.ReadInteger('LineWidth',1);
+  RotateAngle:=AReader.ReadFloat('RotateAngle',0);
+end;
+
+procedure TReportPolygon.Assign(Source: TPersistent);
+
+Var
+  P : TReportPolygon;
+
+begin
+  if (Source is TReportPolygon) then
+    begin
+    P:=Source as TReportPolygon;
+    Corners:=P.Corners;
+    Color:=P.Color;
+    LineWidth:=P.LineWidth;
+    RotateAngle:=P.RotateAngle;
+    end;
+  inherited Assign(Source);
+end;
+
+{$IFDEF EXPORTFPGUI}
+Procedure AggPasPaintPolygon(Ex: TFPReportExportAggPas; ARect : TFPReportRect; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TRGBTriple);
+
+Var
+  C : TFPReportPoint;
+  I : Integer;
+  P : Array of TFPReportPoint;
+  R,A,Step : Double;
+  AWidth,AHeight : Double;
+
+begin
+  ex.Agg.noFill;
+  ex.Agg.lineColor(aColor.Red, aColor.Green, aColor.Blue);
+  ex.Agg.lineWidth(aLineWidth);
+  if ANumber<3 then
+    exit;
+  AWidth:=ARect.Width;
+  AHeight:=ARect.Height;
+  C.Left:=ARect.Left+AWidth / 2;
+  C.Top:=ARect.Top+AHeight / 2;
+  if aWidth<aHeight then
+    R:=AWidth / 2
+  else
+    R:=AHeight / 2;
+  SetLength(P,ANumber);
+  A:=AStartAngle;
+  Step:=(2*Pi)/ANumber;
+  For I:=0 to ANumber-1 do
+    begin
+    P[i].Left:=Ex.mmToPixels(C.Left+R*Cos(a));
+    P[i].Top:=Ex.mmToPixels(C.Top-R*Sin(a));
+    A:=A+Step;
+    end;
+  For I:=0 to ANumber-2 do
+    ex.agg.Line(P[I].Left,P[i].Top,P[I+1].Left,P[I+1].Top);
+  ex.agg.Line(P[ANumber-1].Left,P[ANumber-1].Top,P[0].Left,P[0].Top);
+end;
+
+Procedure RenderPolygonInAggPas(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
+var
+  PR : TFPReportExportAggPas;
+  PG : TReportPolygon;
+  rPt : TFPReportRect;
+
+begin
+  PR:=RE as TFPReportExportAggPas;
+  PG:=E as TReportPolygon;
+  rpt.Left:=AOffset.Left+E.RTLayout.Left;
+  rpt.Top:=AOffset.Top+E.RTLayout.top;
+  Rpt.Width:=E.RTLayout.Width;
+  Rpt.Height:=E.RTLayout.Height;
+  AggPasPaintPolygon(PR,rpt,PG.Corners,PG.RotateAngle,PG.LineWidth,PR.ColorToRGBTriple(PG.Color));
+end;
+
+{$ENDIF}
+
+begin
+  gElementFactory.RegisterClass('Polygon',TReportPolygon);
+  // Fallback renderer
+  gElementFactory.RegisterImageRenderer(TReportPolygon,@RenderPolygonToImage);
+{$IFDEF NATIVERENDERER}
+
+{$IFDEF EXPORTPDF}
+  gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportPDF,@RenderPolygonInPDF);
+{$ENDIF}
+{$IFDEF EXPORTFPIMAGE}
+  gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportfpImage,@RenderPolygonInFPImage);
+{$ENDIF}
+{$IFDEF EXPORTLCL}
+  gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportCanvas,@RenderPolygonInLCL);
+{$ENDIF}
+{$IFDEF EXPORTFPGUI}
+  gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportAggPas,@RenderPolygonInAggPas);
+  gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportCanvas,@RenderPolygonInAggPas);
+{$ENDIF}
+{$ENDIF}
+
+end.
+

BIN
packages/fcl-report/demos/polygon/testpolygon.ico


+ 89 - 0
packages/fcl-report/demos/polygon/testpolygon.lpi

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="testpolygon"/>
+      <ResourceType Value="res"/>
+      <UseXPManifest Value="True"/>
+      <Icon Value="0"/>
+    </General>
+    <i18n>
+      <EnableI18N LFM="False"/>
+    </i18n>
+    <VersionInfo>
+      <StringTable ProductVersion=""/>
+    </VersionInfo>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="3">
+      <Item1>
+        <PackageName Value="lclfpreport"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="fclfpreport"/>
+      </Item2>
+      <Item3>
+        <PackageName Value="LCL"/>
+      </Item3>
+    </RequiredPackages>
+    <Units Count="3">
+      <Unit0>
+        <Filename Value="testpolygon.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="frmmain.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="Form1"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="reportpolygon.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="testpolygon"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Options>
+        <Win32>
+          <GraphicApplication Value="True"/>
+        </Win32>
+      </Options>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 21 - 0
packages/fcl-report/demos/polygon/testpolygon.lpr

@@ -0,0 +1,21 @@
+program testpolygon;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}{$IFDEF UseCThreads}
+  cthreads,
+  {$ENDIF}{$ENDIF}
+  Interfaces, // this includes the LCL widgetset
+  Forms, frmmain, reportpolygon
+  { you can add units after this };
+
+{$R *.res}
+
+begin
+  RequireDerivedFormResource:=True;
+  Application.Initialize;
+  Application.CreateForm(TForm1, Form1);
+  Application.Run;
+end.
+

BIN
packages/fcl-report/demos/polygon/testpolygon.res


+ 63 - 0
packages/fcl-report/demos/regreports.pp

@@ -0,0 +1,63 @@
+unit regreports;
+
+{$mode objfpc}{$H+}
+
+{$i demos.inc}
+
+interface
+
+uses
+  rptsimplelist,
+  rptexpressions,
+  rptgrouping,
+  rptframes,
+  rptimages,
+  rptttf,
+  rptshapes,
+  rptdataset,
+  rptcolumns,
+  rptmasterdetail,
+{$IFDEF USEFIREBIRD}
+  rptmasterdetaildataset,
+{$ENDIF}
+  rptjson,
+  rptcontnr,
+  udapp
+  ;
+
+Procedure RegisterReports;
+
+implementation
+
+procedure RegisterReports;
+
+  Procedure R(AName : String; AClass : TReportDemoAppClass);
+
+  begin
+    TReportDemoApplication.RegisterReport(aName,AClass);
+  end;
+
+
+begin
+  R('simplelist',TSimpleListDemo);
+  R('expressions',TExpressionsDemo);
+  R('grouping',TGroupingDemo);
+  R('frames',TFramesDemo);
+  R('Images',TImagesDemo);
+  R('shapes',TShapesDemo);
+  R('truetypefonts',TTTFDemo);
+  R('dataset',TDatasetDemo);
+  R('columns',TColumnsDemo);
+  R('masterdetail',TMasterDetailDemo);
+  {$IFDEF USEFIREBIRD}
+  R('masterdetaildataset',TMasterDetailDatasetDemo);
+  {$ENDIF}
+  R('jsondata',TJSONDemo);
+  R('collectiondata',TCollectionDemo);
+  R('objectlistdata',TObjectListDemo);
+end;
+
+initialization
+  RegisterReports;
+end.
+

+ 378 - 0
packages/fcl-report/demos/rptcolumns.pp

@@ -0,0 +1,378 @@
+unit rptcolumns;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+
+  TColumnsDemo = class(TReportDemoApp)
+  private
+    FDataPage1: TFPReportUserData;
+    FDataPage2: TFPReportUserData;
+    FStringListPage1: TStringList;
+    FStringListPage2: TStringList;
+    procedure   GetReportDataPage1Value(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataPage1EOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportDataPage1Names(Sender: TObject; List: TStrings);
+    procedure   GetReportDataPage2Value(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataPage2EOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportDataPage2Names(Sender: TObject; List: TStrings);
+  protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF;
+
+{ TColumnsDemo }
+
+procedure TColumnsDemo.GetReportDataPage1Value(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  if AValueName = 'p1element' then
+  begin
+    AValue := FStringListPage1[FDataPage1.RecNo-1];
+  end;
+end;
+
+procedure TColumnsDemo.GetReportDataPage1EOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  if FDataPage1.RecNo > FStringListPage1.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TColumnsDemo.GetReportDataPage2Value(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  if AValueName = 'p2element' then
+  begin
+    AValue := FStringListPage2[FDataPage2.RecNo-1];
+  end;
+end;
+
+procedure TColumnsDemo.GetReportDataPage2EOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  if FDataPage2.RecNo > FStringListPage2.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TColumnsDemo.InitialiseData;
+var
+  i: integer;
+begin
+  FStringListPage1 := TStringList.Create;
+  for i := 1 to 50 do
+    FStringListPage1.Add(Format('DataItem %d', [i]));
+
+  FStringListPage2 := TStringList.Create;
+  for i := 1 to 77 do
+    FStringListPage2.Add(Format('Item %d', [i]));
+end;
+
+procedure TColumnsDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  PageHeader: TFPReportPageHeaderBand;
+  ColumnHeader: TFPReportColumnHeaderBand;
+  ColumnFooter: TFPReportColumnFooterBand;
+  DataHeader: TFPReportDataHeaderBand;
+  DataFooter: TFPReportDataFooterBand;
+  ChildBand: TFPReportChildBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 9 - Multi Columns';
+
+  { Page 1 }
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 10;
+  p.Margins.Top := 10;
+  p.Margins.Right := 10;
+  p.Margins.Bottom := 10;
+  p.Data := FDataPage1;
+  p.Font.Name := 'LiberationSans';
+
+  p.ColumnCount := 2;
+  p.ColumnGap := 10;
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 277;
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := TFPReportColor($003366);
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 3;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := TitleBand.Layout.Width-6;
+  Memo.Layout.Height := TitleBand.Layout.Height-6;
+  Memo.Text := 'THE REPORT TITLE' + LineEnding + '(start of designed report page 1)';
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'LiberationSans-Bold';
+  Memo.Font.Size := 20;
+  Memo.Font.Color := TFPReportColor($5B7290);
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := TFPReportColor($663366);
+  Memo.Frame.BackgroundColor := TFPReportColor($E7EBF0);
+  Memo.Frame.Width := 5;
+
+  PageHeader := TFPReportPageHeaderBand.Create(p);
+  PageHeader.Layout.Height := 30;
+  PageHeader.VisibleOnPage := vpNotOnFirst;
+  PageHeader.Frame.Shape := fsRectangle;
+  PageHeader.Frame.BackgroundColor := TFPReportColor($003366);
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 15;
+  Memo.Layout.Width := 70;
+  Memo.Layout.Height := 10;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := clWhite;
+  Memo.Text := 'PageHeader - designed report page 1';
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 6;
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.Color := TFPReportColor($B1BDCD);
+  DataBand.Frame.BackgroundColor := TFPReportColor($E7EBF0);
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 55;
+  Memo.Layout.Height := 6;
+  Memo.Text := 'DataBand <[p1element]>.';
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  PageFooter.VisibleOnPage := vpNotOnFirst;
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := TFPReportColor($5B7290);
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 135;
+  Memo.Layout.Top := 9;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 20;
+  Memo.Text := 'PageFooter Band' + LineEnding + '<i>Page [PageNo]</i>';
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Options := [moAllowHTML];
+
+  { Page 2 }
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := FDataPage2;
+  p.Font.Name := 'LiberationSans';
+
+  p.ColumnCount := 3;
+  p.ColumnGap := 5;
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clLtGray;
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 65;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'THE REPORT TITLE' + LineEnding + '(Start of Designed Report Page 2)';
+
+  PageHeader := TFPReportPageHeaderBand.Create(p);
+  PageHeader.Layout.Height := 20;
+  PageHeader.Frame.Shape := fsRectangle;
+  PageHeader.Frame.BackgroundColor := clTeal;
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 10;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'Page Header Band  - designed report page 2';
+
+  ColumnHeader := TFPReportColumnHeaderBand.Create(p);
+  ColumnHeader.Layout.Height := 15;
+  ColumnHeader.Frame.Shape := fsRectangle;
+  ColumnHeader.Frame.BackgroundColor := clDkRed;
+
+  Memo := TFPReportMemo.Create(ColumnHeader);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 2.5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'LiberationSans-Bold';
+  Memo.Font.Color := clWhite;
+  Memo.Text := 'ColumnHeader Band';
+  Memo.TextAlignment.Horizontal := taLeftJustified;
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  DataHeader := TFPReportDataHeaderBand.Create(p);
+  DataHeader.Layout.Height := 10;
+  DataHeader.Frame.Shape := fsRectangle;
+  DataHeader.Frame.BackgroundColor := TFPReportColor($ffa500);
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 1.5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 8;
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'LiberationSans-Bold';
+  Memo.Font.Color := clWhite;
+  Memo.Text := 'DataHeader Band';
+  Memo.TextAlignment.Horizontal := taLeftJustified;
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 10;
+  DataBand.Data := FDataPage2;
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  { associated DataHeader band }
+  DataBand.HeaderBand := DataHeader;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 40;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'DataBand <[p2element]>.';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 9;
+  ChildBand.Frame.Shape := fsRectangle;
+  ChildBand.Frame.BackgroundColor := clLtGray;
+
+  Memo := TFPReportMemo.Create(ChildBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 40;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'ChildBand - [p2element]';
+
+  DataBand.ChildBand := ChildBand;
+
+  DataFooter := TFPReportDataFooterBand.Create(p);
+  DataFooter.Layout.Height := 10;
+  DataFooter.Frame.Shape := fsRectangle;
+  DataFooter.Frame.BackgroundColor := TFPReportColor($ffa500);
+  DataFooter.UseParentFont := False;
+  DataFooter.Font.Name := 'LiberationSans-Bold';
+  DataFooter.Font.Color := clWhite;
+
+  Memo := TFPReportMemo.Create(DataFooter);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 1.5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 8;
+  Memo.Text := 'DataFooter Band';
+  Memo.TextAlignment.Horizontal := taLeftJustified;
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  ColumnFooter := TFPReportColumnFooterBand.Create(p);
+  ColumnFooter.Layout.Height := 15;
+  ColumnFooter.Frame.Shape := fsRectangle;
+  ColumnFooter.Frame.BackgroundColor := clGreen;
+//  ColumnFooter.FooterPosition := fpAfterLast;
+
+  Memo := TFPReportMemo.Create(ColumnFooter);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 2.5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'ColumnFooter Band';
+  Memo.TextAlignment.Horizontal := taLeftJustified;
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clLtGray;
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 135;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 10;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'PageFooter Band';
+end;
+
+constructor TColumnsDemo.Create(AOwner: TComponent);
+begin
+  Inherited;
+  FDataPage1 := TFPReportUserData.Create(nil);
+  FDataPage1.OnGetValue := @GetReportDataPage1Value;
+  FDataPage1.OnGetEOF := @GetReportDataPage1EOF;
+  FDataPage1.OnGetNames := @GetReportDataPage1Names;
+  FDataPage2 := TFPReportUserData.Create(nil);
+  FDataPage2.OnGetValue := @GetReportDataPage2Value;
+  FDataPage2.OnGetEOF := @GetReportDataPage2EOF;
+  FDataPage2.OnGetNames := @GetReportDataPage2Names;
+end;
+
+destructor TColumnsDemo.Destroy;
+begin
+  FreeAndNil(FDataPage1);
+  FreeAndNil(FDataPage2);
+  FreeAndNil(FStringListPage1);
+  FreeAndNil(FStringListPage2);
+  inherited Destroy;
+end;
+
+procedure TColumnsDemo.GetReportDataPage2Names(Sender: TObject; List: TStrings);
+begin
+  List.Add('p2element');
+end;
+
+procedure TColumnsDemo.GetReportDataPage1Names(Sender: TObject; List: TStrings);
+begin
+  List.Add('p1element');
+end;
+
+
+end.
+

+ 278 - 0
packages/fcl-report/demos/rptcontnr.pp

@@ -0,0 +1,278 @@
+unit rptcontnr;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  fpreportcontnr,
+  contnrs,
+  udapp;
+
+type
+
+  { TCountry }
+
+  TCountry = Class(TCollectionItem)
+  private
+    FName: String;
+    FPopulation: Int64;
+  Published
+    Property Name : String Read FName Write FName;
+    Property Population : Int64 Read FPopulation Write FPopulation;
+  end;
+
+  { TCollectionDemo }
+
+  TContnrDemo = class(TReportDemoApp)
+  Protected
+    FReportData : TFPReportObjectData;
+  public
+    procedure   CreateReportDesign;override;
+    procedure   LoadDesignFromFile(const AFilename: string);
+    procedure   HookupData(const AComponentName: string; const AData: TFPReportData);
+    destructor  Destroy; override;
+  end;
+
+  TCollectionDemo = class(TContnrDemo)
+  Protected
+    procedure   InitialiseData; override;
+  Public
+    constructor Create(AOWner :TComponent); override;
+  end;
+
+  { TObjectListDemo }
+
+  TObjectListDemo = class(TContnrDemo)
+  Protected
+    procedure   InitialiseData; override;
+  Public
+    constructor Create(AOWner :TComponent); override;
+  end;
+
+
+implementation
+
+uses
+  fpReportStreamer,
+  fpTTF,
+  fpJSON,
+  jsonparser;
+
+{ TObjectListDemo }
+
+procedure TObjectListDemo.InitialiseData;
+Var
+  SL : TStringList;
+  i : Integer;
+  N,V : String;
+  C : TCountry;
+  List : TFPObjectList;
+
+begin
+  List:=TFPObjectList.Create(True);
+  TFPReportObjectListData(FReportData).List:=List;
+  SL:=TStringList.Create;
+  try
+    {$I countries.inc}
+    SL.Sort;
+    For I:=0 to SL.Count-1 do
+      begin
+      C:=TCountry.Create(Nil);
+      List.Add(C);
+      SL.GetNameValue(I,N,V);
+      C.Name:=N;
+      C.Population:=StrToInt64Def(V,0);
+      end;
+  finally
+    SL.Free;
+  end;
+end;
+
+constructor TObjectListDemo.Create(AOWner: TComponent);
+begin
+  inherited Create(AOWner);
+  FReportData := TFPReportObjectListData.Create(nil);
+  TFPReportObjectListData(FReportData).OwnsList:=True;
+end;
+
+
+procedure TContnrDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 12 - JSON Data';
+
+  p :=  TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := FReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 35;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 80;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'COUNTRY AND POPULATION AS OF 2014';
+
+  GroupHeader := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader.Layout.Height := 15;
+  GroupHeader.GroupCondition := 'copy(''[Name]'',1,1)';
+  {$ifdef ColorBands}
+  GroupHeader.Frame.Shape := fsRectangle;
+  GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(GroupHeader);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 10;
+  Memo.Layout.Height := 8;
+  Memo.UseParentFont := False;
+  Memo.Text := '[copy(Name,1,1)]';
+  Memo.Font.Size := 16;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[Name]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', Population)]';
+
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+end;
+
+procedure TContnrDemo.LoadDesignFromFile(const AFilename: string);
+var
+  rs: TFPReportJSONStreamer;
+  fs: TFileStream;
+  lJSON: TJSONObject;
+begin
+  if AFilename = '' then
+    Exit;
+  if not FileExists(AFilename) then
+    raise Exception.CreateFmt('The file "%s" can not be found', [AFilename]);
+  fs := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
+  try
+    lJSON := TJSONObject(GetJSON(fs));
+  finally
+    fs.Free;
+  end;
+  rs := TFPReportJSONStreamer.Create(nil);
+  rs.JSON := lJSON; // rs takes ownership of lJSON
+  try
+    rpt.ReadElement(rs);
+  finally
+    rs.Free;
+  end;
+end;
+
+procedure TContnrDemo.HookupData(const AComponentName: string; const AData: TFPReportData);
+var
+  b: TFPReportCustomBandWithData;
+begin
+  b := TFPReportCustomBandWithData(rpt.FindRecursive(AComponentName));
+  if Assigned(b) then
+    b.Data := AData;
+end;
+
+destructor TContnrDemo.Destroy;
+begin
+  FreeAndNil(FReportData);
+  inherited Destroy;
+end;
+
+constructor TCollectionDemo.Create(AOWner: TComponent);
+begin
+  inherited;
+  FReportData := TFPReportCollectionData.Create(nil);
+  TFPReportCollectionData(FReportData).OwnsCollection:=True;
+end;
+
+{ TCollectionDemo }
+
+procedure TCollectionDemo.InitialiseData;
+
+Var
+  SL : TStringList;
+  i : Integer;
+  N,V : String;
+  C : TCountry;
+  Coll : TCollection;
+
+begin
+  Coll:=TCollection.Create(TCountry);
+  TFPReportCollectionData(FReportData).Collection:=coll;
+  SL:=TStringList.Create;
+  try
+    {$I countries.inc}
+    SL.Sort;
+    For I:=0 to SL.Count-1 do
+      begin
+      C:=Coll.Add As TCountry;
+      SL.GetNameValue(I,N,V);
+      C.Name:=N;
+      C.Population:=StrToInt64Def(V,0);
+      end;
+  finally
+    SL.Free;
+  end;
+end;
+
+end.
+

+ 198 - 0
packages/fcl-report/demos/rptdataset.pp

@@ -0,0 +1,198 @@
+unit rptdataset;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  fpreportdb,
+  db,
+  dbf,
+  udapp;
+
+type
+  TDatasetDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportDatasetData;
+    DataSet: TDBF;
+  Public
+    procedure   CreateReportDesign; override;
+    procedure   InitialiseData; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF,
+  FPCanvas,
+  dbf_fields;
+
+{ TDatasetDemo }
+
+procedure TDatasetDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  Image: TFPReportImage;
+  PageFooter: TFPReportPageFooterBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 8 - Datasets';
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 140;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Dataset Demo';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := TFPReportColor($000080);
+  Memo.Font.Size := 24;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 30;
+
+  Image := TFPReportImage.Create(DataBand);
+  Image.Layout.Top := 0;
+  Image.Layout.Left := 10;
+  Image.Layout.Height := 20;
+  Image.Layout.Width := 14.8;
+  Image.FieldName := 'Photo';
+  Image.Stretched := True;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Name: [name]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 80;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Email: [Address]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Age: [Age]';
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 30;
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+end;
+
+procedure TDatasetDemo.InitialiseData;
+
+var
+  fields: TDbfFieldDefs;
+  lDataSet : TDBF;
+begin
+  lDataSet := TDBF.Create(Self);
+  lDataSet.TableName := 'test.dbf';
+  Dataset:=lDataset;
+  lReportData.DataSet:= DataSet;
+
+  if FileExists('test.dbf') then
+    exit;
+  // If you wanted to create a new DBF table
+  fields := TDbfFieldDefs.Create(nil);
+  fields.Add('Name', ftString, 50);
+  fields.Add('Address', ftString, 150);
+  fields.Add('Age', ftInteger);
+  fields.Add('Photo', ftBlob);
+  lDataSet.CreateTableEx(fields); // <== Now we have an empty db table
+
+  lDataSet.Open;
+
+  lDataSet.Insert;
+  lDataSet.FieldByName('Name').AsString := 'Kimi Raikkonen';
+  lDataSet.FieldByName('Address').AsString := '[email protected]';
+  lDataSet.FieldByName('Age').AsInteger := 35;
+  TBlobField(lDataSet.FieldByName('Photo')).LoadFromFile(ExpandFileName('../common/pictures/man01.png'));
+  lDataSet.Post;
+
+  lDataSet.Insert;
+  lDataSet.FieldByName('Name').AsString := 'Michael Schumacher';
+  lDataSet.FieldByName('Address').AsString := '[email protected]';
+  lDataSet.FieldByName('Age').AsInteger := 28;
+  TBlobField(lDataSet.FieldByName('Photo')).LoadFromFile(ExpandFileName('../common/pictures/man02.png'));
+  lDataSet.Post;
+
+  lDataSet.Insert;
+  lDataSet.FieldByName('Name').AsString := 'Alain Prost';
+  lDataSet.FieldByName('Address').AsString := '[email protected]';
+  lDataSet.FieldByName('Age').AsInteger := 64;
+  TBlobField(lDataSet.FieldByName('Photo')).LoadFromFile(ExpandFileName('../common/pictures/man03.png'));
+  lDataSet.Post;
+
+  lDataSet.Insert;
+  lDataSet.FieldByName('Name').AsString := 'Jenson Button';
+  lDataSet.FieldByName('Address').AsString := '[email protected]';
+  lDataSet.FieldByName('Age').AsInteger := 50;
+  TBlobField(lDataSet.FieldByName('Photo')).LoadFromFile(ExpandFileName('../common/pictures/man04.png'));
+  lDataSet.Post;
+
+  lDataSet.Insert;
+  lDataSet.FieldByName('Name').AsString := 'Fernando Allonso';
+  lDataSet.FieldByName('Address').AsString := '[email protected]';
+  lDataSet.FieldByName('Age').AsInteger := 47;
+  TBlobField(lDataSet.FieldByName('Photo')).LoadFromFile(ExpandFileName('../common/pictures/man05.png'));
+  lDataSet.Post;
+
+  fields.Free;
+end;
+
+constructor TDatasetDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  lReportData := TFPReportDatasetData.Create(nil);
+end;
+
+destructor TDatasetDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(DataSet);
+  inherited Destroy;
+end;
+
+end.
+

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

@@ -0,0 +1,259 @@
+unit rptexpressions;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+
+  { TExpressionsDemo }
+
+  TExpressionsDemo = class(TReportDemoApp)
+  private
+    FReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure DoBeforePrint(Sender: TFPReportElement);
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportFieldNames(Sender: TObject; List: TStrings);
+  protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+
+{ TExpressionsDemo }
+
+procedure TExpressionsDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TExpressionsDemo.DoBeforePrint(Sender: TFPReportElement);
+begin
+  With rpt.Variables.FindVariable('isEven') do
+    AsBoolean:=Not AsBoolean;
+end;
+
+procedure TExpressionsDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'element' then
+  begin
+    AValue := sl[FReportData.RecNo-1];
+  end;
+end;
+
+procedure TExpressionsDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if FReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TExpressionsDemo.GetReportFieldNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('element');
+end;
+
+procedure TExpressionsDemo.InitialiseData;
+var
+  i: integer;
+begin
+  sl := TStringList.Create;
+  for i := 1 to 15 do
+    sl.Add(Format(Char(64+i)+'-Item %d', [i]));
+end;
+
+procedure TExpressionsDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  DataHeader: TFPReportDataHeaderBand;
+begin
+  PaperManager.RegisterStandardSizes;
+
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 2 - Expression Evaluation';
+
+  // Line zero : even..
+  rpt.Variables.AddVariable('isEven').AsBoolean:=True;
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 20;
+  p.Margins.Top := 20;
+  p.Margins.Right := 20;
+  p.Margins.Bottom := 20;
+  p.Data := FReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 8;
+  Memo.Text := 'THE REPORT TITLE';
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 125;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 8;
+  Memo.Text := 'Report Date: [TODAY]';
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '1 + 2 = [1 + 2].';
+  Memo.Options := [moDisableExpressions];
+
+  DataHeader := TFPReportDataHeaderBand.Create(p);
+  DataHeader.Layout.Height := 10;
+  {$ifdef ColorBands}
+  DataHeader.Frame.Shape := fsRectangle;
+  DataHeader.Frame.BackgroundColor := clDataHeaderFooter;
+  {$endif}
+  DataHeader.UseParentFont := False;
+  DataHeader.Font.Name := 'LiberationSans-Bold';
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 75;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Complex Example';
+  Memo.Frame.Lines := [flBottom];
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 85;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 25;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Single Calc';
+  Memo.Frame.Lines := [flBottom];
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 120;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 35;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'System Variables';
+  Memo.StretchMode := smActualHeight;
+  Memo.Frame.Lines := [flBottom];
+  Memo.TextAlignment.Vertical := tlCenter;
+
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 10;
+  DataBand.HeaderBand := DataHeader;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+  DataBand.OnBeforePrint:=@DoBeforePrint;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 75;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Even row: [isEven]. Hello world "[element]", and first letter is "[COPY(element, 1, 1)]".';
+  Memo.Options := [];
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 85;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 25;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[2 + RecNo - 6]';
+  Memo.Options := [moHideZeros];
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 120;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 25;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'RecNo = [RecNo].';
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 135;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '1 + 2 = [1 + 2].';
+end;
+
+constructor TExpressionsDemo.Create(AOwner: TComponent);
+begin
+  Inherited;
+  FReportData := TFPReportUserData.Create(self);
+  FReportData.OnGetValue := @GetReportDataValue;
+  FReportData.OnGetEOF := @GetReportDataEOF;
+  FReportData.OnFirst := @GetReportDataFirst;
+  FReportData.OnGetNames := @GetReportFieldNames;
+end;
+
+destructor TExpressionsDemo.Destroy;
+begin
+  FreeAndNil(FReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+
+
+end.
+

+ 297 - 0
packages/fcl-report/demos/rptframes.pp

@@ -0,0 +1,297 @@
+unit rptframes;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+
+  TFramesDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportFieldNames(Sender: TObject; List: TStrings);
+
+  protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent) ; override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  FPCanvas,
+  fpTTF;
+
+{ TFramesDemo }
+
+procedure TFramesDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TFramesDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'country' then
+  begin
+    AValue := sl.Names[lReportData.RecNo-1];
+  end
+  else if AValueName = 'population' then
+  begin
+    AValue := sl.Values[sl.Names[lReportData.RecNo-1]];
+  end;
+end;
+
+procedure TFramesDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TFramesDemo.GetReportFieldNames(Sender: TObject; List: TStrings);
+begin
+  {$IFDEF gdebug}
+  writeln('********** GetReportFieldNames');
+  {$ENDIF}
+  List.Add('country');
+  List.Add('population');
+end;
+
+procedure TFramesDemo.InitialiseData;
+begin
+  sl := TStringList.Create;
+  {$I countries.inc}
+  sl.Sort;
+end;
+
+procedure TFramesDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  ReportSummary: TFPReportSummaryBand;
+  PageHeader: TFPReportPageHeaderBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 4 - Frames and Fonts';
+
+  p :=  TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 140;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Country and Population as of 2014';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := clNavy;
+  Memo.Font.Name := 'LiberationSerif';
+  Memo.Font.Size := 24;
+
+  PageHeader := TFPReportPageHeaderBand.Create(p);
+  PageHeader.Layout.Height := 30;
+  {$ifdef ColorBands}
+  PageHeader.Frame.Shape := fsRectangle;
+  PageHeader.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 15;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'PageHeader band';
+
+  GroupHeader := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader.Layout.Height := 15;
+  GroupHeader.GroupCondition := 'copy(''[country]'',1,1)';
+  GroupHeader.Frame.Color := clNavy;
+  GroupHeader.Frame.Pen := psDashDot;
+  {$ifdef ColorBands}
+  GroupHeader.Frame.Shape := fsRectangle;
+  GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(GroupHeader);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 10;
+  Memo.Layout.Height := 8;
+  Memo.Text := '[copy(country,1,1)]';
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.Font.Color := TFPReportColor($C00000);
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := TFPReportColor($008080);
+  Memo.Frame.Pen := psDot;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[country]';
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  { ReportSummary could have been designed before PageFooter. The layouting
+    will sort out the order anyway. }
+  ReportSummary := TFPReportSummaryBand.Create(p);
+  ReportSummary.Layout.Height := 60;
+  ReportSummary.StartNewPage := True;
+  {$ifdef ColorBands}
+  ReportSummary.Frame.Shape := fsRectangle;
+  ReportSummary.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+  ReportSummary.UseParentFont := False;
+  ReportSummary.Font.Size := 8;
+
+  Memo := TFPReportMemo.Create(ReportSummary);
+  Memo.Layout.Left := 3;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 100;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'This block is the ReportSummary band - forced on a new page.';
+
+  Memo := TFPReportMemo.Create(ReportSummary);
+  Memo.Layout.Left := 20;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Lines: Left & Bottom';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Color := clNavy;
+  Memo.Frame.Lines := [flLeft, flBottom];
+
+  Memo := TFPReportMemo.Create(ReportSummary);
+  Memo.Layout.Left := 90;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Lines: Top & Right';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Color := clNavy;
+  Memo.Frame.Lines := [flTop, flRight];
+
+  Memo := TFPReportMemo.Create(ReportSummary);
+  Memo.Layout.Left := 20;
+  Memo.Layout.Top := 40;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Lines: Top & Bottom';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Color := clNavy;
+  Memo.Frame.Lines := [flTop, flBottom];
+
+  Memo := TFPReportMemo.Create(ReportSummary);
+  Memo.Layout.Left := 90;
+  Memo.Layout.Top := 40;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Lines: Left & Right';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Color := clNavy;
+  Memo.Frame.Lines := [flLeft, flRight];
+end;
+
+constructor TFramesDemo.Create(AOwner: TComponent);
+begin
+  inherited;
+  lReportData := TFPReportUserData.Create(nil);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnFirst := @GetReportDataFirst;
+  lReportData.OnGetNames := @GetReportFieldNames;
+end;
+
+destructor TFramesDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+
+end.
+

+ 242 - 0
packages/fcl-report/demos/rptgrouping.pp

@@ -0,0 +1,242 @@
+unit rptgrouping;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+
+  TGroupingDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportFieldNames(Sender: TObject; List: TStrings);
+  Protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign;override;
+    procedure   LoadDesignFromFile(const AFilename: string);
+    procedure   HookupData(const AComponentName: string; const AData: TFPReportData);
+  public
+    constructor Create(AOWner :TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpReportStreamer,
+  fpTTF,
+  fpJSON,
+  jsonparser;
+
+{ TGroupingDemo }
+
+procedure TGroupingDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TGroupingDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'country' then
+  begin
+    AValue := sl.Names[lReportData.RecNo-1];
+  end
+  else if AValueName = 'population' then
+  begin
+    AValue := sl.Values[sl.Names[lReportData.RecNo-1]];
+  end;
+end;
+
+procedure TGroupingDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TGroupingDemo.GetReportFieldNames(Sender: TObject; List: TStrings);
+begin
+  {$IFDEF gdebug}
+  writeln('********** GetReportFieldNames');
+  {$ENDIF}
+  List.Add('country');
+  List.Add('population');
+end;
+
+procedure TGroupingDemo.InitialiseData;
+begin
+  sl := TStringList.Create;
+  {$I countries.inc}
+  sl.Sort;
+end;
+
+procedure TGroupingDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 3 - Grouping';
+
+  p :=  TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 35;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 80;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'COUNTRY AND POPULATION AS OF 2014';
+
+  GroupHeader := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader.Layout.Height := 15;
+  GroupHeader.GroupCondition := 'copy(''[country]'',1,1)';
+  {$ifdef ColorBands}
+  GroupHeader.Frame.Shape := fsRectangle;
+  GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(GroupHeader);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 10;
+  Memo.Layout.Height := 8;
+  Memo.UseParentFont := False;
+  Memo.Text := '[copy(country,1,1)]';
+  Memo.Font.Size := 16;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[country]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+end;
+
+procedure TGroupingDemo.LoadDesignFromFile(const AFilename: string);
+var
+  rs: TFPReportJSONStreamer;
+  fs: TFileStream;
+  lJSON: TJSONObject;
+begin
+  if AFilename = '' then
+    Exit;
+  if not FileExists(AFilename) then
+    raise Exception.CreateFmt('The file "%s" can not be found', [AFilename]);
+
+  fs := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
+  try
+    lJSON := TJSONObject(GetJSON(fs));
+  finally
+    fs.Free;
+  end;
+
+  rs := TFPReportJSONStreamer.Create(nil);
+  rs.JSON := lJSON; // rs takes ownership of lJSON
+  try
+    rpt.ReadElement(rs);
+  finally
+    rs.Free;
+  end;
+end;
+
+procedure TGroupingDemo.HookupData(const AComponentName: string; const AData: TFPReportData);
+var
+  b: TFPReportCustomBandWithData;
+begin
+  b := TFPReportCustomBandWithData(rpt.FindRecursive(AComponentName));
+  if Assigned(b) then
+    b.Data := AData;
+end;
+
+constructor TGroupingDemo.Create(AOwner: TComponent);
+begin
+  inherited;
+  lReportData := TFPReportUserData.Create(nil);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnFirst := @GetReportDataFirst;
+  lReportData.OnGetNames := @GetReportFieldNames;
+end;
+
+destructor TGroupingDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+end.
+

+ 245 - 0
packages/fcl-report/demos/rptimages.pp

@@ -0,0 +1,245 @@
+unit rptimages;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  SysUtils,
+  Classes,
+  fpreport,
+  udapp;
+
+type
+  TImagesDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+  protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOWner : TComponent); override;
+    destructor  Destroy; override;
+    procedure GetReportDataNames(Sender: TObject; List: TStrings);
+  end;
+
+
+implementation
+
+uses
+  fpTTF,
+  FPCanvas;
+
+{ TImagesDemo }
+
+procedure TImagesDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  if AValueName = 'country' then
+  begin
+    AValue := sl.Names[lReportData.RecNo-1];
+  end
+  else if AValueName = 'population' then
+  begin
+    AValue := sl.Values[sl.Names[lReportData.RecNo-1]];
+  end;
+end;
+
+procedure TImagesDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TImagesDemo.InitialiseData;
+begin
+  sl := TStringList.Create;
+  {$I countries.inc}
+  sl.Sort;
+end;
+
+procedure TImagesDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  PageHeader: TFPReportPageHeaderBand;
+  Image: TFPReportImage;
+  Checkbox: TFPReportCheckbox;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 7 - Images and Checkboxes';
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 140;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Country and Population as of 2014';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := TFPReportColor($000080);
+  Memo.Font.Name := 'LiberationSerif';
+  Memo.Font.Size := 24;
+
+  PageHeader := TFPReportPageHeaderBand.Create(p);
+  PageHeader.Layout.Height := 8;
+  PageHeader.Frame.BackgroundColor := TFPReportColor($000080);
+  PageHeader.Frame.Shape := fsRectangle;
+  PageHeader.VisibleOnPage := vpNotOnFirst;
+  {$ifdef ColorBands}
+  PageHeader.Frame.Shape := fsRectangle;
+  PageHeader.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+  PageHeader.UseParentFont := False;
+  PageHeader.Font.Color := clWhite;
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 1.5;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Country';
+  Memo.TextAlignment.Vertical := tlCenter;
+  {$ifdef ColorBands}
+  // just so the text is visible in this situation
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clNavy;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 1.5;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Population';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  {$ifdef ColorBands}
+  // just so the text is visible in this situation
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clNavy;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageHeader);
+  Memo.Layout.Left := 115;
+  Memo.Layout.Top := 1.5;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '> 30 million';
+  Memo.TextAlignment.Vertical := tlCenter;
+  {$ifdef ColorBands}
+  // just so the text is visible in this situation
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clNavy;
+  {$endif}
+
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  DataBand.Data := lReportData;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[country]';
+  Memo.TextAlignment.Vertical := tlCenter;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  Checkbox := TFPReportCheckbox.Create(DataBand);
+  Checkbox.Layout.Left := 120;
+  Checkbox.Layout.Top := 1;
+  Checkbox.Expression := '[population] > 30000000';
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 30;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  Image := TFPReportImage.Create(PageFooter);
+  Image.Layout.Left := 0;
+  Image.Layout.Top := 0;
+  Image.Layout.Width := 40;
+  Image.Layout.Height := 30;
+  Image.LoadFromFile('company-logo.png');
+  Image.Stretched := True;
+end;
+
+constructor TImagesDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  lReportData := TFPReportUserData.Create(Self);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnGetNames := @GetReportDataNames;
+end;
+
+destructor TImagesDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+procedure TImagesDemo.GetReportDataNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('country');
+  List.Add('population');
+end;
+
+
+
+end.
+

+ 191 - 0
packages/fcl-report/demos/rptjson.pp

@@ -0,0 +1,191 @@
+unit rptjson;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  fpreportjson,
+  udapp;
+
+type
+
+  TJSONDemo = class(TReportDemoApp)
+  private
+    FReportData : TFPReportJSONData;
+  Protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign;override;
+    procedure   LoadDesignFromFile(const AFilename: string);
+    procedure   HookupData(const AComponentName: string; const AData: TFPReportData);
+  public
+    constructor Create(AOWner :TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpReportStreamer,
+  fpTTF,
+  fpJSON,
+  jsonparser;
+
+{ TJSONDemo }
+
+procedure TJSONDemo.InitialiseData;
+begin
+  FReportData.Path:='Data';
+  FReportData.LoadFromFile('countries.json');
+end;
+
+procedure TJSONDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 12 - JSON Data';
+
+  p :=  TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := FReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 35;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 80;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'COUNTRY AND POPULATION AS OF 2014';
+
+  GroupHeader := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader.Layout.Height := 15;
+  GroupHeader.GroupCondition := 'copy(''[Name]'',1,1)';
+  {$ifdef ColorBands}
+  GroupHeader.Frame.Shape := fsRectangle;
+  GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(GroupHeader);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 10;
+  Memo.Layout.Height := 8;
+  Memo.UseParentFont := False;
+  Memo.Text := '[copy(Name,1,1)]';
+  Memo.Font.Size := 16;
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[Name]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 70;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', Population)]';
+
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+end;
+
+procedure TJSONDemo.LoadDesignFromFile(const AFilename: string);
+var
+  rs: TFPReportJSONStreamer;
+  fs: TFileStream;
+  lJSON: TJSONObject;
+begin
+  if AFilename = '' then
+    Exit;
+  if not FileExists(AFilename) then
+    raise Exception.CreateFmt('The file "%s" can not be found', [AFilename]);
+
+  fs := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
+  try
+    lJSON := TJSONObject(GetJSON(fs));
+  finally
+    fs.Free;
+  end;
+
+  rs := TFPReportJSONStreamer.Create(nil);
+  rs.JSON := lJSON; // rs takes ownership of lJSON
+  try
+    rpt.ReadElement(rs);
+  finally
+    rs.Free;
+  end;
+end;
+
+procedure TJSONDemo.HookupData(const AComponentName: string; const AData: TFPReportData);
+var
+  b: TFPReportCustomBandWithData;
+begin
+  b := TFPReportCustomBandWithData(rpt.FindRecursive(AComponentName));
+  if Assigned(b) then
+    b.Data := AData;
+end;
+
+constructor TJSONDemo.Create(AOwner: TComponent);
+begin
+  inherited;
+  FReportData := TFPReportJSONData.Create(nil);
+end;
+
+destructor TJSONDemo.Destroy;
+begin
+  FreeAndNil(FReportData);
+  inherited Destroy;
+end;
+
+end.
+

+ 320 - 0
packages/fcl-report/demos/rptmasterdetail.pp

@@ -0,0 +1,320 @@
+unit rptmasterdetail;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+  TMasterDetailDemo = class(TReportDemoApp)
+  private
+    FMasterData: TFPReportUserData;
+    FDetailData: TFPReportUserData;
+    FDetail2Data: TFPReportUserData;
+    FMasterNo: Integer;
+    FDetailNo: Integer;
+    FDetail2No: Integer;
+    procedure   MasterDataFirst(Sender: TObject);
+    procedure   MasterDataGetValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   MasterDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   MasterDataNext(Sender: TObject);
+    procedure   DetailDataFirst(Sender: TObject);
+    procedure   DetailDataGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+    procedure   DetailDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   DetailDataNext(Sender: TObject);
+    procedure   Detail2DataFirst(Sender: TObject);
+    procedure   Detail2DataGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+    procedure   Detail2DataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   Detail2DataNext(Sender: TObject);
+  Public
+    procedure   CreateReportDesign; override;
+    procedure MasterDataGetNames(Sender: TObject; List: TStrings);
+    procedure DetailDataGetNames(Sender: TObject; List: TStrings);
+    procedure Detail2DataGetNames(Sender: TObject; List: TStrings);
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF;
+
+{ Our user defined data stored in arrays. }
+const
+  Master: array[1..3, 1..2] of String = ( // master ID, master name
+    ('1', 'master 1'),
+    ('2', 'master 2'),
+    ('3', 'master 3'));
+
+  Detail: array[1..12, 1..2] of String = ( // master ID, detail name
+    ('1', 'detail 1.1'), ('1', 'detail 1.2'), ('1', 'detail 1.3'),
+    ('1', 'detail 1.4'), ('1', 'detail 1.5'),
+
+    ('2', 'detail 2.1'), ('2', 'detail 2.2'), ('2', 'detail 2.3'),
+
+    ('3', 'detail 3.1'), ('3', 'detail 3.2'), ('3', 'detail 3.3'),
+    ('3', 'detail 3.4'));
+
+  D2: array[1..4, 1..2] of String = ( // detail #1 name, detail #2 name
+    ('detail 1.2', 'detail 1.2.1'),
+    ('detail 1.4', 'detail 1.4.1'),
+    ('detail 1.4', 'detail 1.4.2'),
+    ('detail 2.2', 'detail 2.2.1'));
+
+{ TMasterDetailDemo }
+
+procedure TMasterDetailDemo.MasterDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('MasterDataFirst');
+  {$ENDIF}
+  FMasterNo := 1;
+end;
+
+procedure TMasterDetailDemo.MasterDataGetValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('MasterDataGetValue - %d', [FMasterData.RecNo]));
+  {$ENDIF}
+  if AValueName  = 'mastername' then
+    AValue := Master[FMasterNo][2];
+end;
+
+procedure TMasterDetailDemo.MasterDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('MasterDataEOF - %d', [FMasterData.RecNo]));
+  {$ENDIF}
+  IsEOF := FMasterNo > High(Master);
+end;
+
+procedure TMasterDetailDemo.MasterDataNext(Sender: TObject);
+begin
+  Inc(FMasterNo);
+end;
+
+procedure TMasterDetailDemo.DetailDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('DetailDataFirst');
+  {$ENDIF}
+  FDetailNo := 1;
+  while (not FDetailData.EOF) and (Detail[FDetailNo][1] <> Master[FMasterNo][1]) do
+    Inc(FDetailNo);
+end;
+
+procedure TMasterDetailDemo.DetailDataGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  {$IFDEF gdebug}
+  writeln('DetailDataGetValue');
+  {$ENDIF}
+  if AValueName = 'detailname' then
+    AValue := Detail[FDetailNo][2];
+end;
+
+procedure TMasterDetailDemo.DetailDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln('DetailDataEOF');
+  {$ENDIF}
+  IsEOF := FDetailNo > High(Detail);
+end;
+
+procedure TMasterDetailDemo.DetailDataNext(Sender: TObject);
+begin
+  Inc(FDetailNo);
+  while (not FDetailData.EOF) and (Detail[FDetailNo][1] <> Master[FMasterNo][1]) do
+    Inc(FDetailNo);
+end;
+
+procedure TMasterDetailDemo.Detail2DataFirst(Sender: TObject);
+begin
+  FDetail2No := 1;
+  while (not FDetail2Data.EOF) and (D2[FDetail2No][1] <> Detail[FDetailNo][2]) do
+    Inc(FDetail2No);
+end;
+
+procedure TMasterDetailDemo.Detail2DataGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  if AValueName = 'detail2name' then
+    AValue := D2[FDetail2No][2]
+end;
+
+procedure TMasterDetailDemo.Detail2DataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  IsEOF := FDetail2No > High(D2);
+end;
+
+procedure TMasterDetailDemo.Detail2DataNext(Sender: TObject);
+begin
+  Inc(FDetail2No);
+  while (not FDetail2Data.EOF) and (D2[FDetail2No][1] <> Detail[FDetailNo][2]) do
+    Inc(FDetail2No);
+end;
+
+procedure TMasterDetailDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  MasterDataBand: TFPReportDataBand;
+  DetailDataBand: TFPReportDataBand;
+  Detail2DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 10 - Master/Detail using userdata';
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := FMasterData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 20;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := TitleBand.Layout.Width;
+  Memo.Layout.Height := 15;
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'LiberationSans-Bold';
+  Memo.Font.Size := 18;
+  Memo.Text := 'FPReport Demo 10' + LineEnding + 'Master/Detail using userdata';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+
+
+  MasterDataBand := TFPReportDataBand.Create(p);
+  MasterDataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  MasterDataBand.Frame.Shape := fsRectangle;
+  MasterDataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(MasterDataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[mastername]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+
+  DetailDataBand := TFPReportDataBand.Create(p);
+  DetailDataBand.Layout.Height := 8;
+  DetailDataBand.Data := FDetailData;
+  {$ifdef ColorBands}
+  DetailDataBand.Frame.Shape := fsRectangle;
+  DetailDataBand.Frame.BackgroundColor := clChildBand;
+  {$endif}
+
+  { associate with Master band }
+  DetailDataBand.MasterBand := MasterDataBand;
+
+  Memo := TFPReportMemo.Create(DetailDataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 40;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[detailname]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+
+  Detail2DataBand := TFPReportDataBand.Create(p);
+  Detail2DataBand.Layout.Height := 8;
+  Detail2DataBand.Data := FDetail2Data;
+  {$ifdef ColorBands}
+  Detail2DataBand.Frame.Shape := fsRectangle;
+  Detail2DataBand.Frame.BackgroundColor := TFPReportColor($FFFFA9);
+  {$endif}
+
+  { associate with Master band }
+  Detail2DataBand.MasterBand := DetailDataBand;
+
+  Memo := TFPReportMemo.Create(Detail2DataBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 40;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[detail2name]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clNavy;
+end;
+
+procedure TMasterDetailDemo.MasterDataGetNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('mastername');
+end;
+
+procedure TMasterDetailDemo.DetailDataGetNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('detailname');
+end;
+
+procedure TMasterDetailDemo.Detail2DataGetNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('detail2name');
+end;
+
+constructor TMasterDetailDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  FMasterData := TFPReportUserData.Create(nil);
+  FMasterData.OnGetValue := @MasterDataGetValue;
+  FMasterData.OnGetEOF := @MasterDataEOF;
+  FMasterData.OnFirst := @MasterDataFirst;
+  FMasterData.OnNext := @MasterDataNext;
+  FMasterData.OnGetNames := @MasterDataGetNames;
+
+  FDetailData := TFPReportUserData.Create(nil);
+  FDetailData.OnGetValue := @DetailDataGetValue;
+  FDetailData.OnGetEOF := @DetailDataEOF;
+  FDetailData.OnFirst := @DetailDataFirst;
+  FDetailData.OnNext := @DetailDataNext;
+  FDetailData.OnGetNames := @DetailDataGetNames;
+
+  FDetail2Data := TFPReportUserData.Create(nil);
+  FDetail2Data.OnGetValue := @Detail2DataGetValue;
+  FDetail2Data.OnGetEOF := @Detail2DataEOF;
+  FDetail2Data.OnFirst := @Detail2DataFirst;
+  FDetail2Data.OnNext := @Detail2DataNext;
+  FDetail2Data.OnGetNames := @Detail2DataGetNames;
+end;
+
+destructor TMasterDetailDemo.Destroy;
+begin
+  FreeAndNil(FMasterData);
+  FreeAndNil(FDetailData);
+  FreeAndNil(FDetail2Data);
+  inherited Destroy;
+end;
+
+
+end.
+

+ 346 - 0
packages/fcl-report/demos/rptmasterdetaildataset.pp

@@ -0,0 +1,346 @@
+unit rptmasterdetaildataset;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  fpreportdb,
+  db,
+  sqldb,
+  IBConnection,
+  udapp;
+
+type
+
+  TMasterDetailDatasetDemo = class(TReportDemoApp)
+  private
+    IBConnection1: TIBConnection;
+    SQLTransaction1: TSQLTransaction;
+    ProjectDS: TDataSource;
+    qryProject: TSQLQuery;
+    qryEmployee: TSQLQuery;
+    qryProjBudget: TSQLQuery;
+    ReportMasterData: TFPReportDatasetData;
+    ReportDetailData: TFPReportDatasetData;
+    ReportBudgetData: TFPReportDatasetData;
+  Protected
+    procedure   CreateReportDesign; override;
+    procedure   InitialiseData; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF;
+
+const
+  cDatabase = 'localhost:/usr/share/doc/firebird2.5-common-doc/examples/empbuild/employee.fdb';
+//  cDatabase = '/opt/firebird/examples/empbuild/employee.fdb';
+
+{ TMasterDetailDatasetDemo }
+
+procedure TMasterDetailDatasetDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  MasterDataBand: TFPReportDataBand;
+  DetailDataBand: TFPReportDataBand;
+  ProjBudgetBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  DataHeader: TFPReportDataHeaderBand;
+  BudgetDataHeader: TFPReportDataHeaderBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 11 - Master/Detail using datasets';
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := ReportMasterData;
+  p.Font.Name := 'LiberationSans';
+
+  // ======== ReportTitle band ===========
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 20;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := TitleBand.Layout.Width;
+  Memo.Layout.Height := 15;
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'LiberationSans-Bold';
+  Memo.Font.Size := 18;
+  Memo.Text := 'FPReport Demo 11' + LineEnding + 'Master/Detail using datasets';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+
+  // ======== MasterData band ===========
+  MasterDataBand := TFPReportDataBand.Create(p);
+  MasterDataBand.Layout.Height := 8;
+  {$ifdef ColorBands}
+  MasterDataBand.Frame.Shape := fsRectangle;
+  MasterDataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(MasterDataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[reportmasterdata.proj_id] - [reportmasterdata.proj_name]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+  // ======== DataHeader band for DetailData ===========
+  DataHeader := TFPReportDataHeaderBand.Create(p);
+  DataHeader.Layout.Height := 8;
+  {$ifdef ColorBands}
+  DataHeader.Frame.Shape := fsRectangle;
+  DataHeader.Frame.BackgroundColor := clDataHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Emp No.';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Options := [moDisableWordWrap];
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+  Memo := TFPReportMemo.Create(DataHeader);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Employee Name';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+  // ======== DetailData band ===========
+  DetailDataBand := TFPReportDataBand.Create(p);
+  DetailDataBand.Layout.Height := 5;
+  DetailDataBand.Data := ReportDetailData;
+  { associate this band with the MasterData band }
+  DetailDataBand.MasterBand := MasterDataBand;
+  { associate DataHeader band }
+  DetailDataBand.HeaderBand := DataHeader;
+  DetailDataBand.DisplayPosition := 0;
+  {$ifdef ColorBands}
+  DetailDataBand.Frame.Shape := fsRectangle;
+  DetailDataBand.Frame.BackgroundColor := clChildBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DetailDataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[reportdetaildata.emp_no]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+
+  Memo := TFPReportMemo.Create(DetailDataBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[reportdetaildata.first_name] [reportdetaildata.last_name]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+
+
+  // ======== DataHeader band for DetailData ===========
+  BudgetDataHeader := TFPReportDataHeaderBand.Create(p);
+  BudgetDataHeader.Layout.Height := 8;
+  {$ifdef ColorBands}
+  BudgetDataHeader.Frame.Shape := fsRectangle;
+  BudgetDataHeader.Frame.BackgroundColor := clDataHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(BudgetDataHeader);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Year';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+  Memo := TFPReportMemo.Create(BudgetDataHeader);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Budget';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+  Memo.Frame.BackgroundColor := clLtGray;
+
+
+  // ======== Project Budget band ===========
+  ProjBudgetBand := TFPReportDataBand.Create(p);
+  ProjBudgetBand.Layout.Height := 5;
+  ProjBudgetBand.Data := ReportBudgetData;
+  { associate this band with the MasterData band }
+  ProjBudgetBand.MasterBand := MasterDataBand;
+  ProjBudgetBand.HeaderBand := BudgetDataHeader;
+  ProjBudgetBand.DisplayPosition := 1;
+  {$ifdef ColorBands}
+  ProjBudgetBand.Frame.Shape := fsRectangle;
+  ProjBudgetBand.Frame.BackgroundColor := clChildBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(ProjBudgetBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[ReportBudgetData.FISCAL_YEAR]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+
+  Memo := TFPReportMemo.Create(ProjBudgetBand);
+  Memo.Layout.Left := 30;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', ReportBudgetData.PROJECTED_BUDGET)]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.Color := clBlack;
+end;
+
+procedure TMasterDetailDatasetDemo.InitialiseData;
+begin
+  SQLTransaction1 := TSQLTransaction.Create(Self);
+
+  IBConnection1 := TIBConnection.Create(Self);
+  with IBConnection1 do
+  begin
+    LoginPrompt := False;
+    DatabaseName := cDatabase;
+    KeepConnection := False;
+    Password := 'masterkey';
+    Transaction := SQLTransaction1;
+    UserName := 'sysdba';
+    Connected := True;
+  end;
+
+  qryProject := TSQLQuery.Create(Self);
+  with qryProject do
+  begin
+    Database := IBConnection1;
+    Transaction := SQLTransaction1;
+    ReadOnly := True;
+    SQL.Text := 'select * from PROJECT order by PROJ_NAME';
+    Active := True
+  end;
+
+  ProjectDS := TDataSource.Create(Self);
+  ProjectDS.DataSet := qryProject;
+
+  qryEmployee := TSQLQuery.Create(Self);
+  with qryEmployee do
+  begin
+    Database := IBConnection1;
+    Transaction := SQLTransaction1;
+    ReadOnly := True;
+    SQL.Text :=
+          'SELECT ' +
+          '  e.* ' +
+          'FROM EMPLOYEE e ' +
+          '  INNER JOIN EMPLOYEE_PROJECT ep on e.EMP_NO = ep.EMP_NO ' +
+          'where ep.proj_id = :proj_id ' +
+          'order by e.EMP_NO';
+    DataSource := ProjectDS;
+    Active := True
+  end;
+
+  qryProjBudget := TSQLQuery.Create(Self);
+  with qryProjBudget do
+  begin
+    Database := IBConnection1;
+    Transaction := SQLTransaction1;
+    ReadOnly := True;
+    SQL.Text :=
+          'SELECT ' +
+          '  b.FISCAL_YEAR, b.PROJECTED_BUDGET ' +
+          'FROM PROJ_DEPT_BUDGET b ' +
+          'where b.proj_id = :proj_id ' +
+          'order by b.FISCAL_YEAR';
+    DataSource := ProjectDS;
+    Active := True
+  end;
+
+  ReportMasterData.DataSet:= qryProject;
+  ReportDetailData.DataSet:= qryEmployee;
+  ReportBudgetData.DataSet:= qryProjBudget;
+end;
+
+constructor TMasterDetailDatasetDemo.Create(AOwner: TComponent);
+begin
+  Inherited;
+  ReportMasterData := TFPReportDatasetData.Create(Self);
+  ReportMasterData.Name := 'ReportMasterData';
+  ReportDetailData := TFPReportDatasetData.Create(Self);
+  ReportDetailData.Name := 'ReportDetailData';
+  ReportBudgetData := TFPReportDatasetData.Create(Self);
+  ReportBudgetData.Name := 'ReportBudgetData';
+end;
+
+destructor TMasterDetailDatasetDemo.Destroy;
+begin
+  IBConnection1.Close();
+  FreeAndNil(qryEmployee);
+  FreeAndNil(qryProject);
+  FreeAndNil(ReportMasterData);
+  FreeAndNil(ReportDetailData);
+  FreeAndNil(ReportBudgetData);
+  FreeAndNil(ProjectDS);
+  FreeAndNil(SQLTransaction1);
+  FreeAndNil(IBConnection1);
+  inherited Destroy;
+end;
+
+
+
+end.
+

+ 228 - 0
packages/fcl-report/demos/rptshapes.pp

@@ -0,0 +1,228 @@
+unit rptshapes;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+{$IFDEF USEPOLYGON}
+  reportpolygon,
+{$ENDIF}
+  udapp;
+
+type
+  TShapesDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+  protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF;
+
+{ TShapesDemo }
+
+procedure TShapesDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TShapesDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'element' then
+  begin
+    AValue := sl[lReportData.RecNo-1];
+  end;
+end;
+
+procedure TShapesDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TShapesDemo.InitialiseData;
+var
+  i: integer;
+begin
+  sl := TStringList.Create;
+  for i := 1 to 10 do
+    sl.Add(Format('Item %d', [i]));
+end;
+
+procedure TShapesDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  Memo: TFPReportMemo;
+  Shape: TFPReportShape;
+{$IFDEF USEPOLYGON}
+  Poly : TReportPolygon;
+{$ENDIF}
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 6 - Shapes';
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 200;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 40;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 100;
+  Memo.Layout.Height := 20;
+  Memo.Text := 'Demo 6 - Shapes';
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 24;
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 40;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stEllipse;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 55;
+  Shape.Layout.Top := 40;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stCircle;
+  Shape.Color:=clRed;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 110;
+  Shape.Layout.Top := 40;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stLine;
+//  Shape.Orientation := orNorth;
+//  Shape.Orientation := orSouth;
+//  Shape.Orientation := orEast;
+//  Shape.Orientation := orWest;
+//  Shape.Orientation := orNorthWest;
+//  Shape.Orientation := orSouthEast;
+//  Shape.Orientation := orNorthEast;
+  Shape.Orientation := orSouthWest;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 75;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stSquare;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 55;
+  Shape.Layout.Top := 75;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stTriangle;
+//  Shape.Orientation := orNorth;
+//  Shape.Orientation := orSouth;
+//  Shape.Orientation := orEast;
+//  Shape.Orientation := orWest;
+  Shape.Orientation := orNorthEast;
+//  Shape.Orientation := orSouthWest;
+//  Shape.Orientation := orSouthEast;
+//  Shape.Orientation := orNorthWest;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+  Shape := TFPReportShape.Create(TitleBand);
+  Shape.Layout.Left := 110;
+  Shape.Layout.Top := 75;
+  Shape.Layout.Width := 40;
+  Shape.Layout.Height := 25;
+  Shape.ShapeType := stRoundedRect;
+  Shape.Color:=clBlue;
+//  Shape.CornerRadius := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+
+{$IFDEF USEPOLYGON}
+  Poly := TReportPolygon.Create(TitleBand);
+  Poly.Layout.Left := 0;
+  Poly.Layout.Top := 110;
+  Poly.Layout.Width := 40;
+  Poly.Layout.Height := 25;
+  Poly.Corners:=5;
+  Poly.Color:=clRed;
+  Poly.LineWidth:=2;
+  Poly.Frame.Shape := fsRectangle;
+  Poly.Frame.BackgroundColor := TFPReportColor($E0E0E0);
+{$ENDIF}
+
+end;
+
+constructor TShapesDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  lReportData := TFPReportUserData.Create(self);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnFirst := @GetReportDataFirst;
+end;
+
+destructor TShapesDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+
+
+end.
+

+ 190 - 0
packages/fcl-report/demos/rptsimplelist.pp

@@ -0,0 +1,190 @@
+unit rptsimplelist;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+  TSimpleListDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportDataNames(Sender: TObject; List: TStrings);
+  Protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+uses
+  fpTTF;
+
+{ TSimpleListDemo }
+
+procedure TSimpleListDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TSimpleListDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'element' then
+  begin
+    AValue := sl[lReportData.RecNo-1];
+  end;
+end;
+
+procedure TSimpleListDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TSimpleListDemo.GetReportDataNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('element');
+end;
+
+procedure TSimpleListDemo.InitialiseData;
+var
+  i: integer;
+begin
+  sl := TStringList.Create;
+  for i := 1 to 30 do
+    sl.Add(Format('Item %d', [i]));
+end;
+
+procedure TSimpleListDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  SummaryBand: TFPReportSummaryBand;
+begin
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 1 - Simple Listing';
+  PaperManager.RegisterStandardSizes;
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 20;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 10;
+  Memo.Text := 'THE REPORT TITLE';
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 10;
+  {$ifdef ColorBands}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 60;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Hello world <[element]>.';
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef ColorBands}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 135;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo]';
+
+
+  SummaryBand := TFPReportSummaryBand.Create(p);
+  SummaryBand.Layout.Height := 40;
+  {$ifdef ColorBands}
+  SummaryBand.Frame.Shape := fsRectangle;
+  SummaryBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(SummaryBand);
+  Memo.Layout.Left := 19;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := 70;
+  Memo.Layout.Height := 25;
+  Memo.StretchMode := smActualHeight;
+  Memo.Text := 'This is some long text that should be wrapping inside the memo. It has a 10mm left margin, and a 7mm right margin. 0mm margin top and bottom.';
+  Memo.TextAlignment.LeftMargin := 10;
+  Memo.TextAlignment.RightMargin := 7;
+  Memo.TextAlignment.Horizontal := taWidth;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := clLtGray;
+end;
+
+constructor TSimpleListDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  lReportData := TFPReportUserData.Create(Self);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnFirst := @GetReportDataFirst;
+  lReportData.OnGetNames := @GetReportDataNames;
+end;
+
+destructor TSimpleListDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+
+end.
+

+ 235 - 0
packages/fcl-report/demos/rptttf.pp

@@ -0,0 +1,235 @@
+unit rptttf;
+
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+{.$define gdebugframes}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+  TTTFDemo = class(TReportDemoApp)
+  private
+    lReportData: TFPReportUserData;
+    sl: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportFieldNames(Sender: TObject; List: TStrings);
+  Protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign; override;
+  public
+    constructor Create(AOwner : TComponent); override;
+    destructor  Destroy; override;
+  end;
+
+
+implementation
+
+
+{ TTTFDemo }
+
+procedure TTTFDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+end;
+
+procedure TTTFDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if AValueName = 'country' then
+  begin
+    AValue := sl.Names[lReportData.RecNo-1];
+  end
+  else if AValueName = 'population' then
+  begin
+    AValue := sl.Values[sl.Names[lReportData.RecNo-1]];
+  end;
+end;
+
+procedure TTTFDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if lReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TTTFDemo.GetReportFieldNames(Sender: TObject; List: TStrings);
+begin
+  {$IFDEF gdebug}
+  writeln('********** GetReportFieldNames');
+  {$ENDIF}
+  List.Add('country');
+  List.Add('population');
+end;
+
+procedure TTTFDemo.InitialiseData;
+begin
+  sl := TStringList.Create;
+  {$I countries.inc}
+  sl.Sort;
+end;
+
+procedure TTTFDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+begin
+  PaperManager.RegisterStandardSizes;
+  rpt.Author := 'Graeme Geldenhuys';
+  rpt.Title := 'FPReport Demo 5 - TrueType Fonts';
+  rpt.TwoPass := True;
+
+  p := TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 30;
+  p.Margins.Top := 20;
+  p.Margins.Right := 30;
+  p.Margins.Bottom := 20;
+  p.Data := lReportData;
+  p.Font.Name := 'LiberationSans';
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  {$IFDEF gdebugframes}
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+  {$ENDIF}
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 140;
+  Memo.Layout.Height := 15;
+  Memo.Text := 'Country and Population as of 2014';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := clWhite;
+  Memo.Font.Name := 'Ubuntu'; // our custom TTF font
+  Memo.Font.Size := 24;
+  Memo.Frame.Shape := fsRectangle;
+  Memo.Frame.BackgroundColor := TFPReportColor($01579B);
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 5;
+  Memo.Layout.Top := 15;
+  Memo.Layout.Width := 140;
+  Memo.Layout.Height := 8;
+  Memo.Text := 'Developed for the <font color="#ffffff" bgcolor="#2E7D32"><a href="http://www.freepascal.org">Free Pascal</a></font> project';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Color := clGray;
+  Memo.Font.Name := 'Ubuntu'; // our custom TTF font
+  Memo.Options := [moAllowHTML, moDisableWordWrap];
+
+  GroupHeader := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader.Layout.Height := 15;
+  GroupHeader.Data := lReportData;
+  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];
+  {$ifdef gdebugframes}
+  GroupHeader.Frame.Shape := fsRectangle;
+  GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(GroupHeader);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := 7;
+  Memo.Layout.Height := 8;
+  Memo.Text := '[copy(country,1,1)]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 18;
+  Memo.Font.Color := TFPReportColor($01579B);
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  {$ifdef gdebugframes}
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 15;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := '<a href="http://en.wikipedia.org/wiki/[country]">[country]</a>';
+  Memo.Options := [moAllowHTML, moDisableWordWrap];
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.LinkColor := TFPReportColor($2E7D32);
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 105;
+  Memo.Layout.Top := 0;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  {$ifdef gdebugframes}
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+  {$endif}
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 125;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 30;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo] of [PAGECOUNT]';
+  Memo.UseParentFont := False;
+  Memo.Font.Name := 'DejaVuSans'; // our custom TTF font
+  Memo.Font.Size := 10;
+end;
+
+constructor TTTFDemo.Create(AOwner : TComponent);
+begin
+  Inherited;
+  lReportData := TFPReportUserData.Create(nil);
+  lReportData.OnGetValue := @GetReportDataValue;
+  lReportData.OnGetEOF := @GetReportDataEOF;
+  lReportData.OnFirst := @GetReportDataFirst;
+  lReportData.OnGetNames := @GetReportFieldNames;
+end;
+
+destructor TTTFDemo.Destroy;
+begin
+  FreeAndNil(lReportData);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+end.
+

BIN
packages/fcl-report/demos/test.dbf


BIN
packages/fcl-report/demos/test.dbt


+ 458 - 0
packages/fcl-report/demos/udapp.pp

@@ -0,0 +1,458 @@
+unit udapp;
+
+{$mode objfpc}
+{$h+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes, SysUtils, fpttf, fpreport,
+
+  {$IFDEF ExportPDF}
+  fpreportpdfexport,
+  {$ENDIF}
+  {$IFDEF ExportFPIMAGE}
+  fpreportfpimageexport,
+  {$ENDIF}
+  {$IFDEF ExportHTML}
+  fpreporthtmlexport,
+  {$ENDIF}
+  {$IFDEF ExportAggPas}
+  fpreportaggpasexport,
+  {$ENDIF}
+  {$IFDEF ExportLCL}
+  fpreportformexport,
+  fpreportprinterexport,
+  fpreportpreview,
+  cfgfpreportpdfexport,
+  cfgfpreportimageexport,
+  forms,
+  interfaces,
+  {$ENDIF}
+  {$IFDEF ExportFPGui}
+  fpreport_export_form,
+  fpg_base,
+  fpg_main,
+  fpg_form,
+  {$ENDIF}
+  custapp,
+  fpreportstreamer;
+
+Type
+  // Order is important for default. First available class will be used as default.
+  TRenderFormat = (rfDefault,rfPDF,rfFPImage,rfAggPas,rfLCL,rfFPGui,rfHTML);
+  TFPReportExporterClass = Class of TFPReportExporter;
+
+  { TReportDemoApp }
+
+  TReportDemoApp = class(TComponent)
+  private
+    Frpt: TFPReport;
+  protected
+    procedure InitialiseData; virtual;
+    procedure CreateReportDesign; virtual;
+  public
+//    procedure DoCreateJSON(const AFileName: String; RunTime: Boolean=False);
+    Property rpt : TFPReport read Frpt Write FRpt;
+  end;
+  TReportDemoAppClass = Class of TReportDemoApp;
+
+  { TReportDemoApplication }
+
+  { TReportRunner }
+
+  TReportRunner = Class (TComponent)
+  private
+    FBaseOutputFileName: String;
+    FDesignFileName: String;
+    FLocation: String;
+  Public
+    FCreateJSON: Boolean;
+    FReportApp : TReportDemoApp;
+    FExporter : TFPReportExporter;
+    FFormat : TRenderFormat;
+    FRunFileName: String;
+    Function  CreateReportExport : TFPReportExporter;
+    procedure DoCreateJSON(const AFileName: String; RunTime: Boolean);
+    procedure ExportReport;
+    procedure RunReport(AFileName: string);
+  Public
+    destructor destroy; override;
+    Procedure Execute;
+    Property CreateJSON : Boolean Read FCreateJSON Write FCreateJSON;
+    Property ReportApp : TReportDemoApp Read FReportApp Write FReportApp;
+    Property Format : TRenderFormat Read FFormat Write FFormat;
+    Property RunFileName : String Read FRunFileName Write FRunFileName;
+    Property DesignFileName : String Read FDesignFileName Write FDesignFileName;
+    Property Exporter : TFPReportExporter Read FExporter;
+    Property Location : String Read FLocation Write FLocation;
+    Property BaseOutputFileName : String Read FBaseOutputFileName Write FBaseOutputFileName;
+   end;
+
+  TReportDemoApplication = class(TCustomApplication)
+  private
+    FRunner: TReportRunner;
+    procedure ListReports(AWithIndentation: boolean = False);
+    procedure Usage(Msg: String);
+  Class
+    Var Reports : TStrings;
+  Protected
+    Property Runner : TReportRunner Read FRunner;
+  public
+    constructor Create(AOwner :TComponent) ; override;
+    destructor  Destroy; override;
+    procedure  DoRun; override;
+    class function GetReportClass(AName: String): TReportDemoAppClass;
+    Class Procedure RegisterReport(aName : String; AClasss : TReportDemoAppClass);
+    Class Procedure GetRegisteredReports(aList : TStrings);
+    Class function GetRenderClass(F: TRenderFormat): TFPReportExporterClass;
+    Class Function FormatName(F : TRenderFormat) : String;
+  end;
+
+
+
+implementation
+
+
+class function TReportDemoApplication.FormatName(F: TRenderFormat): String;
+begin
+  Str(F,Result);
+  delete(Result,1,2);
+end;
+
+{ TReportDemoApp }
+
+
+procedure TReportDemoApp.InitialiseData;
+begin
+  // Do nothing
+end;
+
+procedure TReportDemoApp.CreateReportDesign;
+begin
+  // Do nothing
+end;
+
+
+class function TReportDemoApplication.GetRenderClass(F: TRenderFormat
+  ): TFPReportExporterClass;
+
+begin
+  Case F of
+  {$IFDEF ExportPDF}
+     rfPDF: Result:=TFPReportExportPDF;
+  {$ENDIF}
+  {$IFDEF ExportFPIMAGE}
+     rfFPImage: Result:=TFPReportExportFPImage;
+  {$ENDIF}
+  {$IFDEF ExportFPIMAGE}
+     rfhtml: Result:=TFPReportExportHTML;
+  {$ENDIF}
+  {$IFDEF ExportAggPas}
+     rfAggPas: Result:=TFPReportExportAggPas;
+  {$ENDIF}
+  {$IFDEF ExportLCL}
+     rfLCL: Result:=TFPreportPreviewExport;
+  {$ENDIF}
+  {$IFDEF ExportFPGui}
+     rfFPGui: Result := TFPreportPreviewExport;
+  {$ENDIF}
+  else
+    Result:=Nil;
+  end;
+end;
+
+function TReportRunner.CreateReportExport: TFPReportExporter;
+
+Var
+  C, Def : TFPReportExporterClass;
+  F : TRenderFormat;
+
+begin
+  Result:=Nil;
+  C:=Nil;
+  Def:=Nil;
+  {$IFDEF ExportLCL}
+  def:=TFPreportPreviewExport;
+  {$ENDIF}
+  {$IFDEF ExportfpGUI}
+  def:=fpreport_export_form.TFPreportPreviewExport;
+  {$ENDIF}
+  F:=Succ(rfDefault);
+  While (Result=Nil) and (F<=High(TRenderFormat)) do
+    begin
+    C:=TReportDemoApplication.GetRenderClass(F);
+    if (Def=Nil) and (C<>Nil) then
+      Def:=C;
+    if (F=FFormat) and (C<>Nil)  then
+      Result:=C.Create(Self);
+    F:=Succ(F);
+    end;
+  If (Result=Nil) then
+    begin
+    if (FFormat=rfDefault) then
+      begin
+      if Def=Nil then
+        Raise Exception.Create('No default render format available. Please check the defines in udapp.pp')
+      else
+        Result:=Def.Create(Self);
+      end
+    else
+      Raise Exception.Create('Requested format %s not available. Please check the defines in udapp.');
+    end;
+end;
+
+destructor TReportRunner.destroy;
+begin
+  FreeAndNil(FReportApp);
+  inherited destroy;
+end;
+
+procedure TReportRunner.Execute;
+begin
+  FReportApp.InitialiseData;
+  FReportApp.CreateReportDesign;
+  If (DesignFileName<>'') then
+    DoCreateJSON(DesignFileName,False);
+  RunReport(RunFileName);
+  ExportReport;
+end;
+
+constructor TReportDemoApplication.Create(AOwner : TComponent);
+begin
+  Inherited;
+  StopOnException:=True;
+  FRunner:=TReportRunner.Create(Self);
+  FRunner.Location:=Location;
+end;
+
+destructor TReportDemoApplication.Destroy;
+begin
+  FreeAndNil(FRunner);
+  FreeAndNil(Reports);
+  inherited Destroy;
+end;
+
+procedure TReportRunner.RunReport(AFileName : string);
+
+begin
+  // specify what directories should be used to find TrueType fonts
+  gTTFontCache.SearchPath.Add(Location+'/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/dejavu/');
+{$ENDIF}
+  // ask to generate the font cache
+  gTTFontCache.BuildFontCache;
+  ReportApp.Rpt.RunReport;
+  If (aFileName<>'') then
+    DoCreateJSON(aFileName,True);
+end;
+
+Type
+  THackFPReport = Class(TFPReport)
+  Public
+    Property RTObjects;
+  end;
+
+procedure TReportRunner.DoCreateJSON(const AFileName: String; RunTime: Boolean);
+
+var
+  F : Text;
+  rs: TFPReportJSONStreamer;
+  S :String;
+begin
+  rs := TFPReportJSONStreamer.Create(Nil);
+  try
+    if RunTime then
+      TFPReportComponent(THackFPReport(FReportApp.rpt).RTObjects[0]).WriteElement(rs)
+    else
+      THackFPReport(FReportApp.rpt).WriteElement(rs);
+    S:=rs.JSON.FormatJSON;
+  finally
+    rs.Free;
+  end;
+ // Write to file
+  AssignFile(F,AFileName);
+  Rewrite(F);
+  Writeln(F,S);
+  CloseFile(F);
+end;
+
+procedure TReportRunner.ExportReport;
+
+begin
+  FExporter:=CreateReportExport;
+  try
+    {$IFDEF ExportLCL}
+    If FExporter is TFPreportPreviewExport then
+      Application.Initialize;
+    {$ENDIF}
+    {$IFDEF ExportFPGui}
+    If FExporter is TFPreportPreviewExport then
+      fpgApplication.Initialize;
+    {$ENDIF}
+
+    FReportApp.rpt.RenderReport(FExporter);
+  finally
+    FreeAndNil(FExporter);
+  end;
+end;
+
+procedure TReportDemoApplication.Usage(Msg : String);
+
+var
+  F : TRenderFormat;
+
+begin
+  if (Msg<>'') then
+  begin
+    Writeln('Error : ',Msg);
+    Writeln('');
+  end;
+  ExitCode:=Ord((Msg<>''));
+  Writeln('Usage : ',ExtractFileName(ParamStr(0)),' [options]');
+  Writeln('Where options is one of:');
+  Writeln('-h --help          This help');
+  Writeln('-l --list          List available reports.');
+  Writeln('-j --json=file     Also write report design to JSON file.');
+  Writeln('-f --format=FMT    Export format to use (use "default" for first, default format).');
+  Writeln('-r --runtime=file  Also write first page of report runtime to JSON file.');
+  Writeln('-d --demo=<name>   Run the demo specified by <name>.');
+  Writeln('');
+
+  Writeln('Known output formats for this binary: ');
+  for F in  TRenderformat do
+    if GetRenderClass(F)<>Nil then
+    WriteLn('   ', FormatName(F));
+
+  Writeln('');
+  Writeln('Known demos for this binary: ');
+  ListReports(True);
+  Free;
+  Halt(Ord(Msg<>''));
+end;
+
+procedure TReportDemoApplication.ListReports(AWithIndentation: boolean);
+Var
+  S : String;
+  lIndent: string;
+begin
+  if AWithIndentation then
+    lIndent := '  ';
+  if Assigned(Reports) then
+    for S in reports do
+    begin
+       Writeln(lIndent, s);
+    end;
+end;
+
+Type
+  { TReportDef }
+
+  TReportDef = Class
+    ReportClass: TReportDemoAppClass;
+    Constructor create(AClass : TReportDemoAppClass);
+  end;
+
+{ TReportDef }
+
+constructor TReportDef.create(AClass: TReportDemoAppClass);
+begin
+  ReportClass:=AClass;
+end;
+
+class function TReportDemoApplication.GetReportClass(AName: String
+  ): TReportDemoAppClass;
+
+Var
+  I : Integer;
+
+begin
+  Result:=Nil;
+  if Reports<>Nil then
+    begin
+    I:=Reports.IndexOf(AName);
+    if I<>-1 then
+      Result:=TReportDef(Reports.Objects[i]).ReportClass;
+    end;
+  if Result=Nil then
+    Raise Exception.Create('No such demo : '+AName);
+end;
+
+class procedure TReportDemoApplication.RegisterReport(aName: String;
+  AClasss: TReportDemoAppClass);
+begin
+  If Reports=Nil then
+    begin
+    Reports:=TStringList.Create;
+    TStringList(Reports).Duplicates:=dupError;
+    TStringList(Reports).Sorted:=True;
+    TStringList(Reports).OwnsObjects:=True;
+    end;
+  Reports.AddObject(AName,TReportDef.Create(AClasss));
+end;
+
+class procedure TReportDemoApplication.GetRegisteredReports(aList: TStrings);
+begin
+  aList.Assign(reports);
+end;
+
+Var
+  Demo : String;
+
+Function GetReportAppName : string;
+
+begin
+  Result:='fpreportdemo';
+  if (demo<>'') then
+    Result:=Result+'-'+demo;
+end;
+
+procedure TReportDemoApplication.DoRun;
+
+Var
+  D,F,S,J : String;
+  Fmt : TRenderFormat;
+
+begin
+  OnGetApplicationName:=@GetReportAppName;
+  S:=CheckOptions('lj::hf:r:d:',['list','json::','help','format:','runtime:','demo:']);
+  if (S<>'') or HasOption('h','help') then
+    Usage(S);
+  if HasOption('l','list') then
+    begin
+    ListReports;
+    Terminate;
+    exit;
+    end;
+  FRunner.RunFileName:=GetoptionValue('r','runtime');
+  D:=GetOptionValue('d','demo');
+  if (D='') then
+    Usage('Need demo name');
+  Demo:=D;
+  if HasOption('j','json') then
+    begin
+    J:=GetOptionValue('j','json');
+    if J='' then
+      J:=ChangeFileExt(Paramstr(0),'.json');
+    end;
+  F:=GetOptionValue('f','format');
+  Fmt:=High(TRenderFormat);
+  While (Fmt>rfDefault) and (CompareText(FormatName(Fmt),F)<>0) do
+    Fmt:=Pred(Fmt);
+  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.Format:=Fmt;
+  FRunner.DesignFileName:=J;
+  FRunner.Execute;
+
+  Terminate;
+end;
+
+end.
+

+ 77 - 0
packages/fcl-report/demos/webdemo.lpi

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="webdemo"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="fclfpreport"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="FCL"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="4">
+      <Unit0>
+        <Filename Value="webdemo.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="wmreports.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="regreports.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="udapp.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit3>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="webdemo"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="polygon;../units/$(TargetCPU)-$(TargetOS)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 16 - 0
packages/fcl-report/demos/webdemo.pp

@@ -0,0 +1,16 @@
+program webdemo;
+
+{$mode objfpc}{$H+}
+
+uses
+  fphttpapp, regreports, wmreports;
+
+begin
+  Application.Port:=8080;
+  Application.AllowDefaultModule:=True;
+  Application.DefaultModuleName:='Page';
+  Application.PreferModuleName:=True;
+  Application.Initialize;
+  Application.Run;
+end.
+

+ 121 - 0
packages/fcl-report/demos/wmreports.pp

@@ -0,0 +1,121 @@
+unit wmreports;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, httpdefs, fphttp, fpweb, fpreport;
+
+Type
+
+  { TGenerateReportModule }
+
+  TGenerateReportModule = class(TCustomHTTPModule)
+  Public
+    Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); override;
+  end;
+
+  { TPageReportModule }
+
+  TPageReportModule = class(TCustomHTTPModule)
+  Public
+    Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); override;
+  end;
+
+  { TViewReportModule }
+
+  TViewReportModule = class(TCustomHTTPModule)
+  Public
+    Procedure HandleRequest(ARequest: TRequest; AResponse: TResponse); override;
+  end;
+
+implementation
+
+uses udapp;
+
+{ TViewReportModule }
+
+procedure TViewReportModule.HandleRequest(ARequest: TRequest;
+  AResponse: TResponse);
+
+begin
+end;
+
+{ TGenerateReportModule }
+
+procedure TGenerateReportModule.HandleRequest(ARequest: TRequest;
+  AResponse: TResponse);
+Var
+  F,D : String;
+  FRPT : TReportDemoApp;
+  Fmt : TRenderFormat;
+  FRunner : TReportRunner;
+
+begin
+  D:=ARequest.ContentFields.Values['demo'];
+  if (D='') or (TReportDemoApplication.GetReportClass(D)=Nil) then
+    Raise Exception.CreateFmt('Invalid or empty demo name : "%s"',[D]);
+  F:=ARequest.ContentFields.Values['format'];
+  Fmt:=High(TRenderFormat);
+  While (fmt>rfDefault) and (CompareText(TReportDemoApplication.FormatName(fmt),F)<>0) do
+    fmt:=Pred(fmt);
+  if (fmt=rfDefault) then
+    Raise Exception.CreateFmt('Invalid or empty format name : "%s"',[F]);
+  FRunner:=TReportRunner.Create(Self);
+  FRunner.ReportApp:=TReportDemoApplication.GetReportClass(D).Create(Self);
+  FRunner.ReportApp.rpt:=TFPReport.Create(FRunner.ReportApp);
+  FRunner.Format:=Fmt;
+  FRunner.Execute;
+end;
+
+{ TPageReportModule }
+
+procedure TPageReportModule.HandleRequest(ARequest: TRequest;
+  AResponse: TResponse);
+
+Var
+  L,RL : TStrings;
+  I : Integer;
+  F : TRenderFormat;
+
+begin
+  RL:=Nil;
+  L:=TStringList.Create;
+  try
+    RL:=TStringList.Create;
+    L.Add('<HTML><HEAD><TITLE>FPReport web demo</TITLE></HEAD>');
+    L.Add('<BODY>');
+    L.Add('<H1>Select report and output type</H1>');
+    L.Add('<FORM ACTION="../Generate" METHOD=POST>');
+    L.Add('Report: ');
+    L.Add('<SELECT NAME="demo">');
+    //
+    TReportDemoApplication.GetRegisteredReports(RL);
+    For I:=0 to RL.Count-1 do
+      L.Add('<OPTION>'+RL[i]+'</option>');
+    L.Add('</SELECT>');
+    L.Add('</p>');
+    L.Add('Format: ');
+    L.Add('<SELECT NAME="format">');
+    for F in TRenderFormat do
+      if TReportDemoApplication.GetRenderClass(F)<>Nil then
+      L.Add('<OPTION>'+TReportDemoApplication.FormatName(F)+'</option>');
+    L.Add('</SELECT>');
+    L.Add('</p>');
+    L.Add('<INPUT TYPE="Submit" Value="Generate"/>');
+    L.Add('</FORM>');
+    L.Add('</BODY>');
+    L.Add('</HTML>');
+    AResponse.Content:=L.Text;
+  finally
+    L.Free;
+  end;
+end;
+
+initialization
+  TPageReportModule.RegisterModule('Page',True);
+  TGenerateReportModule.RegisterModule('Generate',True);
+  TViewReportModule.RegisterModule('View',True);
+end.
+

+ 120 - 0
packages/fcl-report/fpmake.pp

@@ -0,0 +1,120 @@
+{$ifndef ALLPACKAGES}
+{$mode objfpc}{$H+}
+program fpmake;
+
+uses fpmkunit;
+
+Var
+  T : TTarget;
+  P : TPackage;
+begin
+  With Installer do
+    begin
+{$endif ALLPACKAGES}
+
+    P:=AddPackage('fcl-report');
+    P.ShortName:='fpreport';
+{$ifdef ALLPACKAGES}
+    P.Directory:=ADirectory;
+{$endif ALLPACKAGES}
+    P.Version:='3.1.1';
+    P.Dependencies.Add('fcl-base');
+    P.Dependencies.Add('fcl-image');
+    P.Dependencies.Add('fcl-xml');
+    P.Dependencies.Add('fcl-pdf');
+    P.Dependencies.Add('fcl-json');
+    P.Author := 'Michael Van Canneyt';
+    P.License := 'LGPL with modification, ';
+    P.HomepageURL := 'www.freepascal.org';
+    P.Email := '';
+    P.Description := 'GUI-independent Reporting Engine';
+    P.NeedLibC:= false;
+    P.OSes:=[linux, win32, darwin, freebsd];
+    P.SourcePath.Add('src');
+{$IFDEF VER2_6}    
+    T:=P.Targets.AddUnit('fprepexprpars.pp');
+    T.ResourceStrings := True;
+{$ENDIF}
+    T:=P.Targets.AddUnit('fpreportstreamer.pp');
+    T.ResourceStrings := True;
+    
+    T:=P.Targets.AddUnit('fpreporthtmlparser.pp');
+   
+    T:=P.Targets.AddUnit('fpreport.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      begin
+      AddUnit('fpreportstreamer');
+      AddUnit('fpreporthtmlparser');
+      end;
+      
+    T:=P.Targets.AddUnit('fpjsonreport.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+      
+    T:=P.Targets.AddUnit('fpreportjson.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      begin
+      AddUnit('fpreportstreamer');
+      AddUnit('fpreport');
+      end;
+    {  
+    T:=P.Targets.AddUnit('fpreportdom.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      begin
+      AddUnit('fpreportstreamer');
+      AddUnit('fpreport');
+      end;
+    }
+    T:=P.Targets.AddUnit('fpreportdb.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+
+    T:=P.Targets.AddUnit('fpextfuncs.pp');
+    with T.Dependencies do
+      AddUnit('fpreport');
+
+    T:=P.Targets.AddUnit('fpreportcontnr.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+
+    T:=P.Targets.AddUnit('fpreportcanvashelper.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+      
+    T:=P.Targets.AddUnit('fpreporthtmlutil.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+
+    T:=P.Targets.AddUnit('fpreportpdfexport.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      AddUnit('fpreport');
+
+    T:=P.Targets.AddUnit('fpreporthtmlexport.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      begin
+      AddUnit('fpreport');
+      AddUnit('fpreporthtmlutil');
+      end;
+
+    T:=P.Targets.AddUnit('fpreportfpimageexport.pp');
+    T.ResourceStrings := True;
+    with T.Dependencies do
+      begin
+      AddUnit('fpreport');
+      AddUnit('fpreporthtmlutil');
+      end;
+{$ifndef ALLPACKAGES}
+    Run;
+    end;
+end.
+{$endif ALLPACKAGES}

+ 190 - 0
packages/fcl-report/src/fpextfuncs.pp

@@ -0,0 +1,190 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2016 Michael Van Canneyt,
+    member of the Free Pascal development team
+
+    FPReport exra functions for FPC expression parser.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpExtFuncs;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  StrUtils,
+  DateUtils,
+  fpexprpars;
+
+
+procedure RegisterExtraFunctions;
+
+implementation
+
+
+procedure BuiltInCurrentDate(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := FormatDateTime(Args[0].ResString, Date);
+end;
+
+procedure BuiltInCurrentTime(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := FormatDateTime(Args[0].ResString, Time);
+end;
+
+procedure BuiltInCurrentDateTime(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := FormatDateTime(Args[0].ResString, Now);
+end;
+
+procedure BuiltInScanDateTime(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResDateTime := ScanDateTime(Args[0].ResString, Args[1].ResString, Args[2].ResInteger);
+end;
+
+procedure BuiltInMonthOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := MonthOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInDayOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := DayOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInYearOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := YearOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInHourOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := HourOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInMinuteOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := MinuteOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInSecondOf(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := SecondOf(Args[0].ResDateTime);
+end;
+
+procedure BuiltInFormatFloat(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := FormatFloat(Args[0].ResString, Args[1].ResFloat);
+end;
+
+{ For example:  Memo.Text := '[IntToStrEx(1234,''%4.4x (%0:d)'')]'; }
+procedure BuiltInIntToStrEx(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := Format(Args[1].ResString, [Args[0].ResInteger]);
+end;
+
+{ First letter uppercased, rest untouched. }
+procedure BuiltInPrettyCase(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+var
+  sBuffer: string;
+begin
+  sBuffer := Args[0].ResString;
+  Result.ResString := UpperCase(Copy(sBuffer, 1, 1)) + Copy(sBuffer, 2, Length(sBuffer)-1);
+end;
+
+procedure BuiltInInsert(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+var
+  s: string;
+begin
+  s := Args[1].ResString;
+  Insert(Args[0].ResString, s, Args[2].ResInteger);
+  Result.ResString := s;
+end;
+
+procedure BuiltInRPos(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := RPos(Args[0].ResString, Args[1].ResString);
+end;
+
+procedure BuiltInPosEx(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResInteger := PosEx(Args[0].ResString, Args[1].ResString, Args[2].ResInteger);
+end;
+
+procedure BuiltInPadLeft(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := PadLeft(Args[0].ResString, Args[1].ResInteger);
+end;
+
+procedure BuiltInPadRight(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := PadRight(Args[0].ResString, Args[1].ResInteger);
+end;
+
+procedure BuiltInPadCenter(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := PadCenter(Args[0].ResString, Args[1].ResInteger);
+end;
+
+procedure BuiltInExtractWord(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+begin
+  Result.ResString := ExtractWord(Args[0].ResInteger, Args[1].ResString, StdWordDelims);
+end;
+
+{ Extract Nth word. Use chars from string parameter as word separators. }
+procedure BuiltInExtractWordEx(var Result : TFPExpressionResult; const Args : TExprParameterArray);
+var
+  a: TSysCharSet;
+  i: integer;
+  s: string;
+begin
+  a := [];
+  s := Args[2].ResString;
+  for i := 1 to Length(s) do
+    a := a + [s[i]];
+  Result.ResString := ExtractWord(Args[0].ResInteger, Args[1].ResString, a);
+end;
+
+
+procedure RegisterExtraFunctions;
+begin
+  with BuiltinIdentifiers do
+  begin
+    AddFunction(bcDateTime, 'CurrentDate', 'S', 'S', @BuiltInCurrentDate);
+    AddFunction(bcDateTime, 'CurrentTime', 'S', 'S', @BuiltInCurrentTime);
+    AddFunction(bcDateTime, 'CurrentDateTime', 'S', 'S', @BuiltInCurrentDateTime);
+    AddFunction(bcDateTime, 'ScanDateTime', 'D', 'SSI', @BuiltInScanDateTime);
+    AddFunction(bcDateTime, 'MonthOf', 'I', 'D', @BuiltInMonthOf);
+    AddFunction(bcDateTime, 'DayOf', 'I', 'D', @BuiltInDayOf);
+    AddFunction(bcDateTime, 'YearOf', 'I', 'D', @BuiltInYearOf);
+    AddFunction(bcDateTime, 'HourOf', 'I', 'D', @BuiltInHourOf);
+    AddFunction(bcDateTime, 'MinuteOf', 'I', 'D', @BuiltInMinuteOf);
+    AddFunction(bcDateTime, 'SecondOf', 'I', 'D', @BuiltInSecondOf);
+    {$IFNDEF VER3}
+    // AddFunction(bcConversion, 'FormatFloat', 'S', 'SF', @BuiltInFormatFloat);
+    {$ENDIF VER3}
+    AddFunction(bcConversion, 'IntToStrEx', 'S', 'IS', @BuiltInIntToStrEx);
+    AddFunction(bcStrings, 'PrettyCase', 'S', 'S', @BuiltInPrettyCase);
+    AddFunction(bcStrings, 'Insert', 'S', 'SSI', @BuiltInInsert);
+    AddFunction(bcStrings, 'RPos', 'I', 'SS', @BuiltInRPos);
+    AddFunction(bcStrings, 'PosEx', 'I', 'SSI', @BuiltInPosEx);
+    AddFunction(bcStrings, 'PadLeft', 'S', 'SI', @BuiltInPadLeft);
+    AddFunction(bcStrings, 'PadRight', 'S', 'SI', @BuiltInPadRight);
+    AddFunction(bcStrings, 'PadCenter', 'S', 'SI', @BuiltInPadCenter);
+    AddFunction(bcStrings, 'ExtractWord', 'S', 'IS', @BuiltInExtractWord);
+    AddFunction(bcStrings, 'ExtractWordEx', 'S', 'ISS', @BuiltInExtractWordEx);
+  end;
+end;
+
+end.
+

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

@@ -0,0 +1,205 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team
+
+    TFPReport descendent that stores it's design in a JSON structure. 
+    Can be used in an IDE
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpjsonreport;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpreport, fpjson, fpreportstreamer;
+
+Type
+
+  { TFPJSONReport }
+
+  TFPJSONReport = class(TFPReport)
+  private
+    FDesignTimeJSON: TJSONObject;
+    procedure ReadReportJSON(Reader: TReader);
+    procedure WriteReportJSON(Writer: TWriter);
+  Protected
+    Procedure DefineProperties(Filer: TFiler); override;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    destructor Destroy; override;
+    procedure LoadFromStream(const aStream: TStream);
+    procedure SaveToStream(const aStream: TStream);
+    Procedure LoadFromJSON(aJSON : TJSONObject); virtual;
+    Procedure SavetoJSON(aJSON : TJSONObject); virtual;
+    Procedure LoadFromFile(const aFileName : String);
+    Procedure SaveToFile(const aFileName : String);
+    Property DesignTimeJSON : TJSONObject Read FDesignTimeJSON;
+  end;
+
+implementation
+
+Resourcestring
+  SErrInvalidJSONData = 'Invalid JSON Data';
+  SErrFailedToLoad = 'Failed to load report: %s';
+
+{ TFPJSONReport }
+
+procedure TFPJSONReport.ReadReportJSON(Reader: TReader);
+
+Var
+  S : UnicodeString;
+  D : TJSONData;
+
+begin
+  FDesignTimeJSON.Clear;
+  S:=Reader.ReadUnicodeString;
+  if (S<>'') then
+    begin
+    D:=GetJSON(UTF8Encode(S),True);
+    if D is TJSONObject then
+      begin
+      FreeAndNil(FDesignTimeJSON);
+      FDesignTimeJSON:=D as TJSONObject
+      end
+    else
+      begin
+      D.Free;
+      FDesignTimeJSON:=TJSONObject.Create;
+      Raise EReportError.CreateFmt(SErrFailedToLoad,[SErrInvalidJSONData]);
+      end;
+    end;
+end;
+
+procedure TFPJSONReport.WriteReportJSON(Writer: TWriter);
+
+Var
+  S : UnicodeString;
+
+begin
+  S:='';
+  if (FDesignTimeJSON.Count>0) then
+    S:=UTF8Decode(FDesignTimeJSON.AsJSON);
+  Writer.WriteUnicodeString(S);
+end;
+
+procedure TFPJSONReport.DefineProperties(Filer: TFiler);
+begin
+  inherited DefineProperties(Filer);
+  Filer.DefineProperty('ReportJSON',@ReadReportJSON,@WriteReportJSON,Assigned(FDesignTimeJSON) and (FDesignTimeJSON.Count>0));
+end;
+
+constructor TFPJSONReport.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDesignTimeJSON:=TJSONObject.Create;
+end;
+
+destructor TFPJSONReport.Destroy;
+begin
+  FreeAndNil(FDesignTimeJSON);
+  inherited Destroy;
+end;
+
+procedure TFPJSONReport.LoadFromJSON(aJSON: TJSONObject);
+
+Var
+  R : TFPReportJSONStreamer;
+
+begin
+  R:=TFPReportJSONStreamer.Create(Nil);
+  try
+    R.OwnsJSON:=False;
+    R.JSON:=aJSON;
+    ReadElement(R);
+  finally
+    R.Free;
+  end;
+end;
+
+procedure TFPJSONReport.SavetoJSON(aJSON: TJSONObject);
+
+Var
+  R : TFPReportJSONStreamer;
+
+begin
+  R:=TFPReportJSONStreamer.Create(Nil);
+  try
+    R.OwnsJSON:=False;
+    R.JSON:=aJSON;
+    WriteElement(R);
+  finally
+    R.Free;
+  end;
+end;
+
+procedure TFPJSONReport.LoadFromStream(const aStream : TStream);
+
+Var
+  D : TJSONData;
+
+begin
+  D:=GetJSON(aStream);
+  try
+    if not (D is TJSONObject) then
+      Raise EReportError.CreateFmt(SErrFailedToLoad,[SErrInvalidJSONData]);
+    LoadFromJSON(D as TJSONObject);
+  finally
+    D.Free;
+  end;
+end;
+
+procedure TFPJSONReport.SaveToStream(const aStream: TStream);
+
+Var
+  O : TJSONObject;
+  S : TJSONStringType;
+
+begin
+  O:=TJSONObject.Create;
+  try
+    SaveToJSON(O);
+    S:=O.AsJSON;
+    aStream.WriteBuffer(S[1],Length(S));
+  finally
+    O.Free;
+  end;
+end;
+
+procedure TFPJSONReport.LoadFromFile(const aFileName: String);
+
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    LoadFromStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TFPJSONReport.SaveToFile(const aFileName: String);
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmCreate);
+  try
+    SaveToStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+end.
+

+ 3966 - 0
packages/fcl-report/src/fprepexprpars.pp

@@ -0,0 +1,3966 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2008 Michael Van Canneyt.
+
+    Expression parser, supports variables, functions and
+    float/integer/string/boolean/datetime operations.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{$mode objfpc}
+{$h+}
+unit fprepexprpars;
+
+interface
+
+uses
+  Classes, SysUtils, contnrs;
+
+Type
+  // tokens
+  TTokenType = (ttPlus, ttMinus, ttLessThan, ttLargerThan, ttEqual, ttDiv,
+                ttMul, ttLeft, ttRight, ttLessThanEqual, ttLargerThanEqual,
+                ttunequal, ttNumber, ttString, ttIdentifier,
+                ttComma, ttand, ttOr,ttXor,ttTrue,ttFalse,ttnot,ttif,
+                ttCase,ttEOF);
+
+  TExprFloat = Double;
+
+Const
+  ttDelimiters = [ttPlus, ttMinus, ttLessThan, ttLargerThan, ttEqual, ttDiv,
+                  ttMul, ttLeft, ttRight, ttLessThanEqual, ttLargerThanEqual,
+                  ttunequal];
+  ttComparisons = [ttLargerThan,ttLessthan,
+                   ttLargerThanEqual,ttLessthanEqual,
+                   ttEqual,ttUnequal];
+
+Type
+
+  TFPExpressionParser = Class;
+  TExprBuiltInManager = Class;
+  TFPExprFunction = Class;
+  TFPExprFunctionClass = Class of TFPExprFunction;
+
+  { TFPExpressionScanner }
+
+  TFPExpressionScanner = Class(TObject)
+    FSource : String;
+    LSource,
+    FPos : Integer;
+    FChar : PChar;
+    FToken : String;
+    FTokenType : TTokenType;
+  private
+    function GetCurrentChar: Char;
+    procedure ScanError(Msg: String);
+  protected
+    procedure SetSource(const AValue: String); virtual;
+    function DoIdentifier: TTokenType;
+    function DoNumber: TTokenType;
+    function DoDelimiter: TTokenType;
+    function DoString: TTokenType;
+    Function NextPos : Char; // inline;
+    procedure SkipWhiteSpace; // inline;
+    function IsWordDelim(C : Char) : Boolean; // inline;
+    function IsDelim(C : Char) : Boolean; // inline;
+    function IsDigit(C : Char) : Boolean; // inline;
+    function IsAlpha(C : Char) : Boolean; // inline;
+  public
+    Constructor Create;
+    Function GetToken : TTokenType;
+    Property Token : String Read FToken;
+    Property TokenType : TTokenType Read FTokenType;
+    Property Source : String Read FSource Write SetSource;
+    Property Pos : Integer Read FPos;
+    Property CurrentChar : Char Read GetCurrentChar;
+  end;
+
+  EExprScanner = Class(Exception);
+
+  TResultType = (rtBoolean,rtInteger,rtFloat,rtDateTime,rtString);
+  TResultTypes = set of TResultType;
+
+  TFPExpressionResult = record
+    ResString   : String;
+    Case ResultType : TResultType of
+      rtBoolean  : (ResBoolean  : Boolean);
+      rtInteger  : (ResInteger  : Int64);
+      rtFloat    : (ResFloat    : TExprFloat);
+      rtDateTime : (ResDateTime : TDatetime);
+      rtString   : ();
+  end;
+  PFPExpressionResult = ^TFPExpressionResult;
+  TExprParameterArray = Array of TFPExpressionResult;
+
+  { TFPExprNode }
+
+  TFPExprNode = Class(TObject)
+  Protected
+    Procedure CheckNodeType(Anode : TFPExprNode; Allowed : TResultTypes);
+    // A procedure with var saves an implicit try/finally in each node
+    // A marked difference in execution speed.
+    Procedure GetNodeValue(var Result : TFPExpressionResult); virtual; abstract;
+  Public
+    Procedure Check; virtual; abstract;
+    Procedure InitAggregate; virtual;
+    Procedure UpdateAggregate; virtual;
+    Class Function IsAggregate : Boolean; virtual;
+    Function HasAggregate : Boolean; virtual;
+    Function NodeType : TResultType; virtual; abstract;
+    Function NodeValue : TFPExpressionResult;
+    Function AsString : string; virtual; abstract;
+  end;
+  TExprArgumentArray = Array of TFPExprNode;
+
+  { TFPBinaryOperation }
+
+  TFPBinaryOperation = Class(TFPExprNode)
+  private
+    FLeft: TFPExprNode;
+    FRight: TFPExprNode;
+  Protected
+    Procedure CheckSameNodeTypes;
+  Public
+    Constructor Create(ALeft,ARight : TFPExprNode);
+    Destructor Destroy; override;
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+    Function HasAggregate : Boolean; override;
+    Procedure Check; override;
+    Property left : TFPExprNode Read FLeft;
+    Property Right : TFPExprNode Read FRight;
+  end;
+  TFPBinaryOperationClass = Class of TFPBinaryOperation;
+
+
+  { TFPBooleanOperation }
+
+  TFPBooleanOperation = Class(TFPBinaryOperation)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+  { TFPBinaryAndOperation }
+
+  TFPBinaryAndOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBinaryOrOperation }
+
+  TFPBinaryOrOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBinaryXOrOperation }
+
+  TFPBinaryXOrOperation = Class(TFPBooleanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPBooleanResultOperation }
+
+  TFPBooleanResultOperation = Class(TFPBinaryOperation)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+  TFPBooleanResultOperationClass = Class of TFPBooleanResultOperation;
+
+
+  { TFPEqualOperation }
+
+  TFPEqualOperation = Class(TFPBooleanResultOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPUnequalOperation }
+
+  TFPUnequalOperation = Class(TFPEqualOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPOrderingOperation }
+
+  TFPOrderingOperation = Class(TFPBooleanResultOperation)
+  Public
+    Procedure Check; override;
+  end;
+
+  { TFPLessThanOperation }
+
+  TFPLessThanOperation = Class(TFPOrderingOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPGreaterThanOperation }
+
+  TFPGreaterThanOperation = Class(TFPOrderingOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPLessThanEqualOperation }
+
+  TFPLessThanEqualOperation = Class(TFPGreaterThanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+
+  { TFPGreaterThanEqualOperation }
+
+  TFPGreaterThanEqualOperation = Class(TFPLessThanOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TIfOperation }
+
+  TIfOperation = Class(TFPBinaryOperation)
+  private
+    FCondition: TFPExprNode;
+  protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Procedure Check; override;
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+    Function HasAggregate : Boolean; override;
+    Function NodeType : TResultType; override;
+    Constructor Create(ACondition,ALeft,ARight : TFPExprNode);
+    Destructor destroy; override;
+    Function AsString : string ; override;
+    Property Condition : TFPExprNode Read FCondition;
+  end;
+
+  { TCaseOperation }
+
+  TCaseOperation = Class(TFPExprNode)
+  private
+    FArgs : TExprArgumentArray;
+    FCondition: TFPExprNode;
+  protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Procedure Check; override;
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+    function HasAggregate: Boolean; override;
+    Function NodeType : TResultType; override;
+    Constructor Create(Args : TExprArgumentArray);
+    Destructor destroy; override;
+    Function AsString : string ; override;
+    Property Condition : TFPExprNode Read FCondition;
+  end;
+
+  { TMathOperation }
+
+  TMathOperation = Class(TFPBinaryOperation)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType; override;
+  end;
+
+  { TFPAddOperation }
+
+  TFPAddOperation = Class(TMathOperation)
+  Protected
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  Public
+    Function AsString : string ; override;
+  end;
+
+  { TFPSubtractOperation }
+
+  TFPSubtractOperation = Class(TMathOperation)
+  Public
+    Procedure Check; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+    Function AsString : string ; override;
+  end;
+
+  { TFPMultiplyOperation }
+
+  TFPMultiplyOperation = Class(TMathOperation)
+  Public
+    Procedure check; override;
+    Function AsString : string ; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  end;
+
+  { TFPDivideOperation }
+
+  TFPDivideOperation = Class(TMathOperation)
+  Public
+    Procedure Check; override;
+    Function AsString : string ; override;
+    Function NodeType : TResultType; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+  end;
+
+  { TFPUnaryOperator }
+
+  TFPUnaryOperator = Class(TFPExprNode)
+  private
+    FOperand: TFPExprNode;
+  Public
+    Constructor Create(AOperand : TFPExprNode);
+    Destructor Destroy; override;
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+    Function HasAggregate : Boolean; override;
+    Procedure Check; override;
+    Property Operand : TFPExprNode Read FOperand;
+  end;
+
+  { TFPConvertNode }
+
+  TFPConvertNode = Class(TFPUnaryOperator)
+    Function AsString : String; override;
+  end;
+
+  { TFPNotNode }
+
+  TFPNotNode = Class(TFPUnaryOperator)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : String; override;
+  end;
+
+  TIntConvertNode = Class(TFPConvertNode)
+  Public
+    Procedure Check; override;
+  end;
+
+  { TIntToFloatNode }
+  TIntToFloatNode = Class(TIntConvertNode)
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TIntToDateTimeNode }
+
+  TIntToDateTimeNode = Class(TIntConvertNode)
+  Public
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TFloatToDateTimeNode }
+
+  TFloatToDateTimeNode = Class(TFPConvertNode)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TFPNegateOperation }
+
+  TFPNegateOperation = Class(TFPUnaryOperator)
+  Public
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : String; override;
+  end;
+
+  { TFPConstExpression }
+
+  TFPConstExpression = Class(TFPExprnode)
+  private
+    FValue : TFPExpressionResult;
+  public
+    Constructor CreateString(AValue : String);
+    Constructor CreateInteger(AValue : Int64);
+    Constructor CreateDateTime(AValue : TDateTime);
+    Constructor CreateFloat(AValue : TExprFloat);
+    Constructor CreateBoolean(AValue : Boolean);
+    Procedure Check; override;
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Function AsString : string ; override;
+   // For inspection
+    Property ConstValue : TFPExpressionResult read FValue;
+  end;
+
+
+  TIdentifierType = (itVariable,itFunctionCallBack,itFunctionHandler,itFunctionNode);
+  TFPExprFunctionCallBack = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+  TFPExprFunctionEvent = Procedure (Var Result : TFPExpressionResult; Const Args : TExprParameterArray) of object;
+  TFPExprVariableCallBack = Procedure (Var Result : TFPExpressionResult; ConstRef AName : ShortString);
+  TFPExprVariableEvent = Procedure (Var Result : TFPExpressionResult; ConstRef AName : ShortString) of Object;
+
+  { TFPExprIdentifierDef }
+
+  TFPExprIdentifierDef = Class(TCollectionItem)
+  private
+    FNodeType: TFPExprFunctionClass;
+    FOnGetVarValue: TFPExprVariableEvent;
+    FOnGetVarValueCB: TFPExprVariableCallBack;
+    FStringValue : String;
+    FValue : TFPExpressionResult;
+    FArgumentTypes: String;
+    FIDType: TIdentifierType;
+    FName: ShortString;
+    FOnGetValue: TFPExprFunctionEvent;
+    FOnGetValueCB: TFPExprFunctionCallBack;
+    function GetAsBoolean: Boolean;
+    function GetAsDateTime: TDateTime;
+    function GetAsFloat: TExprFloat;
+    function GetAsInteger: Int64;
+    function GetAsString: String;
+    function GetResultType: TResultType;
+    function GetValue: String;
+    procedure SetArgumentTypes(const AValue: String);
+    procedure SetAsBoolean(const AValue: Boolean);
+    procedure SetAsDateTime(const AValue: TDateTime);
+    procedure SetAsFloat(const AValue: TExprFloat);
+    procedure SetAsInteger(const AValue: Int64);
+    procedure SetAsString(const AValue: String);
+    procedure SetName(const AValue: ShortString);
+    procedure SetResultType(const AValue: TResultType);
+    procedure SetValue(const AValue: String);
+  Protected
+    Procedure CheckResultType(Const AType : TResultType);
+    Procedure CheckVariable;
+    Procedure FetchValue;
+  Public
+    Function ArgumentCount : Integer;
+    Procedure Assign(Source : TPersistent); override;
+    Function EventBasedVariable : Boolean; Inline;
+    Property AsFloat : TExprFloat Read GetAsFloat Write SetAsFloat;
+    Property AsInteger : Int64 Read GetAsInteger Write SetAsInteger;
+    Property AsString : String Read GetAsString Write SetAsString;
+    Property AsBoolean : Boolean Read GetAsBoolean Write SetAsBoolean;
+    Property AsDateTime : TDateTime Read GetAsDateTime Write SetAsDateTime;
+    Property OnGetFunctionValueCallBack : TFPExprFunctionCallBack Read FOnGetValueCB Write FOnGetValueCB;
+    Property OnGetVariableValueCallBack : TFPExprVariableCallBack Read FOnGetVarValueCB Write FOnGetVarValueCB;
+  Published
+    Property IdentifierType : TIdentifierType Read FIDType Write FIDType;
+    Property Name : ShortString Read FName Write SetName;
+    Property Value : String Read GetValue Write SetValue;
+    Property ParameterTypes : String Read FArgumentTypes Write SetArgumentTypes;
+    Property ResultType : TResultType Read GetResultType Write SetResultType;
+    Property OnGetFunctionValue : TFPExprFunctionEvent Read FOnGetValue Write FOnGetValue;
+    Property OnGetVariableValue : TFPExprVariableEvent Read FOnGetVarValue Write FOnGetVarValue;
+    Property NodeType : TFPExprFunctionClass Read FNodeType Write FNodeType;
+  end;
+
+
+  TBuiltInCategory = (bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser,bcAggregate);
+  TBuiltInCategories = Set of TBuiltInCategory;
+
+  { TFPBuiltInExprIdentifierDef }
+
+  TFPBuiltInExprIdentifierDef = Class(TFPExprIdentifierDef)
+  private
+    FCategory: TBuiltInCategory;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    Property Category : TBuiltInCategory Read FCategory Write FCategory;
+  end;
+
+  { TFPExprIdentifierDefs }
+
+  TFPExprIdentifierDefs = Class(TCollection)
+  private
+    FParser: TFPExpressionParser;
+    function GetI(AIndex : Integer): TFPExprIdentifierDef;
+    procedure SetI(AIndex : Integer; const AValue: TFPExprIdentifierDef);
+  Protected
+    procedure Update(Item: TCollectionItem); override;
+    Property Parser: TFPExpressionParser Read FParser;
+  Public
+    Function IndexOfIdentifier(Const AName : ShortString) : Integer;
+    Function FindIdentifier(Const AName : ShortString) : TFPExprIdentifierDef;
+    Function IdentifierByName(Const AName : ShortString) : TFPExprIdentifierDef;
+    Function AddVariable(Const AName : ShortString; AResultType : TResultType; ACallback : TFPExprVariableCallBack) : TFPExprIdentifierDef;
+    Function AddVariable(Const AName : ShortString; AResultType : TResultType; ACallback : TFPExprVariableEvent) : TFPExprIdentifierDef;
+    Function AddVariable(Const AName : ShortString; AResultType : TResultType; AValue : String) : TFPExprIdentifierDef;
+    Function AddBooleanVariable(Const AName : ShortString; AValue : Boolean) : TFPExprIdentifierDef;
+    Function AddIntegerVariable(Const AName : ShortString; AValue : Integer) : TFPExprIdentifierDef;
+    Function AddFloatVariable(Const AName : ShortString; AValue : TExprFloat) : TFPExprIdentifierDef;
+    Function AddStringVariable(Const AName : ShortString; AValue : String) : TFPExprIdentifierDef;
+    Function AddDateTimeVariable(Const AName : ShortString; AValue : TDateTime) : TFPExprIdentifierDef;
+    Function AddFunction(Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionCallBack) : TFPExprIdentifierDef;
+    Function AddFunction(Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionEvent) : TFPExprIdentifierDef;
+    Function AddFunction(Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ANodeClass : TFPExprFunctionClass) : TFPExprIdentifierDef;
+    property Identifiers[AIndex : Integer] : TFPExprIdentifierDef Read GetI Write SetI; Default;
+  end;
+
+  { TFPExprIdentifierNode }
+
+  TFPExprIdentifierNode = Class(TFPExprNode)
+  Private
+    FID : TFPExprIdentifierDef;
+    PResult : PFPExpressionResult;
+    FResultType : TResultType;
+  public
+    Constructor CreateIdentifier(AID : TFPExprIdentifierDef);
+    Function NodeType : TResultType;  override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Property Identifier : TFPExprIdentifierDef Read FID;
+  end;
+
+  { TFPExprVariable }
+
+  TFPExprVariable = Class(TFPExprIdentifierNode)
+    Procedure Check; override;
+    function AsString: string; override;
+  end;
+
+  { TFPExprFunction }
+
+  TFPExprFunction = Class(TFPExprIdentifierNode)
+  private
+    FArgumentNodes : TExprArgumentArray;
+    FargumentParams : TExprParameterArray;
+  Protected
+    Procedure CalcParams;
+  Public
+    Procedure Check; override;
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); virtual;
+    Destructor Destroy; override;
+    Property ArgumentNodes : TExprArgumentArray Read FArgumentNodes;
+    Property ArgumentParams : TExprParameterArray Read FArgumentParams;
+    Function AsString : String; override;
+  end;
+
+  { TAggregateExpr }
+
+  TAggregateExpr = Class(TFPExprFunction)
+  Protected
+    FResult : TFPExpressionResult;
+  Public
+    Class Function IsAggregate : Boolean; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TAggregateMin }
+
+  TAggregateMin = Class(TAggregateExpr)
+  Public
+    FFirst: Boolean;
+  Public
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+  end;
+
+  { TAggregateMax }
+
+  TAggregateMax = Class(TAggregateExpr)
+  Public
+    FFirst: Boolean;
+  Public
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+  end;
+
+  { TAggregateSum }
+
+  TAggregateSum = Class(TAggregateExpr)
+  Public
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+  end;
+
+  { TAggregateAvg }
+
+  TAggregateAvg = Class(TAggregateSum)
+  Protected
+    FCount : Integer;
+  Public
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+  end;
+
+  { TAggregateCount }
+
+  TAggregateCount = Class(TAggregateExpr)
+  Public
+    Procedure InitAggregate; override;
+    Procedure UpdateAggregate; override;
+  end;
+
+  { TFPFunctionCallBack }
+
+  TFPFunctionCallBack = Class(TFPExprFunction)
+  Private
+    FCallBack : TFPExprFunctionCallBack;
+  Public
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult);  override;
+    Property CallBack : TFPExprFunctionCallBack Read FCallBack;
+  end;
+
+  { TFPFunctionEventHandler }
+
+  TFPFunctionEventHandler = Class(TFPExprFunction)
+  Private
+    FCallBack : TFPExprFunctionEvent;
+  Public
+    Constructor CreateFunction(AID : TFPExprIdentifierDef; Const Args : TExprArgumentArray); override;
+    Procedure GetNodeValue(var Result : TFPExpressionResult); override;
+    Property CallBack : TFPExprFunctionEvent Read FCallBack;
+  end;
+
+  { TFPExpressionParser }
+
+  TFPExpressionParser = class(TComponent)
+  private
+    FBuiltIns: TBuiltInCategories;
+    FExpression: String;
+    FScanner : TFPExpressionScanner;
+    FExprNode : TFPExprNode;
+    FIdentifiers : TFPExprIdentifierDefs;
+    FHashList : TFPHashObjectlist;
+    FDirty : Boolean;
+    procedure CheckEOF;
+    function ConvertNode(Todo: TFPExprNode; ToType: TResultType): TFPExprNode;
+    function GetAsBoolean: Boolean;
+    function GetAsDateTime: TDateTime;
+    function GetAsFloat: TExprFloat;
+    function GetAsInteger: Int64;
+    function GetAsString: String;
+    function MatchNodes(Todo, Match: TFPExprNode): TFPExprNode;
+    procedure CheckNodes(var Left, Right: TFPExprNode);
+    procedure SetBuiltIns(const AValue: TBuiltInCategories);
+    procedure SetIdentifiers(const AValue: TFPExprIdentifierDefs);
+  Protected
+    procedure ParserError(Msg: String);
+    procedure SetExpression(const AValue: String); virtual;
+    Procedure CheckResultType(Const Res :TFPExpressionResult; AType : TResultType); inline;
+    class Function BuiltinsManager : TExprBuiltInManager;
+    Function Level1 : TFPExprNode;
+    Function Level2 : TFPExprNode;
+    Function Level3 : TFPExprNode;
+    Function Level4 : TFPExprNode;
+    Function Level5 : TFPExprNode;
+    Function Level6 : TFPExprNode;
+    Function Primitive : TFPExprNode;
+    function GetToken: TTokenType;
+    Function TokenType : TTokenType;
+    Function CurrentToken : String;
+    Procedure CreateHashList;
+    Property Scanner : TFPExpressionScanner Read FScanner;
+    Property ExprNode : TFPExprNode Read FExprNode;
+    Property Dirty : Boolean Read FDirty;
+  public
+    Constructor Create(AOwner :TComponent); override;
+    Destructor Destroy; override;
+    Function IdentifierByName(const AName : ShortString) : TFPExprIdentifierDef; virtual;
+    Procedure Clear;
+    Procedure EvaluateExpression(out Result : TFPExpressionResult);
+    function ExtractNode(var N: TFPExprNode): Boolean;
+    Function Evaluate : TFPExpressionResult;
+    Function ResultType : TResultType;
+    Function HasAggregate : Boolean;
+    Procedure InitAggregate;
+    Procedure UpdateAggregate;
+    Property AsFloat : TExprFloat Read GetAsFloat;
+    Property AsInteger : Int64 Read GetAsInteger;
+    Property AsString : String Read GetAsString;
+    Property AsBoolean : Boolean Read GetAsBoolean;
+    Property AsDateTime : TDateTime Read GetAsDateTime;
+  Published
+    // The Expression to parse
+    property Expression : String read FExpression write SetExpression;
+    Property Identifiers : TFPExprIdentifierDefs Read FIdentifiers Write SetIdentifiers;
+    Property BuiltIns : TBuiltInCategories Read FBuiltIns Write SetBuiltIns;
+  end;
+
+  { TExprBuiltInManager }
+
+  TExprBuiltInManager = Class(TComponent)
+  Private
+    FDefs : TFPExprIdentifierDefs;
+    function GetCount: Integer;
+    function GetI(AIndex : Integer): TFPBuiltInExprIdentifierDef;
+  protected
+    Property Defs : TFPExprIdentifierDefs Read FDefs;
+  Public
+    Constructor Create(AOwner : TComponent); override;
+    Destructor Destroy; override;
+    Function IndexOfIdentifier(Const AName : ShortString) : Integer;
+    Function FindIdentifier(Const AName : ShortString) : TFPBuiltinExprIdentifierDef;
+    Function IdentifierByName(Const AName : ShortString) : TFPBuiltinExprIdentifierDef;
+    Function AddVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AResultType : TResultType; AValue : String) : TFPBuiltInExprIdentifierDef;
+    Function AddBooleanVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : Boolean) : TFPBuiltInExprIdentifierDef;
+    Function AddIntegerVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : Integer) : TFPBuiltInExprIdentifierDef;
+    Function AddFloatVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : TExprFloat) : TFPBuiltInExprIdentifierDef;
+    Function AddStringVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : String) : TFPBuiltInExprIdentifierDef;
+    Function AddDateTimeVariable(Const ACategory : TBuiltInCategory; Const AName : ShortString; AValue : TDateTime) : TFPBuiltInExprIdentifierDef;
+    Function AddFunction(Const ACategory : TBuiltInCategory; Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionCallBack) : TFPBuiltInExprIdentifierDef;
+    Function AddFunction(Const ACategory : TBuiltInCategory; Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ACallBack : TFPExprFunctionEvent) : TFPBuiltInExprIdentifierDef;
+    Function AddFunction(Const ACategory : TBuiltInCategory; Const AName : ShortString; Const AResultType : Char; Const AParamTypes : String; ANodeClass : TFPExprFunctionClass) : TFPBuiltInExprIdentifierDef;
+    Property IdentifierCount : Integer Read GetCount;
+    Property Identifiers[AIndex : Integer] :TFPBuiltInExprIdentifierDef Read GetI;
+  end;
+
+  EExprParser = Class(Exception);
+
+Const
+  AllBuiltIns = [bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser,bcAggregate];
+
+Function TokenName (AToken : TTokenType) : String;
+Function ResultTypeName (AResult : TResultType) : String;
+Function CharToResultType(C : Char) : TResultType;
+Function BuiltinIdentifiers : TExprBuiltInManager;
+Procedure RegisterStdBuiltins(AManager : TExprBuiltInManager; Categories : TBuiltInCategories = AllBuiltIns);
+function ArgToFloat(Arg: TFPExpressionResult): TExprFloat;
+
+
+
+implementation
+
+uses typinfo;
+
+{ TFPExpressionParser }
+
+const
+  cNull=#0;
+  cSingleQuote = '''';
+
+  Digits        = ['0'..'9','.'];
+  WhiteSpace    = [' ',#13,#10,#9];
+  Operators     = ['+','-','<','>','=','/','*'];
+  Delimiters    = Operators+[',','(',')'];
+  Symbols       = ['%','^']+Delimiters;
+  WordDelimiters = WhiteSpace + Symbols;
+
+Resourcestring
+  SBadQuotes        = 'Unterminated string';
+  SUnknownDelimiter = 'Unknown delimiter character: "%s"';
+  SErrUnknownCharacter = 'Unknown character at pos %d: "%s"';
+  SErrUnexpectedEndOfExpression = 'Unexpected end of expression';
+  SErrUnknownComparison = 'Internal error: Unknown comparison';
+  SErrUnknownBooleanOp = 'Internal error: Unknown boolean operation';
+  SErrBracketExpected = 'Expected ) bracket at position %d, but got %s';
+  SerrUnknownTokenAtPos = 'Unknown token at pos %d : %s';
+  SErrLeftBracketExpected = 'Expected ( bracket at position %d, but got %s';
+  SErrInvalidFloat = '%s is not a valid floating-point value';
+  SErrUnknownIdentifier = 'Unknown identifier: %s';
+  SErrInExpression = 'Cannot evaluate: error in expression';
+  SErrInExpressionEmpty = 'Cannot evaluate: empty expression';
+  SErrCommaExpected =  'Expected comma (,) at position %d, but got %s';
+  SErrInvalidNumberChar = 'Unexpected character in number : %s';
+  SErrInvalidNumber = 'Invalid numerical value : %s';
+  SErrUnterminatedIdentifier = 'Unterminated quoted identifier: %s';
+  SErrNoOperand = 'No operand for unary operation %s';
+  SErrNoleftOperand = 'No left operand for binary operation %s';
+  SErrNoRightOperand = 'No right operand for binary operation %s';
+  SErrNoNegation = 'Cannot negate expression of type %s : %s';
+  SErrNoNOTOperation = 'Cannot perform "not" on expression of type %s: %s';
+  SErrTypesDoNotMatch = 'Type mismatch: %s<>%s for expressions "%s" and "%s".';
+  SErrNoNodeToCheck = 'Internal error: No node to check !';
+  SInvalidNodeType = 'Node type (%s) not in allowed types (%s) for expression: %s';
+  SErrUnterminatedExpression = 'Badly terminated expression. Found token at position %d : %s';
+  SErrDuplicateIdentifier = 'An identifier with name "%s" already exists.';
+  SErrInvalidResultCharacter = '"%s" is not a valid return type indicator';
+  ErrInvalidArgumentCount = 'Invalid argument count for function %s';
+  SErrInvalidArgumentType = 'Invalid type for argument %d: Expected %s, got %s';
+  SErrInvalidResultType = 'Invalid result type: %s';
+  SErrNotVariable = 'Identifier %s is not a variable';
+  SErrIFNeedsBoolean = 'First argument to IF must be of type boolean: %s';
+  SErrCaseNeeds3 = 'Case statement needs to have at least 4 arguments';
+  SErrCaseEvenCount = 'Case statement needs to have an even number of arguments';
+  SErrCaseLabelNotAConst = 'Case label %d "%s" is not a constant expression';
+  SErrCaseLabelType = 'Case label %d "%s" needs type %s, but has type %s';
+  SErrCaseValueType = 'Case value %d "%s" needs type %s, but has type %s';
+
+{ ---------------------------------------------------------------------
+  Auxiliary functions
+  ---------------------------------------------------------------------}
+
+Procedure RaiseParserError(Msg : String);
+begin
+  Raise EExprParser.Create(Msg);
+end;
+
+Procedure RaiseParserError(Fmt : String; Args : Array of const);
+begin
+  Raise EExprParser.CreateFmt(Fmt,Args);
+end;
+
+function TokenName(AToken: TTokenType): String;
+
+begin
+  Result:=GetEnumName(TypeInfo(TTokenType),Ord(AToken));
+end;
+
+function ResultTypeName(AResult: TResultType): String;
+
+begin
+  Result:=GetEnumName(TypeInfo(TResultType),Ord(AResult));
+end;
+
+function CharToResultType(C: Char): TResultType;
+begin
+  Case Upcase(C) of
+    'S' : Result:=rtString;
+    'D' : Result:=rtDateTime;
+    'B' : Result:=rtBoolean;
+    'I' : Result:=rtInteger;
+    'F' : Result:=rtFloat;
+  else
+    RaiseParserError(SErrInvalidResultCharacter,[C]);
+  end;
+end;
+
+Var
+  BuiltIns : TExprBuiltInManager;
+
+function BuiltinIdentifiers: TExprBuiltInManager;
+
+begin
+  If (BuiltIns=Nil) then
+    BuiltIns:=TExprBuiltInManager.Create(Nil);
+  Result:=BuiltIns;
+end;
+
+Procedure FreeBuiltIns;
+
+begin
+  FreeAndNil(Builtins);
+end;
+
+{ TAggregateMax }
+
+procedure TAggregateMax.InitAggregate;
+begin
+  inherited InitAggregate;
+  FFirst:=True;
+  FResult.ResultType:=rtFloat;
+  FResult.resFloat:=0;
+end;
+
+procedure TAggregateMax.UpdateAggregate;
+
+Var
+  OK : Boolean;
+  N : TFPExpressionResult;
+
+begin
+  FArgumentNodes[0].GetNodeValue(N);
+  if FFirst then
+    begin
+    FFirst:=False;
+    OK:=True;
+    end
+  else
+    Case N.ResultType of
+      rtFloat: OK:=N.ResFloat>FResult.ResFloat;
+      rtinteger: OK:=N.ResInteger>FResult.ResFloat;
+    end;
+  if OK then
+    Case N.ResultType of
+      rtFloat: FResult.ResFloat:=N.ResFloat;
+      rtinteger: FResult.ResFloat:=N.ResInteger;
+    end;
+end;
+
+{ TAggregateMin }
+
+procedure TAggregateMin.InitAggregate;
+begin
+  inherited InitAggregate;
+  FFirst:=True;
+  FResult.ResultType:=rtFloat;
+  FResult.resFloat:=0;
+end;
+
+procedure TAggregateMin.UpdateAggregate;
+
+Var
+  OK : Boolean;
+  N : TFPExpressionResult;
+
+begin
+  FArgumentNodes[0].GetNodeValue(N);
+  if FFirst then
+    begin
+    FResult.ResultType:=N.ResultType;
+    FFirst:=False;
+    OK:=True;
+    end
+  else
+    Case N.ResultType of
+      rtFloat: OK:=N.ResFloat<FResult.ResFloat;
+      rtinteger: OK:=N.ResInteger<FResult.ResFloat;
+    end;
+  if OK then
+    Case FResult.ResultType of
+      rtFloat: FResult.ResFloat:=N.ResFloat;
+      rtinteger: FResult.ResFloat:=N.ResInteger;
+    end;
+  inherited UpdateAggregate;
+end;
+
+{ TAggregateAvg }
+
+procedure TAggregateAvg.InitAggregate;
+begin
+  inherited InitAggregate;
+  FCount:=0;
+end;
+
+procedure TAggregateAvg.UpdateAggregate;
+begin
+  inherited UpdateAggregate;
+  Inc(FCount);
+end;
+
+procedure TAggregateAvg.GetNodeValue(var Result: TFPExpressionResult);
+begin
+  inherited GetNodeValue(Result);
+  Result.ResultType:=rtFloat;
+  if FCount=0 then
+    Result.ResFloat:=0
+  else
+    Case FResult.ResultType of
+      rtInteger:
+        Result.ResFloat:=FResult.ResInteger/FCount;
+      rtFloat:
+        Result.ResFloat:=FResult.ResFloat/FCount;
+    end;
+end;
+
+{ TAggregateCount }
+
+procedure TAggregateCount.InitAggregate;
+begin
+  FResult.ResultType:=rtInteger;
+  FResult.ResInteger:=0;
+end;
+
+procedure TAggregateCount.UpdateAggregate;
+begin
+  Inc(FResult.ResInteger);
+end;
+
+{ TAggregateExpr }
+
+class function TAggregateExpr.IsAggregate: Boolean;
+begin
+  Result:=True;
+end;
+
+procedure TAggregateExpr.GetNodeValue(var Result: TFPExpressionResult);
+begin
+  Result:=FResult;
+end;
+
+{ TAggregateSum }
+
+
+procedure TAggregateSum.InitAggregate;
+begin
+  FResult.ResultType:=FArgumentNodes[0].NodeType;
+  Case FResult.ResultType of
+    rtFloat: FResult.ResFloat:=0.0;
+    rtinteger: FResult.ResInteger:=0;
+  end;
+end;
+
+procedure TAggregateSum.UpdateAggregate;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  FArgumentNodes[0].GetNodeValue(R);
+  Case FResult.ResultType of
+    rtFloat: FResult.ResFloat:=FResult.ResFloat+R.ResFloat;
+    rtinteger: FResult.ResInteger:=FResult.ResInteger+R.ResInteger;
+  end;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExpressionScanner
+  ---------------------------------------------------------------------}
+
+function TFPExpressionScanner.IsAlpha(C: Char): Boolean;
+begin
+  Result := C in ['A'..'Z', 'a'..'z'];
+end;
+
+constructor TFPExpressionScanner.Create;
+begin
+  Source:='';
+end;
+
+
+procedure TFPExpressionScanner.SetSource(const AValue: String);
+begin
+  FSource:=AValue;
+  LSource:=Length(FSource);
+  FTokenType:=ttEOF;
+  If LSource=0 then
+    FPos:=0
+  else
+    FPos:=1;
+  FChar:=Pchar(FSource);
+  FToken:='';
+end;
+
+function TFPExpressionScanner.NextPos: Char;
+begin
+  Inc(FPos);
+  Inc(FChar);
+  Result:=FChar^;
+end;
+
+
+function TFPExpressionScanner.IsWordDelim(C: Char): Boolean;
+begin
+  Result:=C in WordDelimiters;
+end;
+
+function TFPExpressionScanner.IsDelim(C: Char): Boolean;
+begin
+  Result:=C in Delimiters;
+end;
+
+function TFPExpressionScanner.IsDigit(C: Char): Boolean;
+begin
+  Result:=C in Digits;
+end;
+
+Procedure TFPExpressionScanner.SkipWhiteSpace;
+
+begin
+  While (FChar^ in WhiteSpace) and (FPos<=LSource) do
+    NextPos;
+end;
+
+Function TFPExpressionScanner.DoDelimiter : TTokenType;
+
+Var
+  B : Boolean;
+  C,D : Char;
+
+begin
+  C:=FChar^;
+  FToken:=C;
+  B:=C in ['<','>'];
+  D:=C;
+  C:=NextPos;
+
+  if B and (C in ['=','>']) then
+    begin
+    FToken:=FToken+C;
+    NextPos;
+    If (D='>') then
+      Result:=ttLargerThanEqual
+    else if (C='>') then
+      Result:=ttUnequal
+    else
+      Result:=ttLessThanEqual;
+    end
+  else
+    Case D of
+      '+' : Result := ttPlus;
+      '-' : Result := ttMinus;
+      '<' : Result := ttLessThan;
+      '>' : Result := ttLargerThan;
+      '=' : Result := ttEqual;
+      '/' : Result := ttDiv;
+      '*' : Result := ttMul;
+      '(' : Result := ttLeft;
+      ')' : Result := ttRight;
+      ',' : Result := ttComma;
+    else
+      ScanError(Format(SUnknownDelimiter,[D]));
+    end;
+
+end;
+
+Procedure TFPExpressionScanner.ScanError(Msg : String);
+
+begin
+  Raise EExprScanner.Create(Msg)
+end;
+
+Function TFPExpressionScanner.DoString : TTokenType;
+
+  Function TerminatingChar(C : Char) : boolean;
+
+  begin
+    Result:=(C=cNull) or
+            ((C=cSingleQuote) and
+              Not ((FPos<LSource) and (FSource[FPos+1]=cSingleQuote)));
+  end;
+
+
+Var
+  C : Char;
+
+begin
+  FToken := '';
+  C:=NextPos;
+  while not TerminatingChar(C) do
+    begin
+    FToken:=FToken+C;
+    If C=cSingleQuote then
+      NextPos;
+    C:=NextPos;
+    end;
+  if (C=cNull) then
+    ScanError(SBadQuotes);
+  Result := ttString;
+  FTokenType:=Result;
+  NextPos;
+end;
+
+function TFPExpressionScanner.GetCurrentChar: Char;
+begin
+  If FChar<>Nil then
+    Result:=FChar^
+  else
+    Result:=#0;
+end;
+
+Function TFPExpressionScanner.DoNumber : TTokenType;
+
+Var
+  C : Char;
+  X : TExprFloat;
+  I : Integer;
+  prevC: Char;
+
+begin
+  C:=CurrentChar;
+  prevC := #0;
+  while (not IsWordDelim(C) or (prevC='E')) and (C<>cNull) do
+    begin
+    If Not ( IsDigit(C)
+             or ((FToken<>'') and (Upcase(C)='E'))
+             or ((FToken<>'') and (C in ['+','-']) and (prevC='E'))
+           )
+    then
+      ScanError(Format(SErrInvalidNumberChar,[C]));
+    FToken := FToken+C;
+    prevC := Upcase(C);
+    C:=NextPos;
+    end;
+  Val(FToken,X,I);
+  If (I<>0) then
+    ScanError(Format(SErrInvalidNumber,[FToken]));
+  Result:=ttNumber;
+end;
+
+Function TFPExpressionScanner.DoIdentifier : TTokenType;
+
+Var
+  C : Char;
+  S : String;
+begin
+  C:=CurrentChar;
+  while (not IsWordDelim(C)) and (C<>cNull) do
+    begin
+    if (C<>'"') then
+      FToken:=FToken+C
+    else
+      begin
+      C:=NextPos;
+      While Not (C in [cNull,'"']) do
+        begin
+        FToken:=FToken+C;
+        C:=NextPos;
+        end;
+      if (C<>'"') then
+        ScanError(Format(SErrUnterminatedIdentifier,[FToken]));
+      end;
+    C:=NextPos;
+    end;
+  S:=LowerCase(Token);
+  If (S='or') then
+    Result:=ttOr
+  else if (S='xor') then
+    Result:=ttXOr
+  else if (S='and') then
+    Result:=ttAnd
+  else if (S='true') then
+    Result:=ttTrue
+  else if (S='false') then
+    Result:=ttFalse
+  else if (S='not') then
+    Result:=ttnot
+  else if (S='if') then
+    Result:=ttif
+  else if (S='case') then
+    Result:=ttcase
+  else
+    Result:=ttIdentifier;
+end;
+
+Function TFPExpressionScanner.GetToken : TTokenType;
+
+Var
+  C : Char;
+
+begin
+  FToken := '';
+  SkipWhiteSpace;
+  C:=FChar^;
+  if c=cNull then
+    Result:=ttEOF
+  else if IsDelim(C) then
+    Result:=DoDelimiter
+  else if (C=cSingleQuote) then
+    Result:=DoString
+  else if IsDigit(C) then
+    Result:=DoNumber
+  else if IsAlpha(C) or (C='"') then
+    Result:=DoIdentifier
+  else
+    ScanError(Format(SErrUnknownCharacter,[FPos,C]))  ;
+  FTokenType:=Result;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExpressionParser
+  ---------------------------------------------------------------------}
+
+function TFPExpressionParser.TokenType: TTokenType;
+
+begin
+  Result:=FScanner.TokenType;
+end;
+
+function TFPExpressionParser.CurrentToken: String;
+begin
+  Result:=FScanner.Token;
+end;
+
+procedure TFPExpressionParser.CreateHashList;
+
+Var
+  ID : TFPExpridentifierDef;
+  BID : TFPBuiltinExpridentifierDef;
+  I  : Integer;
+  M : TExprBuiltinManager;
+
+begin
+  FHashList.Clear;
+  // Builtins
+  M:=BuiltinsManager;
+  If (FBuiltins<>[]) and Assigned(M) then
+    For I:=0 to M.IdentifierCount-1 do
+      begin
+      BID:=M.Identifiers[I];
+      If BID.Category in FBuiltins then
+        FHashList.Add(LowerCase(BID.Name),BID);
+      end;
+  // User
+  For I:=0 to FIdentifiers.Count-1 do
+    begin
+    ID:=FIdentifiers[i];
+    FHashList.Add(LowerCase(ID.Name),ID);
+    end;
+  FDirty:=False;
+end;
+
+function TFPExpressionParser.IdentifierByName(const AName: ShortString): TFPExprIdentifierDef;
+begin
+  If FDirty then
+    CreateHashList;
+  Result:=TFPExprIdentifierDef(FHashList.Find(LowerCase(AName)));
+end;
+
+procedure TFPExpressionParser.Clear;
+begin
+  FExpression:='';
+  FHashList.Clear;
+  FExprNode.Free;
+end;
+
+constructor TFPExpressionParser.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FIdentifiers:=TFPExprIdentifierDefs.Create(TFPExprIdentifierDef);
+  FIdentifiers.FParser:=Self;
+  FScanner:=TFPExpressionScanner.Create;
+  FHashList:=TFPHashObjectList.Create(False);
+end;
+
+destructor TFPExpressionParser.Destroy;
+begin
+  FreeAndNil(FHashList);
+  FreeAndNil(FExprNode);
+  FreeAndNil(FIdentifiers);
+  FreeAndNil(FScanner);
+  inherited Destroy;
+end;
+
+function TFPExpressionParser.GetToken: TTokenType;
+
+begin
+  Result:=FScanner.GetToken;
+end;
+
+procedure TFPExpressionParser.CheckEOF;
+
+begin
+  If (TokenType=ttEOF) then
+    ParserError(SErrUnexpectedEndOfExpression);
+end;
+
+procedure TFPExpressionParser.SetIdentifiers(const AValue: TFPExprIdentifierDefs
+  );
+begin
+  FIdentifiers.Assign(AValue)
+end;
+
+procedure TFPExpressionParser.EvaluateExpression(Out Result: TFPExpressionResult);
+begin
+  If (FExpression='') then
+    ParserError(SErrInExpressionEmpty);
+  if not Assigned(FExprNode) then
+    ParserError(SErrInExpression);
+  FExprNode.GetNodeValue(Result);
+end;
+
+function TFPExpressionParser.ExtractNode(Var N : TFPExprNode) : Boolean;
+begin
+  Result:=Assigned(FExprNode);
+  if Result then
+    begin
+    N:=FExprNode;
+    FExprNode:=Nil;
+    FExpression:='';
+    end;
+end;
+
+procedure TFPExpressionParser.ParserError(Msg: String);
+begin
+  Raise EExprParser.Create(Msg);
+end;
+
+function TFPExpressionParser.ConvertNode(Todo : TFPExprNode; ToType : TResultType): TFPExprNode;
+
+
+begin
+  Result:=ToDo;
+  Case ToDo.NodeType of
+    rtInteger :
+      Case ToType of
+        rtFloat    : Result:=TIntToFloatNode.Create(Result);
+        rtDateTime : Result:=TIntToDateTimeNode.Create(Result);
+      end;
+    rtFloat :
+      Case ToType of
+        rtDateTime : Result:=TFloatToDateTimeNode.Create(Result);
+      end;
+  end;
+end;
+
+function TFPExpressionParser.GetAsBoolean: Boolean;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtBoolean);
+  Result:=Res.ResBoolean;
+end;
+
+function TFPExpressionParser.GetAsDateTime: TDateTime;
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtDateTime);
+  Result:=Res.ResDatetime;
+end;
+
+function TFPExpressionParser.GetAsFloat: TExprFloat;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtFloat);
+  Result:=Res.ResFloat;
+end;
+
+function TFPExpressionParser.GetAsInteger: Int64;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtInteger);
+  Result:=Res.ResInteger;
+end;
+
+function TFPExpressionParser.GetAsString: String;
+
+var
+  Res: TFPExpressionResult;
+
+begin
+  EvaluateExpression(Res);
+  CheckResultType(Res,rtString);
+  Result:=Res.ResString;
+end;
+
+{
+  Checks types of todo and match. If ToDO can be converted to it matches
+  the type of match, then a node is inserted.
+  For binary operations, this function is called for both operands.
+}
+
+function TFPExpressionParser.MatchNodes(Todo,Match : TFPExprNode): TFPExprNode;
+
+Var
+  TT,MT : TResultType;
+
+begin
+  Result:=Todo;
+  TT:=Todo.NodeType;
+  MT:=Match.NodeType;
+  If (TT<>MT) then
+    begin
+    if (TT=rtInteger) then
+      begin
+      if (MT in [rtFloat,rtDateTime]) then
+        Result:=ConvertNode(Todo,MT);
+      end
+    else if (TT=rtFloat) then
+      begin
+      if (MT=rtDateTime) then
+        Result:=ConvertNode(Todo,rtDateTime);
+      end;
+    end;
+end;
+
+{
+  if the result types differ, they are converted to a common type if possible.
+}
+
+procedure TFPExpressionParser.CheckNodes(var Left, Right: TFPExprNode);
+
+begin
+  Left:=MatchNodes(Left,Right);
+  Right:=MatchNodes(Right,Left);
+end;
+
+procedure TFPExpressionParser.SetBuiltIns(const AValue: TBuiltInCategories);
+begin
+  if FBuiltIns=AValue then exit;
+  FBuiltIns:=AValue;
+  FDirty:=True;
+end;
+
+function TFPExpressionParser.Level1: TFPExprNode;
+
+var
+  tt: TTokenType;
+  Right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}Writeln('Level 1 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  if TokenType = ttNot then
+    begin
+    GetToken;
+    CheckEOF;
+    Right:=Level2;
+    Result:=TFPNotNode.Create(Right);
+    end
+  else
+    Result:=Level2;
+  Try
+    while (TokenType in [ttAnd,ttOr,ttXor]) do
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level2;
+      Case tt of
+        ttOr  : Result:=TFPBinaryOrOperation.Create(Result,Right);
+        ttAnd : Result:=TFPBinaryAndOperation.Create(Result,Right);
+        ttXor : Result:=TFPBinaryXorOperation.Create(Result,Right);
+      Else
+        ParserError(SErrUnknownBooleanOp)
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level2: TFPExprNode;
+
+var
+  Right : TFPExprNode;
+  tt : TTokenType;
+  C : TFPBinaryOperationClass;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 2 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level3;
+  try
+    if (TokenType in ttComparisons) then
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level3;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttLessthan         : C:=TFPLessThanOperation;
+        ttLessthanEqual    : C:=TFPLessThanEqualOperation;
+        ttLargerThan       : C:=TFPGreaterThanOperation;
+        ttLargerThanEqual  : C:=TFPGreaterThanEqualOperation;
+        ttEqual            : C:=TFPEqualOperation;
+        ttUnequal          : C:=TFPUnequalOperation;
+      Else
+        ParserError(SErrUnknownComparison)
+      end;
+      Result:=C.Create(Result,Right);
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level3: TFPExprNode;
+
+var
+  tt : TTokenType;
+  right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 3 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level4;
+  try
+    while TokenType in [ttPlus,ttMinus] do
+      begin
+      tt:=TokenType;
+      GetToken;
+      CheckEOF;
+      Right:=Level4;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttPlus  : Result:=TFPAddOperation.Create(Result,Right);
+        ttMinus : Result:=TFPSubtractOperation.Create(Result,Right);
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+
+
+
+function TFPExpressionParser.Level4: TFPExprNode;
+
+var
+  tt : TTokenType;
+  right : TFPExprNode;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 4 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  Result:=Level5;
+  try
+    while (TokenType in [ttMul,ttDiv]) do
+      begin
+      tt:=TokenType;
+      GetToken;
+      Right:=Level5;
+      CheckNodes(Result,Right);
+      Case tt of
+        ttMul : Result:=TFPMultiplyOperation.Create(Result,Right);
+        ttDiv : Result:=TFPDivideOperation.Create(Result,Right);
+      end;
+      end;
+  Except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+function TFPExpressionParser.Level5: TFPExprNode;
+
+Var
+  B : Boolean;
+
+begin
+{$ifdef debugexpr}  Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  B:=False;
+  if (TokenType in [ttPlus,ttMinus]) then
+    begin
+    B:=TokenType=ttMinus;
+    GetToken;
+    end;
+  Result:=Level6;
+  If B then
+    Result:=TFPNegateOperation.Create(Result);
+end;
+
+function TFPExpressionParser.Level6: TFPExprNode;
+begin
+{$ifdef debugexpr}  Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  if (TokenType=ttLeft) then
+    begin
+    GetToken;
+    Result:=Level1;
+    try
+      if (TokenType<>ttRight) then
+        ParserError(Format(SErrBracketExpected,[SCanner.Pos,CurrentToken]));
+      GetToken;
+    Except
+      Result.Free;
+      Raise;
+    end;
+    end
+  else
+    Result:=Primitive;
+end;
+
+function TFPExpressionParser.Primitive: TFPExprNode;
+
+Var
+  I : Int64;
+  C : Integer;
+  X : TExprFloat;
+  ACount : Integer;
+  IFF : Boolean;
+  IFC : Boolean;
+  ID : TFPExprIdentifierDef;
+  Args : TExprArgumentArray;
+  AI : Integer;
+
+begin
+{$ifdef debugexpr}  Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
+  SetLength(Args,0);
+  if (TokenType=ttNumber) then
+    begin
+    if TryStrToInt64(CurrentToken,I) then
+      Result:=TFPConstExpression.CreateInteger(I)
+    else
+      begin
+      Val(CurrentToken,X,C);
+      If (I=0) then
+        Result:=TFPConstExpression.CreateFloat(X)
+      else
+        ParserError(Format(SErrInvalidFloat,[CurrentToken]));
+      end;
+    end
+  else if (TokenType=ttString) then
+    Result:=TFPConstExpression.CreateString(CurrentToken)
+  else if (TokenType in [ttTrue,ttFalse]) then
+    Result:=TFPConstExpression.CreateBoolean(TokenType=ttTrue)
+  else if Not (TokenType in [ttIdentifier,ttIf,ttcase]) then
+    ParserError(Format(SerrUnknownTokenAtPos,[Scanner.Pos,CurrentToken]))
+  else
+    begin
+    IFF:=TokenType=ttIf;
+    IFC:=TokenType=ttCase;
+    if Not (IFF or IFC) then
+      begin
+      ID:=self.IdentifierByName(CurrentToken);
+      If (ID=Nil) then
+        ParserError(Format(SErrUnknownIdentifier,[CurrentToken]))
+      end;
+    // Determine number of arguments
+    if Iff then
+      ACount:=3
+    else if IfC then
+      ACount:=-4
+    else if (ID.IdentifierType in [itFunctionCallBack,itFunctionHandler,itFunctionNode]) then
+      ACount:=ID.ArgumentCount
+    else
+      ACount:=0;
+    // Parse arguments.
+    // Negative is for variable number of arguments, where Abs(value) is the minimum number of arguments
+    If (ACount<>0) then
+      begin
+      GetToken;
+      If (TokenType<>ttLeft) then
+         ParserError(Format(SErrLeftBracketExpected,[Scanner.Pos,CurrentToken]));
+      SetLength(Args,Abs(ACount));
+      AI:=0;
+      Try
+        Repeat
+          GetToken;
+          // Check if we must enlarge the argument array
+          If (ACount<0) and (AI=Length(Args)) then
+            begin
+            SetLength(Args,AI+1);
+            Args[AI]:=Nil;
+            end;
+          Args[AI]:=Level1;
+          Inc(AI);
+          If (TokenType<>ttComma) then
+            If (AI<Abs(ACount)) then
+              ParserError(Format(SErrCommaExpected,[Scanner.Pos,CurrentToken]))
+        Until (AI=ACount) or ((ACount<0) and (TokenType=ttRight));
+        If TokenType<>ttRight then
+          ParserError(Format(SErrBracketExpected,[Scanner.Pos,CurrentToken]));
+      except
+        On E : Exception do
+          begin
+          Dec(AI);
+          While (AI>=0) do
+            begin
+            FreeAndNil(Args[Ai]);
+            Dec(AI);
+            end;
+          Raise;
+          end;
+      end;
+      end;
+    If Iff then
+      Result:=TIfOperation.Create(Args[0],Args[1],Args[2])
+    else If IfC then
+      Result:=TCaseOperation.Create(Args)
+    else
+      Case ID.IdentifierType of
+        itVariable         : Result:= TFPExprVariable.CreateIdentifier(ID);
+        itFunctionCallBack : Result:= TFPFunctionCallback.CreateFunction(ID,Args);
+        itFunctionHandler  : Result:= TFPFunctionEventHandler.CreateFunction(ID,Args);
+        itFunctionNode     : Result:= ID.NodeType.CreateFunction(ID,Args);
+      end;
+    end;
+  GetToken;
+end;
+
+
+procedure TFPExpressionParser.SetExpression(const AValue: String);
+begin
+  if FExpression=AValue then exit;
+  FExpression:=AValue;
+  FScanner.Source:=AValue;
+  If Assigned(FExprNode) then
+    FreeAndNil(FExprNode);
+  If (FExpression<>'') then
+    begin
+    GetToken;
+    FExprNode:=Level1;
+    If (TokenType<>ttEOF) then
+      ParserError(Format(SErrUnterminatedExpression,[Scanner.Pos,CurrentToken]));
+    FExprNode.Check;
+    end
+  else
+    FExprNode:=Nil;
+end;
+
+procedure TFPExpressionParser.CheckResultType(const Res: TFPExpressionResult;
+  AType: TResultType); inline;
+begin
+  If (Res.ResultType<>AType) then
+    RaiseParserError(SErrInvalidResultType,[ResultTypeName(Res.ResultType)]);
+end;
+
+class function TFPExpressionParser.BuiltinsManager: TExprBuiltInManager;
+begin
+  Result:=BuiltinIdentifiers;
+end;
+
+
+function TFPExpressionParser.Evaluate: TFPExpressionResult;
+begin
+  EvaluateExpression(Result);
+end;
+
+function TFPExpressionParser.ResultType: TResultType;
+begin
+  if not Assigned(FExprNode) then
+    ParserError(SErrInExpression);
+  Result:=FExprNode.NodeType;
+end;
+
+function TFPExpressionParser.HasAggregate: Boolean;
+begin
+  Result:=Assigned(FExprNode) and FExprNode.HasAggregate;
+end;
+
+procedure TFPExpressionParser.InitAggregate;
+begin
+  If Assigned(FExprNode) then
+    FExprNode.InitAggregate;
+end;
+
+procedure TFPExpressionParser.UpdateAggregate;
+begin
+  If Assigned(FExprNode) then
+    FExprNode.UpdateAggregate;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExprIdentifierDefs
+  ---------------------------------------------------------------------}
+
+function TFPExprIdentifierDefs.GetI(AIndex : Integer): TFPExprIdentifierDef;
+begin
+  Result:=TFPExprIdentifierDef(Items[AIndex]);
+end;
+
+procedure TFPExprIdentifierDefs.SetI(AIndex : Integer;
+  const AValue: TFPExprIdentifierDef);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+procedure TFPExprIdentifierDefs.Update(Item: TCollectionItem);
+begin
+  If Assigned(FParser) then
+    FParser.FDirty:=True;
+end;
+
+function TFPExprIdentifierDefs.IndexOfIdentifier(const AName: ShortString
+  ): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) And (CompareText(GetI(Result).Name,AName)<>0) do
+    Dec(Result);
+end;
+
+function TFPExprIdentifierDefs.FindIdentifier(const AName: ShortString
+  ): TFPExprIdentifierDef;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfIdentifier(AName);
+  If (I=-1) then
+    Result:=Nil
+  else
+    Result:=GetI(I);
+end;
+
+function TFPExprIdentifierDefs.IdentifierByName(const AName: ShortString
+  ): TFPExprIdentifierDef;
+begin
+  Result:=FindIdentifier(AName);
+  if (Result=Nil) then
+    RaiseParserError(SErrUnknownIdentifier,[AName]);
+end;
+
+function TFPExprIdentifierDefs.AddVariable(const AName: ShortString;
+  AResultType: TResultType; ACallback: TFPExprVariableCallBack
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=AResultType;
+  Result.OnGetVariableValueCallBack:=ACallBack
+end;
+
+function TFPExprIdentifierDefs.AddVariable(const AName: ShortString;
+  AResultType: TResultType; ACallback: TFPExprVariableEvent
+  ): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=AResultType;
+  Result.OnGetVariableValue:=ACallBack
+end;
+
+function TFPExprIdentifierDefs.AddVariable(const AName: ShortString;
+  AResultType: TResultType; AValue: String): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=AResultType;
+  Result.Value:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddBooleanVariable(const AName: ShortString;
+  AValue: Boolean): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtBoolean;
+  Result.FValue.ResBoolean:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddIntegerVariable(const AName: ShortString;
+  AValue: Integer): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtInteger;
+  Result.FValue.ResInteger:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddFloatVariable(const AName: ShortString;
+  AValue: TExprFloat): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtFloat;
+  Result.FValue.ResFloat:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddStringVariable(const AName: ShortString;
+  AValue: String): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtString;
+  Result.FValue.ResString:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddDateTimeVariable(const AName: ShortString;
+  AValue: TDateTime): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.IdentifierType:=itVariable;
+  Result.Name:=AName;
+  Result.ResultType:=rtDateTime;
+  Result.FValue.ResDateTime:=AValue;
+end;
+
+function TFPExprIdentifierDefs.AddFunction(const AName: ShortString;
+  const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionCallBack): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.Name:=Aname;
+  Result.IdentifierType:=itFunctionCallBack;
+  Result.ParameterTypes:=AParamTypes;
+  Result.ResultType:=CharToResultType(AResultType);
+  Result.FOnGetValueCB:=ACallBack;
+end;
+
+function TFPExprIdentifierDefs.AddFunction(const AName: ShortString;
+  const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionEvent): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.Name:=Aname;
+  Result.IdentifierType:=itFunctionHandler;
+  Result.ParameterTypes:=AParamTypes;
+  Result.ResultType:=CharToResultType(AResultType);
+  Result.FOnGetValue:=ACallBack;
+end;
+
+function TFPExprIdentifierDefs.AddFunction(const AName: ShortString;
+  const AResultType: Char; const AParamTypes: String;
+  ANodeClass: TFPExprFunctionClass): TFPExprIdentifierDef;
+begin
+  Result:=Add as TFPExprIdentifierDef;
+  Result.Name:=Aname;
+  Result.IdentifierType:=itFunctionNode;
+  Result.ParameterTypes:=AParamTypes;
+  Result.ResultType:=CharToResultType(AResultType);
+  Result.FNodeType:=ANodeClass;
+end;
+
+{ ---------------------------------------------------------------------
+  TFPExprIdentifierDef
+  ---------------------------------------------------------------------}
+
+procedure TFPExprIdentifierDef.SetName(const AValue: ShortString);
+begin
+  if FName=AValue then exit;
+  If (AValue<>'') then
+    If Assigned(Collection) and (TFPExprIdentifierDefs(Collection).IndexOfIdentifier(AValue)<>-1) then
+      begin
+      Writeln('Setting',AValue,'Index ',Index,' found at ',TFPExprIdentifierDefs(Collection).IndexOfIdentifier(AValue));
+      RaiseParserError(SErrDuplicateIdentifier,[AValue]);
+      end;
+  FName:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetResultType(const AValue: TResultType);
+
+begin
+  If AValue<>FValue.ResultType then
+    begin
+    FValue.ResultType:=AValue;
+    SetValue(FStringValue);
+    end;
+end;
+
+procedure TFPExprIdentifierDef.SetValue(const AValue: String);
+begin
+  FStringValue:=AValue;
+  If (AValue<>'') then
+    Case FValue.ResultType of
+      rtBoolean  : FValue.ResBoolean:=FStringValue='True';
+      rtInteger  : FValue.ResInteger:=StrToInt(AValue);
+      rtFloat    : FValue.ResFloat:=StrToFloat(AValue);
+      rtDateTime : FValue.ResDateTime:=StrToDateTime(AValue);
+      rtString   : FValue.ResString:=AValue;
+    end
+  else
+    Case FValue.ResultType of
+      rtBoolean  : FValue.ResBoolean:=False;
+      rtInteger  : FValue.ResInteger:=0;
+      rtFloat    : FValue.ResFloat:=0.0;
+      rtDateTime : FValue.ResDateTime:=0;
+      rtString   : FValue.ResString:='';
+    end
+end;
+
+procedure TFPExprIdentifierDef.CheckResultType(const AType: TResultType);
+begin
+  If FValue.ResultType<>AType then
+    RaiseParserError(SErrInvalidResultType,[ResultTypeName(AType)])
+end;
+
+procedure TFPExprIdentifierDef.CheckVariable;
+begin
+  If Identifiertype<>itvariable then
+    RaiseParserError(SErrNotVariable,[Name]);
+  if EventBasedVariable then
+    FetchValue;
+end;
+
+function TFPExprIdentifierDef.ArgumentCount: Integer;
+begin
+  Result:=Length(FArgumentTypes);
+end;
+
+procedure TFPExprIdentifierDef.Assign(Source: TPersistent);
+
+Var
+  EID : TFPExprIdentifierDef;
+
+begin
+  if (Source is TFPExprIdentifierDef) then
+    begin
+    EID:=Source as TFPExprIdentifierDef;
+    FStringValue:=EID.FStringValue;
+    FValue:=EID.FValue;
+    FArgumentTypes:=EID.FArgumentTypes;
+    FIDType:=EID.FIDType;
+    FName:=EID.FName;
+    FOnGetValue:=EID.FOnGetValue;
+    FOnGetValueCB:=EID.FOnGetValueCB;
+    FOnGetVarValue:=EID.FOnGetVarValue;
+    FOnGetVarValueCB:=EID.FOnGetVarValueCB;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+procedure TFPExprIdentifierDef.SetArgumentTypes(const AValue: String);
+
+Var
+  I : integer;
+
+begin
+  if FArgumentTypes=AValue then exit;
+  For I:=1 to Length(AValue) do
+    CharToResultType(AValue[i]);
+  FArgumentTypes:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsBoolean(const AValue: Boolean);
+begin
+  CheckVariable;
+  CheckResultType(rtBoolean);
+  FValue.ResBoolean:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsDateTime(const AValue: TDateTime);
+begin
+  CheckVariable;
+  CheckResultType(rtDateTime);
+  FValue.ResDateTime:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsFloat(const AValue: TExprFloat);
+begin
+  CheckVariable;
+  CheckResultType(rtFloat);
+  FValue.ResFloat:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsInteger(const AValue: Int64);
+begin
+  CheckVariable;
+  CheckResultType(rtInteger);
+  FValue.ResInteger:=AValue;
+end;
+
+procedure TFPExprIdentifierDef.SetAsString(const AValue: String);
+begin
+  CheckVariable;
+  CheckResultType(rtString);
+  FValue.resString:=AValue;
+end;
+
+function TFPExprIdentifierDef.GetValue: String;
+begin
+  Case FValue.ResultType of
+    rtBoolean  : If FValue.ResBoolean then
+                   Result:='True'
+                 else
+                   Result:='False';
+    rtInteger  : Result:=IntToStr(FValue.ResInteger);
+    rtFloat    : Result:=FloatToStr(FValue.ResFloat);
+    rtDateTime : Result:=FormatDateTime('cccc',FValue.ResDateTime);
+    rtString   : Result:=FValue.ResString;
+  end;
+end;
+
+procedure TFPExprIdentifierDef.FetchValue;
+
+Var
+  RT,RT2 : TResultType;
+  I : Integer;
+
+begin
+  RT:=FValue.ResultType;
+  if Assigned(FOnGetVarValue) then
+    FOnGetVarValue(FValue,FName)
+  else
+    FOnGetVarValueCB(FValue,FName);
+  RT2:=FValue.ResultType;
+  if RT2<>RT then
+    begin
+    // Automatically convert integer to float.
+    if (rt2=rtInteger) and (rt=rtFLoat) then
+      begin
+      FValue.ResultType:=RT;
+      I:=FValue.resInteger;
+      FValue.resFloat:=I;
+      end
+    else
+      begin
+      // Restore
+      FValue.ResultType:=RT;
+      Raise EExprParser.CreateFmt('Value handler for variable %s returned wrong type, expected "%s", got "%s"',[
+        FName,
+        GetEnumName(TypeInfo(TResultType),Ord(rt)),
+        GetEnumName(TypeInfo(TResultType),Ord(rt2))
+        ]);
+      end;
+    end;
+end;
+
+function TFPExprIdentifierDef.EventBasedVariable: Boolean;
+begin
+  Result:=Assigned(FOnGetVarValue) or Assigned(FOnGetVarValueCB);
+end;
+
+function TFPExprIdentifierDef.GetResultType: TResultType;
+begin
+  Result:=FValue.ResultType;
+end;
+
+function TFPExprIdentifierDef.GetAsFloat: TExprFloat;
+begin
+  CheckResultType(rtFloat);
+  CheckVariable;
+  Result:=FValue.ResFloat;
+end;
+
+function TFPExprIdentifierDef.GetAsBoolean: Boolean;
+begin
+  CheckResultType(rtBoolean);
+  CheckVariable;
+  Result:=FValue.ResBoolean;
+end;
+
+function TFPExprIdentifierDef.GetAsDateTime: TDateTime;
+begin
+  CheckResultType(rtDateTime);
+  CheckVariable;
+  Result:=FValue.ResDateTime;
+end;
+
+function TFPExprIdentifierDef.GetAsInteger: Int64;
+begin
+  CheckResultType(rtInteger);
+  CheckVariable;
+  Result:=FValue.ResInteger;
+end;
+
+function TFPExprIdentifierDef.GetAsString: String;
+begin
+  CheckResultType(rtString);
+  CheckVariable;
+  Result:=FValue.ResString;
+end;
+
+{ ---------------------------------------------------------------------
+  TExprBuiltInManager
+  ---------------------------------------------------------------------}
+
+function TExprBuiltInManager.GetCount: Integer;
+begin
+  Result:=FDefs.Count;
+end;
+
+function TExprBuiltInManager.GetI(AIndex : Integer
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs[Aindex])
+end;
+
+constructor TExprBuiltInManager.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDefs:=TFPExprIdentifierDefs.Create(TFPBuiltInExprIdentifierDef)
+end;
+
+destructor TExprBuiltInManager.Destroy;
+begin
+  FreeAndNil(FDefs);
+  inherited Destroy;
+end;
+
+function TExprBuiltInManager.IndexOfIdentifier(const AName: ShortString
+  ): Integer;
+begin
+  Result:=FDefs.IndexOfIdentifier(AName);
+end;
+
+function TExprBuiltInManager.FindIdentifier(const AName: ShortString
+  ): TFPBuiltinExprIdentifierDef;
+begin
+  Result:=TFPBuiltinExprIdentifierDef(FDefs.FindIdentifier(AName));
+end;
+
+function TExprBuiltInManager.IdentifierByName(const AName: ShortString
+  ): TFPBuiltinExprIdentifierDef;
+begin
+  Result:=TFPBuiltinExprIdentifierDef(FDefs.IdentifierByName(AName));
+end;
+
+function TExprBuiltInManager.AddVariable(const ACategory: TBuiltInCategory;
+  const AName: ShortString; AResultType: TResultType; AValue: String
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.Addvariable(AName,AResultType,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddBooleanVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: Boolean
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddBooleanvariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddIntegerVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: Integer
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddIntegerVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFloatVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString;
+  AValue: TExprFloat): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFloatVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddStringVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: String
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddStringVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddDateTimeVariable(
+  const ACategory: TBuiltInCategory; const AName: ShortString; AValue: TDateTime
+  ): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddDateTimeVariable(AName,AValue));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFunction(const ACategory: TBuiltInCategory;
+  const AName: ShortString; const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionCallBack): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFunction(AName,AResultType,AParamTypes,ACallBack));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFunction(const ACategory: TBuiltInCategory;
+  const AName: ShortString; const AResultType: Char; const AParamTypes: String;
+  ACallBack: TFPExprFunctionEvent): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFunction(AName,AResultType,AParamTypes,ACallBack));
+  Result.Category:=ACategory;
+end;
+
+function TExprBuiltInManager.AddFunction(const ACategory: TBuiltInCategory;
+  const AName: ShortString; const AResultType: Char; const AParamTypes: String;
+  ANodeClass: TFPExprFunctionClass): TFPBuiltInExprIdentifierDef;
+begin
+  Result:=TFPBuiltInExprIdentifierDef(FDefs.AddFunction(AName,AResultType,AParamTypes,ANodeClass));
+  Result. Category:=ACategory;
+end;
+
+
+{ ---------------------------------------------------------------------
+  Various Nodes
+  ---------------------------------------------------------------------}
+
+{ TFPBinaryOperation }
+
+procedure TFPBinaryOperation.CheckSameNodeTypes;
+
+Var
+  LT,RT : TResultType;
+
+
+begin
+  LT:=Left.NodeType;
+  RT:=Right.NodeType;
+  if (RT<>LT) then
+    RaiseParserError(SErrTypesDoNotMatch,[ResultTypeName(LT),ResultTypeName(RT),Left.AsString,Right.AsString])
+end;
+
+constructor TFPBinaryOperation.Create(ALeft, ARight: TFPExprNode);
+begin
+  FLeft:=ALeft;
+  FRight:=ARight;
+end;
+
+destructor TFPBinaryOperation.Destroy;
+begin
+  FreeAndNil(FLeft);
+  FreeAndNil(FRight);
+  inherited Destroy;
+end;
+
+procedure TFPBinaryOperation.InitAggregate;
+begin
+  inherited InitAggregate;
+  if Assigned(Left) then
+    Left.InitAggregate;
+  if Assigned(Right) then
+    Right.InitAggregate;
+end;
+
+procedure TFPBinaryOperation.UpdateAggregate;
+begin
+  inherited UpdateAggregate;
+  if Assigned(Left) then
+    Left.UpdateAggregate;
+  if Assigned(Right) then
+    Right.UpdateAggregate;
+end;
+
+function TFPBinaryOperation.HasAggregate: Boolean;
+begin
+  Result:=inherited HasAggregate;
+  if Assigned(Left) then
+    Result:=Result or Left.HasAggregate;
+  if Assigned(Right) then
+    Result:=Result or Right.HasAggregate;
+end;
+
+procedure TFPBinaryOperation.Check;
+begin
+  If Not Assigned(Left) then
+    RaiseParserError(SErrNoLeftOperand,[classname]);
+  If Not Assigned(Right) then
+    RaiseParserError(SErrNoRightOperand,[classname]);
+end;
+
+{ TFPUnaryOperator }
+
+constructor TFPUnaryOperator.Create(AOperand: TFPExprNode);
+begin
+  FOperand:=AOperand;
+end;
+
+destructor TFPUnaryOperator.Destroy;
+begin
+  FreeAndNil(FOperand);
+  inherited Destroy;
+end;
+
+procedure TFPUnaryOperator.InitAggregate;
+begin
+  inherited InitAggregate;
+  if Assigned(FOperand) then
+    FOperand.InitAggregate;
+
+end;
+
+procedure TFPUnaryOperator.UpdateAggregate;
+begin
+  inherited UpdateAggregate;
+  if Assigned(FOperand) then
+    FOperand.UpdateAggregate;
+end;
+
+function TFPUnaryOperator.HasAggregate: Boolean;
+begin
+  Result:=inherited HasAggregate;
+  if Assigned(FOperand) then
+    Result:=Result or FOperand.HasAggregate;
+end;
+
+procedure TFPUnaryOperator.Check;
+begin
+  If Not Assigned(Operand) then
+    RaiseParserError(SErrNoOperand,[Self.className]);
+end;
+
+{ TFPConstExpression }
+
+constructor TFPConstExpression.CreateString(AValue: String);
+begin
+  FValue.ResultType:=rtString;
+  FValue.ResString:=AValue;
+end;
+
+constructor TFPConstExpression.CreateInteger(AValue: Int64);
+begin
+  FValue.ResultType:=rtInteger;
+  FValue.ResInteger:=AValue;
+end;
+
+constructor TFPConstExpression.CreateDateTime(AValue: TDateTime);
+begin
+  FValue.ResultType:=rtDateTime;
+  FValue.ResDateTime:=AValue;
+end;
+
+constructor TFPConstExpression.CreateFloat(AValue: TExprFloat);
+begin
+  Inherited create;
+  FValue.ResultType:=rtFloat;
+  FValue.ResFloat:=AValue;
+end;
+
+constructor TFPConstExpression.CreateBoolean(AValue: Boolean);
+begin
+  FValue.ResultType:=rtBoolean;
+  FValue.ResBoolean:=AValue;
+end;
+
+procedure TFPConstExpression.Check;
+begin
+  // Nothing to check;
+end;
+
+function TFPConstExpression.NodeType: TResultType;
+begin
+  Result:=FValue.ResultType;
+end;
+
+Procedure TFPConstExpression.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Result:=FValue;
+end;
+
+function TFPConstExpression.AsString: string ;
+begin
+  Case NodeType of
+    rtString  : Result:=''''+FValue.resString+'''';
+    rtInteger : Result:=IntToStr(FValue.resInteger);
+    rtDateTime : Result:=''''+FormatDateTime('cccc',FValue.resDateTime)+'''';
+    rtBoolean : If FValue.ResBoolean then Result:='True' else Result:='False';
+    rtFloat : Str(FValue.ResFloat,Result);
+  end;
+end;
+
+
+{ TFPNegateOperation }
+
+procedure TFPNegateOperation.Check;
+begin
+  Inherited;
+  If Not (Operand.NodeType in [rtInteger,rtFloat]) then
+    RaiseParserError(SErrNoNegation,[ResultTypeName(Operand.NodeType),Operand.AsString])
+end;
+
+function TFPNegateOperation.NodeType: TResultType;
+begin
+  Result:=Operand.NodeType;
+end;
+
+Procedure TFPNegateOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Case Result.ResultType of
+    rtInteger : Result.resInteger:=-Result.ResInteger;
+    rtFloat : Result.resFloat:=-Result.ResFloat;
+  end;
+end;
+
+function TFPNegateOperation.AsString: String;
+begin
+  Result:='-'+TrimLeft(Operand.AsString);
+end;
+
+{ TFPBinaryAndOperation }
+
+procedure TFPBooleanOperation.Check;
+begin
+  inherited Check;
+  CheckNodeType(Left,[rtInteger,rtBoolean]);
+  CheckNodeType(Right,[rtInteger,rtBoolean]);
+  CheckSameNodeTypes;
+end;
+
+function TFPBooleanOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+Procedure TFPBinaryAndOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean and RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger and RRes.ResInteger;
+  end;
+end;
+
+function TFPBinaryAndOperation.AsString: string;
+begin
+  Result:=Left.AsString+' and '+Right.AsString;
+end;
+
+{ TFPExprNode }
+
+procedure TFPExprNode.CheckNodeType(Anode: TFPExprNode; Allowed: TResultTypes);
+
+Var
+  S : String;
+  A : TResultType;
+
+begin
+  If (Anode=Nil) then
+    RaiseParserError(SErrNoNodeToCheck);
+  If Not (ANode.NodeType in Allowed) then
+    begin
+    S:='';
+    For A:=Low(TResultType) to High(TResultType) do
+      If A in Allowed then
+        begin
+        If S<>'' then
+          S:=S+',';
+        S:=S+ResultTypeName(A);
+        end;
+    RaiseParserError(SInvalidNodeType,[ResultTypeName(ANode.NodeType),S,ANode.AsString]);
+    end;
+end;
+
+procedure TFPExprNode.InitAggregate;
+begin
+  // Do nothing
+end;
+
+procedure TFPExprNode.UpdateAggregate;
+begin
+  // Do nothing
+end;
+
+function TFPExprNode.HasAggregate: Boolean;
+begin
+  Result:=IsAggregate;
+end;
+
+class function TFPExprNode.IsAggregate: Boolean;
+begin
+  Result:=False;
+end;
+
+function TFPExprNode.NodeValue: TFPExpressionResult;
+begin
+  GetNodeValue(Result);
+end;
+
+{ TFPBinaryOrOperation }
+
+function TFPBinaryOrOperation.AsString: string;
+begin
+  Result:=Left.AsString+' or '+Right.AsString;
+end;
+
+Procedure TFPBinaryOrOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean or RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger or RRes.ResInteger;
+  end;
+end;
+
+{ TFPBinaryXOrOperation }
+
+function TFPBinaryXOrOperation.AsString: string;
+begin
+  Result:=Left.AsString+' xor '+Right.AsString;
+end;
+
+Procedure TFPBinaryXOrOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean : Result.resBoolean:=Result.ResBoolean xor RRes.ResBoolean;
+    rtInteger : Result.resInteger:=Result.ResInteger xor RRes.ResInteger;
+  end;
+end;
+
+{ TFPNotNode }
+
+procedure TFPNotNode.Check;
+begin
+  If Not (Operand.NodeType in [rtInteger,rtBoolean]) then
+    RaiseParserError(SErrNoNotOperation,[ResultTypeName(Operand.NodeType),Operand.AsString])
+end;
+
+function TFPNotNode.NodeType: TResultType;
+begin
+  Result:=Operand.NodeType;
+end;
+
+procedure TFPNotNode.GetNodeValue(var Result: TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Case result.ResultType of
+    rtInteger : Result.resInteger:=Not Result.resInteger;
+    rtBoolean : Result.resBoolean:=Not Result.resBoolean;
+  end
+end;
+
+function TFPNotNode.AsString: String;
+begin
+  Result:='not '+Operand.AsString;
+end;
+
+{ TIfOperation }
+
+constructor TIfOperation.Create(ACondition, ALeft, ARight: TFPExprNode);
+begin
+  Inherited Create(ALeft,ARight);
+  FCondition:=ACondition;
+end;
+
+destructor TIfOperation.destroy;
+begin
+  FreeAndNil(FCondition);
+  inherited destroy;
+end;
+
+procedure TIfOperation.GetNodeValue(var Result: TFPExpressionResult);
+
+begin
+  FCondition.GetNodeValue(Result);
+  If Result.ResBoolean then
+    Left.GetNodeValue(Result)
+  else
+    Right.GetNodeValue(Result)
+end;
+
+procedure TIfOperation.Check;
+begin
+  inherited Check;
+  if (Condition.NodeType<>rtBoolean) then
+    RaiseParserError(SErrIFNeedsBoolean,[Condition.AsString]);
+  CheckSameNodeTypes;
+end;
+
+procedure TIfOperation.InitAggregate;
+begin
+  inherited InitAggregate;
+  If Assigned(FCondition) then
+    fCondition.InitAggregate;
+end;
+
+procedure TIfOperation.UpdateAggregate;
+begin
+  inherited UpdateAggregate;
+  If Assigned(FCondition) then
+    FCondition.UpdateAggregate;
+end;
+
+function TIfOperation.HasAggregate: Boolean;
+begin
+  Result:=inherited HasAggregate;
+  if Assigned(Condition) then
+    Result:=Result or Condition.HasAggregate;
+end;
+
+function TIfOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+function TIfOperation.AsString: string;
+begin
+  Result:=Format('if(%s , %s , %s)',[Condition.AsString,Left.AsString,Right.AsString]);
+end;
+
+{ TCaseOperation }
+
+procedure TCaseOperation.GetNodeValue(var Result: TFPExpressionResult);
+
+Var
+  I,L : Integer;
+  B : Boolean;
+  RT,RV : TFPExpressionResult;
+
+begin
+  FArgs[0].GetNodeValue(RT);
+  L:=Length(FArgs);
+  I:=2;
+  B:=False;
+  While (Not B) and (I<L) do
+    begin
+    FArgs[i].GetNodeValue(RV);
+    Case RT.ResultType of
+      rtBoolean  : B:=RT.ResBoolean=RV.ResBoolean;
+      rtInteger  : B:=RT.ResInteger=RV.ResInteger;
+      rtFloat    : B:=RT.ResFloat=RV.ResFLoat;
+      rtDateTime : B:=RT.ResDateTime=RV.ResDateTime;
+      rtString   : B:=RT.ResString=RV.ResString;
+    end;
+    If Not B then
+      Inc(I,2);
+    end;
+  // Set result type.
+  Result.ResultType:=FArgs[1].NodeType;
+  If B then
+    FArgs[I+1].GetNodeValue(Result)
+  else if ((L mod 2)=0) then
+    FArgs[1].GetNodeValue(Result);
+end;
+
+procedure TCaseOperation.Check;
+
+Var
+  T,V : TResultType;
+  I : Integer;
+  N : TFPExprNode;
+
+begin
+  If (Length(FArgs)<3) then
+    RaiseParserError(SErrCaseNeeds3);
+  If ((Length(FArgs) mod 2)=1) then
+    RaiseParserError(SErrCaseEvenCount);
+  T:=FArgs[0].NodeType;
+  V:=FArgs[1].NodeType;
+  For I:=2 to Length(Fargs)-1 do
+    begin
+    N:=FArgs[I];
+    // Even argument types (labels) must equal tag.
+    If ((I mod 2)=0) then
+      begin
+      If Not (N is TFPConstExpression) then
+        RaiseParserError(SErrCaseLabelNotAConst,[I div 2,N.AsString]);
+      If (N.NodeType<>T) then
+        RaiseParserError(SErrCaseLabelType,[I div 2,N.AsString,ResultTypeName(T),ResultTypeName(N.NodeType)]);
+      end
+    else // Odd argument types (values) must match first.
+      begin
+      If (N.NodeType<>V) then
+        RaiseParserError(SErrCaseValueType,[(I-1)div 2,N.AsString,ResultTypeName(V),ResultTypeName(N.NodeType)]);
+      end
+    end;
+end;
+
+procedure TCaseOperation.InitAggregate;
+
+Var
+  I : Integer;
+
+begin
+  inherited InitAggregate;
+  if Assigned(FCondition) then
+    FCondition.InitAggregate;
+  For I:=0 to Length(Fargs)-1 do
+    FArgs[i].InitAggregate;
+end;
+
+procedure TCaseOperation.UpdateAggregate;
+Var
+  I : Integer;
+begin
+  inherited UpdateAggregate;
+  if Assigned(FCondition) then
+    FCondition.UpdateAggregate;
+  For I:=0 to Length(Fargs)-1 do
+    FArgs[i].InitAggregate;
+end;
+
+Function  TCaseOperation.HasAggregate : Boolean;
+
+Var
+  I,L : Integer;
+begin
+  Result:=inherited HasAggregate;
+  L:=Length(Fargs);
+  I:=0;
+  While (Not Result) and (I<L) do
+    begin
+    Result:=Result or FArgs[i].HasAggregate;
+    Inc(I)
+    end;
+end;
+
+function TCaseOperation.NodeType: TResultType;
+begin
+  Result:=FArgs[1].NodeType;
+end;
+
+constructor TCaseOperation.Create(Args: TExprArgumentArray);
+begin
+  Fargs:=Args;
+end;
+
+destructor TCaseOperation.destroy;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgs)-1 do
+    FreeAndNil(Fargs[I]);
+  inherited destroy;
+end;
+
+function TCaseOperation.AsString: string;
+
+Var
+  I : integer;
+
+begin
+  Result:='';
+  For I:=0 to Length(FArgs)-1 do
+    begin
+    If (Result<>'') then
+      Result:=Result+', ';
+    Result:=Result+FArgs[i].AsString;
+    end;
+  Result:='Case('+Result+')';
+end;
+
+{ TFPBooleanResultOperation }
+
+procedure TFPBooleanResultOperation.Check;
+begin
+  inherited Check;
+  CheckSameNodeTypes;
+end;
+
+function TFPBooleanResultOperation.NodeType: TResultType;
+begin
+  Result:=rtBoolean;
+end;
+
+{ TFPEqualOperation }
+
+function TFPEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' = '+Right.AsString;
+end;
+
+Procedure TFPEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtBoolean  : Result.resBoolean:=Result.ResBoolean=RRes.ResBoolean;
+    rtInteger  : Result.resBoolean:=Result.ResInteger=RRes.ResInteger;
+    rtFloat    : Result.resBoolean:=Result.ResFloat=RRes.ResFLoat;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime=RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString=RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPUnequalOperation }
+
+function TFPUnequalOperation.AsString: string;
+begin
+  Result:=Left.AsString+' <> '+Right.AsString;
+end;
+
+Procedure TFPUnequalOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+
+{ TFPLessThanOperation }
+
+function TFPLessThanOperation.AsString: string;
+begin
+  Result:=Left.AsString+' < '+Right.AsString;
+end;
+
+procedure TFPLessThanOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtInteger  : Result.resBoolean:=Result.ResInteger<RRes.ResInteger;
+    rtFloat    : Result.resBoolean:=Result.ResFloat<RRes.ResFLoat;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime<RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString<RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPGreaterThanOperation }
+
+function TFPGreaterThanOperation.AsString: string;
+begin
+  Result:=Left.AsString+' > '+Right.AsString;
+end;
+
+Procedure TFPGreaterThanOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  Case Result.ResultType of
+    rtInteger : case Right.NodeType of
+                  rtInteger : Result.resBoolean:=Result.ResInteger>RRes.ResInteger;
+                  rtFloat : Result.resBoolean:=Result.ResInteger>RRes.ResFloat;
+                end;
+    rtFloat   : case Right.NodeType of
+                  rtInteger : Result.resBoolean:=Result.ResFloat>RRes.ResInteger;
+                  rtFloat : Result.resBoolean:=Result.ResFloat>RRes.ResFLoat;
+                end;
+    rtDateTime : Result.resBoolean:=Result.ResDateTime>RRes.ResDateTime;
+    rtString   : Result.resBoolean:=Result.ResString>RRes.ResString;
+  end;
+  Result.ResultType:=rtBoolean;
+end;
+
+{ TFPGreaterThanEqualOperation }
+
+function TFPGreaterThanEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' >= '+Right.AsString;
+end;
+
+Procedure TFPGreaterThanEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+{ TFPLessThanEqualOperation }
+
+function TFPLessThanEqualOperation.AsString: string;
+begin
+  Result:=Left.AsString+' <= '+Right.AsString;
+end;
+
+Procedure TFPLessThanEqualOperation.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Inherited GetNodeValue(Result);
+  Result.ResBoolean:=Not Result.ResBoolean;
+end;
+
+{ TFPOrderingOperation }
+
+procedure TFPOrderingOperation.Check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime,rtString];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited Check;
+end;
+
+{ TMathOperation }
+
+procedure TMathOperation.Check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime,rtString];
+
+begin
+  inherited Check;
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  CheckSameNodeTypes;
+end;
+
+function TMathOperation.NodeType: TResultType;
+begin
+  Result:=Left.NodeType;
+end;
+
+{ TFPAddOperation }
+
+function TFPAddOperation.AsString: string;
+begin
+  Result:=Left.AsString+' + '+Right.asString;
+end;
+
+Procedure TFPAddOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger+RRes.ResInteger;
+    rtString   : Result.ResString:=Result.ResString+RRes.ResString;
+    rtDateTime : Result.ResDateTime:=Result.ResDateTime+RRes.ResDateTime;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat+RRes.ResFLoat;
+  end;
+  Result.ResultType:=NodeType;
+end;
+
+{ TFPSubtractOperation }
+
+procedure TFPSubtractOperation.check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat,rtDateTime];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited check;
+end;
+
+function TFPSubtractOperation.AsString: string;
+begin
+  Result:=Left.AsString+' - '+Right.asString;
+end;
+
+Procedure TFPSubtractOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger-RRes.ResInteger;
+    rtDateTime : Result.ResDateTime:=Result.ResDateTime-RRes.ResDateTime;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat-RRes.ResFLoat;
+  end;
+end;
+
+{ TFPMultiplyOperation }
+
+procedure TFPMultiplyOperation.check;
+
+Const
+  AllowedTypes =[rtInteger,rtfloat];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  Inherited;
+end;
+
+function TFPMultiplyOperation.AsString: string;
+begin
+  Result:=Left.AsString+' * '+Right.asString;
+end;
+
+Procedure TFPMultiplyOperation.GetNodeValue(var Result : TFPExpressionResult);
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResInteger:=Result.ResInteger*RRes.ResInteger;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat*RRes.ResFLoat;
+  end;
+end;
+
+{ TFPDivideOperation }
+
+procedure TFPDivideOperation.check;
+Const
+  AllowedTypes =[rtInteger,rtfloat];
+
+begin
+  CheckNodeType(Left,AllowedTypes);
+  CheckNodeType(Right,AllowedTypes);
+  inherited check;
+end;
+
+function TFPDivideOperation.AsString: string;
+begin
+  Result:=Left.AsString+' / '+Right.asString;
+end;
+
+function TFPDivideOperation.NodeType: TResultType;
+begin
+  Result:=rtFLoat;
+end;
+
+Procedure TFPDivideOperation.GetNodeValue(var Result : TFPExpressionResult);
+
+Var
+  RRes : TFPExpressionResult;
+
+begin
+  Left.GetNodeValue(Result);
+  Right.GetNodeValue(RRes);
+  case Result.ResultType of
+    rtInteger  : Result.ResFloat:=Result.ResInteger/RRes.ResInteger;
+    rtFloat    : Result.ResFLoat:=Result.ResFLoat/RRes.ResFLoat;
+  end;
+  Result.ResultType:=rtFloat;
+end;
+
+{ TFPConvertNode }
+
+function TFPConvertNode.AsString: String;
+begin
+  Result:=Operand.AsString;
+end;
+
+{ TIntToFloatNode }
+
+procedure TIntConvertNode.Check;
+begin
+  inherited Check;
+  CheckNodeType(Operand,[rtInteger])
+end;
+
+function TIntToFloatNode.NodeType: TResultType;
+begin
+  Result:=rtFloat;
+end;
+
+Procedure TIntToFloatNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Result.ResFloat:=Result.ResInteger;
+  Result.ResultType:=rtFloat;
+end;
+
+
+{ TIntToDateTimeNode }
+
+function TIntToDateTimeNode.NodeType: TResultType;
+begin
+  Result:=rtDatetime;
+end;
+
+procedure TIntToDateTimeNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetnodeValue(Result);
+  Result.ResDateTime:=Result.ResInteger;
+  Result.ResultType:=rtDateTime;
+end;
+
+{ TFloatToDateTimeNode }
+
+procedure TFloatToDateTimeNode.Check;
+begin
+  inherited Check;
+  CheckNodeType(Operand,[rtFloat]);
+end;
+
+function TFloatToDateTimeNode.NodeType: TResultType;
+begin
+  Result:=rtDateTime;
+end;
+
+Procedure TFloatToDateTimeNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  Operand.GetNodeValue(Result);
+  Result.ResDateTime:=Result.ResFloat;
+  Result.ResultType:=rtDateTime;
+end;
+
+{ TFPExprIdentifierNode }
+
+constructor TFPExprIdentifierNode.CreateIdentifier(AID: TFPExprIdentifierDef);
+begin
+  Inherited Create;
+  FID:=AID;
+  PResult:[email protected];
+  FResultType:=FID.ResultType;
+end;
+
+function TFPExprIdentifierNode.NodeType: TResultType;
+begin
+  Result:=FResultType;
+end;
+
+Procedure TFPExprIdentifierNode.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  if Identifier.EventBasedVariable then
+    Identifier.FetchValue;
+  Result:=PResult^;
+  Result.ResultType:=FResultType;
+end;
+
+{ TFPExprVariable }
+
+procedure TFPExprVariable.Check;
+begin
+  // Do nothing;
+end;
+
+function TFPExprVariable.AsString: string;
+begin
+  Result:=FID.Name;
+end;
+
+{ TFPExprFunction }
+
+procedure TFPExprFunction.CalcParams;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgumentParams)-1 do
+    begin
+    FArgumentNodes[i].GetNodeValue(FArgumentParams[i]);
+    end;
+end;
+
+procedure TFPExprFunction.Check;
+
+Var
+  I : Integer;
+  rtp,rta : TResultType;
+
+begin
+  If Length(FArgumentNodes)<>FID.ArgumentCount then
+    RaiseParserError(ErrInvalidArgumentCount,[FID.Name]);
+  For I:=0 to Length(FArgumentNodes)-1 do
+    begin
+    rtp:=CharToResultType(FID.ParameterTypes[i+1]);
+    rta:=FArgumentNodes[i].NodeType;
+    If (rtp<>rta) then begin
+
+      // Automatically convert integers to floats in functions that return
+      // a float
+      if (rta = rtInteger) and (rtp = rtFloat) then begin
+        FArgumentNodes[i] := TIntToFloatNode.Create(FArgumentNodes[i]);
+        exit;
+      end;
+
+      RaiseParserError(SErrInvalidArgumentType,[I+1,ResultTypeName(rtp),ResultTypeName(rta)])
+    end;
+    end;
+end;
+
+constructor TFPExprFunction.CreateFunction(AID: TFPExprIdentifierDef;
+  const Args: TExprArgumentArray);
+begin
+  Inherited CreateIdentifier(AID);
+  FArgumentNodes:=Args;
+  SetLength(FArgumentParams,Length(Args));
+end;
+
+destructor TFPExprFunction.Destroy;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to Length(FArgumentNodes)-1 do
+    FreeAndNil(FArgumentNodes[I]);
+  inherited Destroy;
+end;
+
+function TFPExprFunction.AsString: String;
+
+Var
+  S : String;
+  I : Integer;
+
+begin
+  S:='';
+  For I:=0 to length(FArgumentNodes)-1 do
+    begin
+    If (S<>'') then
+      S:=S+',';
+    S:=S+FArgumentNodes[I].AsString;
+    end;
+  If (S<>'') then
+    S:='('+S+')';
+  Result:=FID.Name+S;
+end;
+
+{ TFPFunctionCallBack }
+
+constructor TFPFunctionCallBack.CreateFunction(AID: TFPExprIdentifierDef;
+  Const Args : TExprArgumentArray);
+begin
+  Inherited;
+  FCallBack:=AID.OnGetFunctionValueCallBack;
+end;
+
+Procedure TFPFunctionCallBack.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  If Length(FArgumentParams)>0 then
+    CalcParams;
+
+  FCallBack(Result,FArgumentParams);
+  Result.ResultType:=NodeType;
+end;
+
+{ TFPFunctionEventHandler }
+
+constructor TFPFunctionEventHandler.CreateFunction(AID: TFPExprIdentifierDef;
+  Const Args : TExprArgumentArray);
+begin
+  Inherited;
+  FCallBack:=AID.OnGetFunctionValue;
+end;
+
+Procedure TFPFunctionEventHandler.GetNodeValue(var Result : TFPExpressionResult);
+begin
+  If Length(FArgumentParams)>0 then
+    CalcParams;
+  FCallBack(Result,FArgumentParams);
+  Result.ResultType:=NodeType;
+end;
+
+{ ---------------------------------------------------------------------
+  Standard Builtins support
+  ---------------------------------------------------------------------}
+
+{ Template for builtin.
+
+Procedure MyCallback (Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+end;
+
+}
+
+function ArgToFloat(Arg: TFPExpressionResult): TExprFloat;
+// Utility function for the built-in math functions. Accepts also integers
+// in place of the floating point arguments. To be called in builtins or
+// user-defined callbacks having float results.
+begin
+  if Arg.ResultType = rtInteger then
+    result := Arg.resInteger
+  else
+    result := Arg.resFloat;
+end;
+
+// Math builtins
+
+Procedure BuiltInCos(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Cos(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInSin(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sin(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInArcTan(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Arctan(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInAbs(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Abs(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInSqr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sqr(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInSqrt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Sqrt(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInExp(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Exp(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInLn(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Ln(ArgToFloat(Args[0]));
+end;
+
+Const
+  L10 = ln(10);
+
+Procedure BuiltInLog(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Ln(ArgToFloat(Args[0]))/L10;
+end;
+
+Procedure BuiltInRound(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Round(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInTrunc(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Trunc(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInInt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=Int(ArgToFloat(Args[0]));
+end;
+
+Procedure BuiltInFrac(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resFloat:=frac(ArgToFloat(Args[0]));
+end;
+
+// String builtins
+
+Procedure BuiltInLength(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Length(Args[0].resString);
+end;
+
+Procedure BuiltInCopy(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Copy(Args[0].resString,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInDelete(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Args[0].resString;
+  Delete(Result.resString,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInPos(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=Pos(Args[0].resString,Args[1].resString);
+end;
+
+Procedure BuiltInUppercase(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Uppercase(Args[0].resString);
+end;
+
+Procedure BuiltInLowercase(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resString:=Lowercase(Args[0].resString);
+end;
+
+Procedure BuiltInStringReplace(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  F : TReplaceFlags;
+
+begin
+  F:=[];
+  If Args[3].resBoolean then
+    Include(F,rfReplaceAll);
+  If Args[4].resBoolean then
+    Include(F,rfIgnoreCase);
+  Result.resString:=StringReplace(Args[0].resString,Args[1].resString,Args[2].resString,f);
+end;
+
+Procedure BuiltInCompareText(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=CompareText(Args[0].resString,Args[1].resString);
+end;
+
+// Date/Time builtins
+
+Procedure BuiltInDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Date;
+end;
+
+Procedure BuiltInTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Time;
+end;
+
+Procedure BuiltInNow(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resDateTime:=Now;
+end;
+
+Procedure BuiltInDayofWeek(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  Result.resInteger:=DayOfWeek(Args[0].resDateTime);
+end;
+
+Procedure BuiltInExtractYear(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=Y;
+end;
+
+Procedure BuiltInExtractMonth(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=M;
+end;
+
+Procedure BuiltInExtractDay(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Args[0].resDateTime,Y,M,D);
+  Result.resInteger:=D;
+end;
+
+Procedure BuiltInExtractHour(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=H;
+end;
+
+Procedure BuiltInExtractMin(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=M;
+end;
+
+Procedure BuiltInExtractSec(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=S;
+end;
+
+Procedure BuiltInExtractMSec(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+Var
+  H,M,S,MS : Word;
+
+begin
+  DecodeTime(Args[0].resDateTime,H,M,S,MS);
+  Result.resInteger:=MS;
+end;
+
+Procedure BuiltInEncodedate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=Encodedate(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger);
+end;
+
+Procedure BuiltInEncodeTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=EncodeTime(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger,Args[3].resInteger);
+end;
+
+Procedure BuiltInEncodeDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=EncodeDate(Args[0].resInteger,Args[1].resInteger,Args[2].resInteger)
+                     +EncodeTime(Args[3].resInteger,Args[4].resInteger,Args[5].resInteger,Args[6].resInteger);
+end;
+
+Procedure BuiltInShortDayName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DefaultFormatSettings.ShortDayNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInShortMonthName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DefaultFormatSettings.ShortMonthNames[Args[0].resInteger];
+end;
+Procedure BuiltInLongDayName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DefaultFormatSettings.LongDayNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInLongMonthName(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DefaultFormatSettings.LongMonthNames[Args[0].resInteger];
+end;
+
+Procedure BuiltInFormatDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=FormatDateTime(Args[0].resString,Args[1].resDateTime);
+end;
+
+
+// Conversion
+Procedure BuiltInIntToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=IntToStr(Args[0].resinteger);
+end;
+
+Procedure BuiltInStrToInt(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=StrToInt(Args[0].resString);
+end;
+
+Procedure BuiltInStrToIntDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=StrToIntDef(Args[0].resString,Args[1].resInteger);
+end;
+
+Procedure BuiltInFloatToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=FloatToStr(Args[0].resFloat);
+end;
+
+Procedure BuiltInStrToFloat(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=StrToFloat(Args[0].resString);
+end;
+
+Procedure BuiltInStrToFloatDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resFloat:=StrToFloatDef(Args[0].resString,Args[1].resFloat);
+end;
+
+Procedure BuiltInDateToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=DateToStr(Args[0].resDateTime);
+end;
+
+Procedure BuiltInTimeToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=TimeToStr(Args[0].resDateTime);
+end;
+
+Procedure BuiltInStrToDate(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDate(Args[0].resString);
+end;
+
+Procedure BuiltInStrToDateDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+Procedure BuiltInStrToTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToTime(Args[0].resString);
+end;
+
+Procedure BuiltInStrToTimeDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToTimeDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+Procedure BuiltInStrToDateTime(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateTime(Args[0].resString);
+end;
+
+Procedure BuiltInStrToDateTimeDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resDateTime:=StrToDateTimeDef(Args[0].resString,Args[1].resDateTime);
+end;
+
+procedure BuiltInFormatFloat(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+begin
+  result.ResString := FormatFloat(Args[0].resString, Args[1].ResFloat);
+end;
+
+Procedure BuiltInBoolToStr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resString:=BoolToStr(Args[0].resBoolean);
+end;
+
+Procedure BuiltInStrToBool(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=StrToBool(Args[0].resString);
+end;
+
+Procedure BuiltInStrToBoolDef(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resBoolean:=StrToBoolDef(Args[0].resString,Args[1].resBoolean);
+end;
+
+// Boolean
+Procedure BuiltInShl(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger shl Args[1].resInteger
+end;
+
+Procedure BuiltInShr(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  Result.resInteger:=Args[0].resInteger shr Args[1].resInteger
+end;
+
+Procedure BuiltinIFS(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resString:=Args[1].resString
+  else
+    Result.resString:=Args[2].resString
+end;
+
+Procedure BuiltinIFI(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resinteger:=Args[1].resinteger
+  else
+    Result.resinteger:=Args[2].resinteger
+end;
+
+Procedure BuiltinIFF(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resfloat:=Args[1].resfloat
+  else
+    Result.resfloat:=Args[2].resfloat
+end;
+
+Procedure BuiltinIFD(Var Result : TFPExpressionResult; Const Args : TExprParameterArray);
+
+begin
+  If Args[0].resBoolean then
+    Result.resDateTime:=Args[1].resDateTime
+  else
+    Result.resDateTime:=Args[2].resDateTime
+end;
+
+procedure RegisterStdBuiltins(AManager: TExprBuiltInManager;  Categories: TBuiltInCategories = AllBuiltIns);
+
+begin
+  With AManager do
+    begin
+    if bcMath in Categories then
+      begin
+      AddFloatVariable(bcMath,'pi',Pi);
+      // Math functions
+      AddFunction(bcMath,'cos','F','F',@BuiltinCos);
+      AddFunction(bcMath,'sin','F','F',@BuiltinSin);
+      AddFunction(bcMath,'arctan','F','F',@BuiltinArctan);
+      AddFunction(bcMath,'abs','F','F',@BuiltinAbs);
+      AddFunction(bcMath,'sqr','F','F',@BuiltinSqr);
+      AddFunction(bcMath,'sqrt','F','F',@BuiltinSqrt);
+      AddFunction(bcMath,'exp','F','F',@BuiltinExp);
+      AddFunction(bcMath,'ln','F','F',@BuiltinLn);
+      AddFunction(bcMath,'log','F','F',@BuiltinLog);
+      AddFunction(bcMath,'frac','F','F',@BuiltinFrac);
+      AddFunction(bcMath,'int','F','F',@BuiltinInt);
+      AddFunction(bcMath,'round','I','F',@BuiltinRound);
+      AddFunction(bcMath,'trunc','I','F',@BuiltinTrunc);
+      end;
+    if bcStrings in Categories then
+      begin
+      // String
+      AddFunction(bcStrings,'length','I','S',@BuiltinLength);
+      AddFunction(bcStrings,'copy','S','SII',@BuiltinCopy);
+      AddFunction(bcStrings,'delete','S','SII',@BuiltinDelete);
+      AddFunction(bcStrings,'pos','I','SS',@BuiltinPos);
+      AddFunction(bcStrings,'lowercase','S','S',@BuiltinLowercase);
+      AddFunction(bcStrings,'uppercase','S','S',@BuiltinUppercase);
+      AddFunction(bcStrings,'stringreplace','S','SSSBB',@BuiltinStringReplace);
+      AddFunction(bcStrings,'comparetext','I','SS',@BuiltinCompareText);
+      end;
+    if bcDateTime in Categories then
+      begin
+      // Date/Time
+      AddFunction(bcDateTime,'date','D','',@BuiltinDate);
+      AddFunction(bcDateTime,'time','D','',@BuiltinTime);
+      AddFunction(bcDateTime,'now','D','',@BuiltinNow);
+      AddFunction(bcDateTime,'dayofweek','I','D',@BuiltinDayofweek);
+      AddFunction(bcDateTime,'extractyear','I','D',@BuiltinExtractYear);
+      AddFunction(bcDateTime,'extractmonth','I','D',@BuiltinExtractMonth);
+      AddFunction(bcDateTime,'extractday','I','D',@BuiltinExtractDay);
+      AddFunction(bcDateTime,'extracthour','I','D',@BuiltinExtractHour);
+      AddFunction(bcDateTime,'extractmin','I','D',@BuiltinExtractMin);
+      AddFunction(bcDateTime,'extractsec','I','D',@BuiltinExtractSec);
+      AddFunction(bcDateTime,'extractmsec','I','D',@BuiltinExtractMSec);
+      AddFunction(bcDateTime,'encodedate','D','III',@BuiltinEncodedate);
+      AddFunction(bcDateTime,'encodetime','D','IIII',@BuiltinEncodeTime);
+      AddFunction(bcDateTime,'encodedatetime','D','IIIIIII',@BuiltinEncodeDateTime);
+      AddFunction(bcDateTime,'shortdayname','S','I',@BuiltinShortDayName);
+      AddFunction(bcDateTime,'shortmonthname','S','I',@BuiltinShortMonthName);
+      AddFunction(bcDateTime,'longdayname','S','I',@BuiltinLongDayName);
+      AddFunction(bcDateTime,'longmonthname','S','I',@BuiltinLongMonthName);
+      AddFunction(bcDateTime,'formatdatetime','S','SD',@BuiltinFormatDateTime);
+      end;
+    if bcBoolean in Categories then
+      begin
+      // Boolean
+      AddFunction(bcBoolean,'shl','I','II',@BuiltinShl);
+      AddFunction(bcBoolean,'shr','I','II',@BuiltinShr);
+      AddFunction(bcBoolean,'IFS','S','BSS',@BuiltinIFS);
+      AddFunction(bcBoolean,'IFF','F','BFF',@BuiltinIFF);
+      AddFunction(bcBoolean,'IFD','D','BDD',@BuiltinIFD);
+      AddFunction(bcBoolean,'IFI','I','BII',@BuiltinIFI);
+      end;
+    if (bcConversion in Categories) then
+      begin
+      // Conversion
+      AddFunction(bcConversion,'inttostr','S','I',@BuiltInIntToStr);
+      AddFunction(bcConversion,'strtoint','I','S',@BuiltInStrToInt);
+      AddFunction(bcConversion,'strtointdef','I','SI',@BuiltInStrToIntDef);
+      AddFunction(bcConversion,'floattostr','S','F',@BuiltInFloatToStr);
+      AddFunction(bcConversion,'strtofloat','F','S',@BuiltInStrToFloat);
+      AddFunction(bcConversion,'strtofloatdef','F','SF',@BuiltInStrToFloatDef);
+      AddFunction(bcConversion,'booltostr','S','B',@BuiltInBoolToStr);
+      AddFunction(bcConversion,'strtobool','B','S',@BuiltInStrToBool);
+      AddFunction(bcConversion,'strtobooldef','B','SB',@BuiltInStrToBoolDef);
+      AddFunction(bcConversion,'datetostr','S','D',@BuiltInDateToStr);
+      AddFunction(bcConversion,'timetostr','S','D',@BuiltInTimeToStr);
+      AddFunction(bcConversion,'strtodate','D','S',@BuiltInStrToDate);
+      AddFunction(bcConversion,'strtodatedef','D','SD',@BuiltInStrToDateDef);
+      AddFunction(bcConversion,'strtotime','D','S',@BuiltInStrToTime);
+      AddFunction(bcConversion,'strtotimedef','D','SD',@BuiltInStrToTimeDef);
+      AddFunction(bcConversion,'strtodatetime','D','S',@BuiltInStrToDateTime);
+      AddFunction(bcConversion,'strtodatetimedef','D','SD',@BuiltInStrToDateTimeDef);
+      AddFunction(bcConversion,'formatfloat','S','SF',@BuiltInFormatFloat);
+      end;
+    if bcAggregate in Categories then
+      begin
+      AddFunction(bcAggregate,'count','I','',TAggregateCount);
+      AddFunction(bcAggregate,'sum','F','F',TAggregateSum);
+      AddFunction(bcAggregate,'avg','F','F',TAggregateAvg);
+      AddFunction(bcAggregate,'min','F','F',TAggregateMin);
+      AddFunction(bcAggregate,'max','F','F',TAggregateMax);
+      end;
+    end;
+end;
+
+{ TFPBuiltInExprIdentifierDef }
+
+procedure TFPBuiltInExprIdentifierDef.Assign(Source: TPersistent);
+begin
+  inherited Assign(Source);
+  If Source is TFPBuiltInExprIdentifierDef then
+    FCategory:=(Source as TFPBuiltInExprIdentifierDef).Category;
+end;
+
+initialization
+  RegisterStdBuiltins(BuiltinIdentifiers);
+
+finalization
+  FreeBuiltins;
+end.

+ 9517 - 0
packages/fcl-report/src/fpreport.pp

@@ -0,0 +1,9517 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2008 Michael Van Canneyt, member of the Free Pascal development team
+    Portions (c) 2016 WISA b.v.b.a.
+
+    GUI independent reporting engine core
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit fpreport;
+
+{$mode objfpc}{$H+}
+{$inline on}
+
+{.$define gdebug}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  Variants,
+  contnrs,
+  fpCanvas,
+  fpImage,
+  fpreportstreamer,
+{$IF FPC_FULLVERSION>=30101}
+  fpexprpars,
+{$ELSE}
+  fprepexprpars,
+{$ENDIF}
+  fpReportHTMLParser;
+
+type
+
+  // Do not use other types than the ones below in fpreport.
+  TFPReportString = string;
+  TFPReportUnits  = single; // Units are defined as Millimetres
+  TFPReportScale  = single;
+  TFPReportColor  = type UInt32;
+
+  // A position in report units
+  TFPReportPoint = record
+    Top: TFPReportUnits;
+    Left: TFPReportUnits;
+  end;
+
+  // A rectangle in report units (measures)
+
+  { TFPReportRect }
+
+  TFPReportRect = object  // not a class for static allocations. Not a record because we want methods
+    Top: TFPReportUnits;
+    Left: TFPReportUnits;
+    Width: TFPReportUnits;
+    Height: TFPReportUnits;
+    procedure SetRect(aleft, atop, awidth, aheight: TFPReportUnits);
+    Procedure OffsetRect(aLeft,ATop : TFPReportUnits);
+    Function IsEmpty : Boolean;
+    function Bottom: TFPReportUnits;
+    function Right: TFPReportUnits;
+    function AsString : String;
+  end;
+
+
+  // Scaling factors (mostly for zoom/resize)
+  TFPReportScales = record
+    Vertical: TFPreportScale;
+    Horizontal: TFPreportScale;
+  end;
+
+
+  // Forward declarations
+  TFPReportElement        = class;
+  TFPCustomReport         = class;
+  TFPReportCustomBand     = class;
+  TFPReportCustomPage     = class;
+  TFPReportCustomGroupFooterBand = class;
+  TFPReportData           = class;
+  TFPReportFrame          = class;
+  TFPReportCustomMemo     = class;
+  TFPReportChildBand      = class;
+  TFPReportCustomDataBand = class;
+  TFPReportCustomDataHeaderBand = class;
+  TFPReportCustomDataFooterBand = class;
+  TFPReportCustomGroupHeaderBand = class;
+  TFPReportExporter       = class;
+  TFPReportTextAlignment  = class;
+  TBandList = class;
+
+  TFPReportElementClass   = class of TFPReportElement;
+  TFPReportBandClass      = class of TFPReportCustomBand;
+
+  TFPReportState          = (rsDesign, rsLayout, rsRender);
+  TFPReportPaperOrientation = (poPortrait, poLandscape);
+  TFPReportVertTextAlignment = (tlTop, tlCenter, tlBottom);
+  TFPReportHorzTextAlignment = (taLeftJustified, taRightJustified, taCentered, taWidth);
+  TFPReportShapeType      = (stEllipse, stCircle, stLine, stSquare, stTriangle, stRoundedRect{, stArrow});  // rectangle can be handled by Frame
+  TFPReportOrientation    = (orNorth, orNorthEast, orEast, orSouthEast, orSouth, orSouthWest, orWest, orNorthWest);
+  TFPReportFrameLine      = (flTop, flBottom, flLeft, flRight);
+  TFPReportFrameLines     = set of TFPReportFrameLine;
+  TFPReportFrameShape     = (fsNone, fsRectangle, fsRoundedRect, fsDoubleRect, fsShadow);
+  TFPReportFieldKind      = (rfkString, rfkBoolean, rfkInteger, rfkFloat, rfkDateTime, rfkStream);
+  TFPReportStretchMode    = (smDontStretch, smActualHeight, smMaxHeight);
+  TFPReportHTMLTag        = (htRegular, htBold, htItalic);
+  TFPReportHTMLTagSet     = set of TFPReportHTMLTag;
+  TFPReportColumnLayout   = (clVertical, clHorizontal);
+  TFPReportFooterPosition = (fpAfterLast, fpColumnBottom);
+  TFPReportVisibleOnPage  = (vpAll, vpFirstOnly, vpLastOnly, vpFirstAndLastOnly, vpNotOnFirst, vpNotOnLast, vpNotOnFirstAndLast);
+  // For color coding
+  TFPReportBandType       = (btUnknown,btPageHeader,btReportTitle,btColumnHeader,btDataHeader,btGroupHeader,btDataband,btGroupFooter,
+                             btDataFooter,btColumnFooter,btReportSummary,btPageFooter,btChild);
+  TFPReportMemoOption     = (
+            moSuppressRepeated,
+            moHideZeros,
+            moDisableExpressions,
+            moAllowHTML,
+            moDisableWordWrap,
+            moNoResetAggregateOnPrint,
+            moResetAggregateOnGroup,
+            moResetAggregateOnPage,
+            moResetAggregateOnColumn
+            );
+  TFPReportMemoOptions    = set of TFPReportMemoOption;
+
+const
+  { The format is always RRGGBB (Red, Green, Blue) - no alpha channel }
+  clNone          = TFPReportColor($80000000);  // a special condition: $80 00 00 00
+  { commonly known colors }
+  clAqua          = TFPReportColor($00FFFF);
+  clBlack         = TFPReportColor($000000);
+  clBlue          = TFPReportColor($0000FF);
+  clCream         = TFPReportColor($FFFBF0);
+  clDkGray        = TFPReportColor($A9A9A9);
+  clFuchsia       = TFPReportColor($FF00FF);
+  clGray          = TFPReportColor($808080);
+  clGreen         = TFPReportColor($008000);
+  clLime          = TFPReportColor($00FF00);
+  clLtGray        = TFPReportColor($C0C0C0);
+  clMaroon        = TFPReportColor($800000);
+  clNavy          = TFPReportColor($000080);
+  clOlive         = TFPReportColor($808000);
+  clPurple        = TFPReportColor($800080);
+  clRed           = TFPReportColor($FF0000);
+  clDkRed         = TFPReportColor($C00000);
+  clSilver        = TFPReportColor($C0C0C0);
+  clTeal          = TFPReportColor($008080);
+  clWhite         = TFPReportColor($FFFFFF);
+  clYellow        = TFPReportColor($FFFF00);
+  { some common alias colors }
+  clCyan          = clAqua;
+  clMagenta       = clFuchsia;
+
+const
+  { Some color constants used throughout the demos, designer and documentation. }
+  clPageHeaderFooter    = TFPReportColor($E4E4E4);
+  clReportTitleSummary  = TFPReportColor($63CF80);
+  clGroupHeaderFooter   = TFPReportColor($FFF1D7);
+  clColumnHeaderFooter  = TFPReportColor($FF8E62);
+  clDataHeaderFooter    = TFPReportColor($CBD5EC);
+  clDataBand            = TFPReportColor($89B7EA);
+  clChildBand           = TFPReportColor($B4DFFF);
+
+
+  DefaultBandColors : Array[TFPReportBandType] of TFPReportColor = (
+    clNone,                 // Unknown
+    clPageHeaderFooter,     // Page header
+    clReportTitleSummary,   // Report Title
+    clColumnHeaderFooter,   // Column header
+    clDataHeaderFooter,     // Data header
+    clGroupHeaderFooter,    // Group header
+    clDataBand,             // Databand
+    clGroupHeaderFooter,    // Group footer
+    clDataHeaderFooter,     // Data footer
+    clColumnHeaderFooter,   // Column footer
+    clReportTitleSummary,   // Report summary
+    clPageHeaderFooter,     // Page footer
+    clChildBand             // Child
+  );
+
+  clDarkMoneyGreen = TFPReportColor($A0BCA0);
+
+  { These are default values, but replaced with darker version of DefaultBandColors[] }
+  DefaultBandRectangleColors : Array[TFPReportBandType] of TFPReportColor = (
+    clNone,              // Unknown
+    clDarkMoneyGreen,    // Page header
+    cldkGray,            // Report Title
+    clDarkMoneyGreen,    // Column header
+    clDarkMoneyGreen,    // Data header
+    clDarkMoneyGreen,    // Group header
+    clBlue,              // Databand
+    clDarkMoneyGreen,    // Group footer
+    clDarkMoneyGreen,    // Data footer
+    clDarkMoneyGreen,    // Column footer
+    clDarkMoneyGreen,    // Report summary
+    clDarkMoneyGreen,    // Page footer
+    clDkGray             // Child
+  );
+
+const
+  cMMperInch = 25.4;
+  cCMperInch = 2.54;
+  cMMperCM = 10;
+  DefaultBandNames : Array[TFPReportBandType] of string
+    = ('Unknown','Page Header','Report Title','Column Header', 'Data Header','Group Header','Data','Group Footer',
+       'Data Footer','Column Footer','Report Summary','PageFooter','Child');
+
+type
+  // Event handlers
+  TFPReportGetEOFEvent      = procedure(Sender: TObject; var IsEOF: boolean) of object;
+  TFPReportGetValueEvent    = procedure(Sender: TObject; const AValueName: string; var AValue: variant) of object;
+  TFPReportBeginReportEvent = procedure of object;
+  TFPReportEndReportEvent   = procedure of object;
+  TFPReportGetValueNamesEvent = procedure(Sender: TObject; List: TStrings) of object;
+  TFPReportBeforePrintEvent = procedure(Sender: TFPReportElement) of object;
+
+
+  TFPReportExporterConfigHandler = Procedure (Sender : TObject; AExporter : TFPReportExporter; var Cancelled : Boolean) of object;
+
+  { TFPReportExporter }
+
+  TFPReportExporter = class(TComponent)
+  private
+    FAutoRun: Boolean;
+    FBaseFileName: string;
+    FPReport: TFPCustomReport;
+    procedure SetFPReport(AValue: TFPCustomReport);
+  protected
+    procedure SetBaseFileName(AValue: string); virtual;
+    Procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    procedure DoExecute(const ARTObjects: TFPList); virtual; abstract;
+    // Override this to render an image on the indicated position. If AImage is non-nil on return, it will be freed by caller.
+    Procedure RenderImage(aPos : TFPReportRect; var AImage: TFPCustomImage) ; virtual;
+    Procedure RenderUnknownElement(aBasePos : TFPReportPoint; AElement : TFPReportElement; ADPI : Integer);
+    Class function DefaultConfig : TFPReportExporterConfigHandler; virtual;
+  public
+    procedure Execute;
+    // Descendents can treat this as a hint to set the filename.
+    Procedure SetFileName(Const aFileName : String);virtual;
+    Class Procedure RegisterExporter;
+    Class Procedure UnRegisterExporter;
+    Class Function Description : String; virtual;
+    Class Function Name : String; virtual;
+    // DefaultExtension should return non-empty if output is file based.
+    // Must contain .
+    Class Function DefaultExtension : String; virtual;
+    Function ShowConfig : Boolean;
+  Published
+    Property AutoRun : Boolean Read FAutoRun Write FAutoRun;
+    property Report: TFPCustomReport read FPReport write SetFPReport;
+  end;
+  TFPReportExporterClass = Class of TFPReportExporter;
+
+
+  // Width & Height are in portrait position, units: millimetres.
+  TFPReportPaperSize = class(TObject)
+  private
+    FWidth: TFPReportUnits;
+    FHeight: TFPReportUnits;
+  public
+    constructor Create(const AWidth, AHeight: TFPReportUnits);
+    property Width: TFPReportUnits read FWidth;
+    property Height: TFPReportUnits read FHeight;
+  end;
+
+
+  TFPReportFont = class(TPersistent)
+  private
+    FFontName: string;
+    FFontSize: integer;
+    FFontColor: TFPReportColor;
+    procedure   SetFontName(const avalue: string);
+    procedure   SetFontSize(const avalue: integer);
+    procedure   SetFontColor(const avalue: TFPReportColor);
+  public
+    constructor Create; virtual;
+    procedure   Assign(Source: TPersistent); override;
+    property    Name: string read FFontName write SetFontName;
+    { value is in font Point units }
+    property    Size: integer read FFontSize write SetFontSize default 10;
+    property    Color: TFPReportColor read FFontColor write SetFontColor default clBlack;
+  end;
+
+
+  TFPReportComponent = class(TComponent)
+  private
+    FReportState: TFPReportState;
+  protected
+    // called when the layouter starts its job on the report.
+    procedure StartLayout; virtual;
+    // called when the layouter ends its job on the report.
+    procedure EndLayout; virtual;
+    // called when the renderer starts its job on the report.
+    procedure StartRender; virtual;
+    // called when the renderer ends its job on the report.
+    procedure EndRender; virtual;
+  public
+    procedure WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); virtual;
+    procedure ReadElement(AReader: TFPReportStreamer); virtual;
+    property ReportState: TFPReportState read FReportState;
+  end;
+
+
+
+
+  // The Memo text is prepared as one or more TFPTextBlock objects for the report renderers
+  TFPTextBlock = class(TObject)
+  public
+    Pos: TFPReportPoint;
+    Width: TFPReportUnits;
+    Height: TFPReportUnits;
+    Descender: TFPReportUnits;
+    Text: TFPReportString;
+    FontName: string;
+    FGColor: TFPReportColor;
+    BGColor: TFPReportColor;
+  end;
+
+  // Extension of TFPTextBlock with support to hold URL information
+  TFPHTTPTextBlock = class(TFPTextBlock)
+  private
+    FURL: String;
+  public
+    property URL: string read FURL write FURL;
+  end;
+
+
+  TFPTextBlockList = class(TFPObjectList)
+  protected
+    function GetItem(AIndex: Integer): TFPTextBlock; reintroduce;
+    procedure SetItem(AIndex: Integer; AObject: TFPTextBlock); reintroduce;
+  public
+    property Items[AIndex: Integer]: TFPTextBlock read GetItem write SetItem; default;
+  end;
+
+
+  TFPReportDataField = class(TCollectionItem)
+  private
+    FDisplayWidth: integer;
+    FFieldKind: TFPReportFieldKind;
+    FFieldName: string;
+  public
+    function GetValue: variant; virtual;
+    procedure Assign(Source: TPersistent); override;
+  published
+    property FieldName: string read FFieldName write FFieldName;
+    property FieldKind: TFPReportFieldKind read FFieldKind write FFieldKind;
+    property DisplayWidth: integer read FDisplayWidth write FDisplayWidth;
+  end;
+
+
+  TFPReportDataFields = class(TCollection)
+  private
+    FReportData: TFPReportData;
+    function GetF(AIndex: integer): TFPReportDataField;
+    procedure SetF(AIndex: integer; const AValue: TFPReportDataField);
+  public
+    function AddField(AFieldName: string; AFieldKind: TFPReportFieldKind): TFPReportDataField;
+    function IndexOfField(const AFieldName: string): integer;
+    function FindField(const AFieldName: string): TFPReportDataField; overload;
+    function FindField(const AFieldName: string; const AFieldKind: TFPReportFieldKind): TFPReportDataField; overload;
+    function FieldByName(const AFieldName: string): TFPReportDataField;
+    property ReportData: TFPReportData read FReportData;
+    property Fields[AIndex: integer]: TFPReportDataField read GetF write SetF; default;
+  end;
+
+
+  { TFPReportData }
+
+  TFPReportData = class(TFPReportComponent)
+  private
+    FDataFields: TFPReportDataFields;
+    FOnClose: TNotifyEvent;
+    FOnFirst: TNotifyEvent;
+    FOnGetEOF: TFPReportGetEOFEvent;
+    FOnNext: TNotifyEvent;
+    FOnOpen: TNotifyEvent;
+    FRecNo: integer;
+    FIsOpened: boolean; // tracking the state
+    function GetFieldCount: integer;
+    function GetFieldName(Index: integer): string;
+    function GetFieldType(AFieldName: string): TFPReportFieldKind;
+    function GetFieldValue(AFieldName: string): variant;
+    function GetFieldWidth(AFieldName: string): integer;
+    procedure SetDataFields(const AValue: TFPReportDataFields);
+  protected
+    function CreateDataFields: TFPReportDataFields; virtual;
+    procedure DoGetValue(const AFieldName: string; var AValue: variant); virtual;
+    // Fill Datafields Collection. Should not change after Open.
+    procedure DoInitDataFields; virtual;
+    procedure DoOpen; virtual;
+    procedure DoFirst; virtual;
+    procedure DoNext; virtual;
+    procedure DoClose; virtual;
+    function  DoEOF: boolean; virtual;
+    property DataFields: TFPReportDataFields read FDataFields write SetDataFields;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    // Navigation
+    procedure InitFieldDefs;
+    procedure Open;
+    procedure First;
+    procedure Next;
+    procedure Close;
+    function EOF: boolean;
+    //  Public access methods
+    procedure GetFieldList(List: TStrings);
+    Function IndexOfField (const AFieldName: string): Integer;
+    function HasField(const AFieldName: string): boolean;
+    property FieldNames[Index: integer]: string read GetFieldName;
+    property FieldValues[AFieldName: string]: variant read GetFieldValue; default;
+    property FieldWidths[AFieldName: string]: integer read GetFieldWidth;
+    property FieldTypes[AFieldName: string]: TFPReportFieldKind read GetFieldType;
+    property FieldCount: integer read GetFieldCount;
+    property RecNo: integer read FRecNo;
+    property IsOpened: boolean read FIsOpened;
+  published
+    property OnOpen: TNotifyEvent read FOnOpen write FOnOpen;
+    property OnClose: TNotifyEvent read FOnClose write FOnClose;
+    property OnFirst: TNotifyEvent read FOnFirst write FOnFirst;
+    property OnNext: TNotifyEvent read FOnNext write FOnNext;
+    property OnGetEOF: TFPReportGetEOFEvent read FOnGetEOF write FOnGetEOF;
+  end;
+
+
+  TFPReportUserData = class(TFPReportData)
+  private
+    FOnGetValue: TFPReportGetValueEvent;
+    FOnGetNames: TFPReportGetValueNamesEvent;
+  protected
+    procedure DoGetValue(const AFieldName: string; var AValue: variant); override;
+    procedure DoInitDataFields; override;
+  published
+    property DataFields;
+    property OnGetValue: TFPReportGetValueEvent read FOnGetValue write FOnGetValue;
+    property OnGetNames: TFPReportGetValueNamesEvent read FOnGetNames write FOnGetNames;
+  end;
+
+  { TFPReportDataItem }
+
+  TFPReportDataItem = Class(TCollectionItem)
+  private
+    FData: TFPReportData;
+    procedure SetData(AValue: TFPReportData);
+  Protected
+    Function GetDisplayName: string; override;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+  Published
+    property Data : TFPReportData Read FData Write SetData;
+  end;
+
+
+  { TFPReportDataCollection }
+
+  TFPReportDataCollection = Class(TCollection)
+  private
+    function GetData(AIndex : Integer): TFPReportDataItem;
+    procedure SetData(AIndex : Integer; AValue: TFPReportDataItem);
+  Public
+    Function IndexOfReportData(AData : TFPReportData) : Integer;
+    Function IndexOfReportData(Const ADataName : String) : Integer;
+    Function FindReportDataItem(AData : TFPReportData) : TFPReportDataItem;
+    Function FindReportDataItem(Const ADataName : String) : TFPReportDataItem;
+    Function FindReportData(Const ADataName : String) : TFPReportData;
+    function AddReportData(AData: TFPReportData): TFPReportDataItem;
+    Property Data[AIndex : Integer] : TFPReportDataItem Read GetData Write SetData; default;
+  end;
+
+  // The frame around each printable element.
+  TFPReportFrame = class(TPersistent)
+  private
+    FColor: TFPReportColor;
+    FFrameLines: TFPReportFrameLines;
+    FFrameShape: TFPReportFrameShape;
+    FPenStyle: TFPPenStyle;
+    FReportElement: TFPReportElement;
+    FWidth: integer;
+    FBackgroundColor: TFPReportColor;
+    procedure SetColor(const AValue: TFPReportColor);
+    procedure SetFrameLines(const AValue: TFPReportFrameLines);
+    procedure SetFrameShape(const AValue: TFPReportFrameShape);
+    procedure SetPenStyle(const AValue: TFPPenStyle);
+    procedure SetWidth(const AValue: integer);
+    procedure SetBackgrounColor(AValue: TFPReportColor);
+  protected
+    procedure Changed; // Called whenever the visual properties change.
+  public
+    constructor Create(AElement: TFPReportElement);
+    procedure Assign(ASource: TPersistent); override;
+    procedure WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportFrame = nil); virtual;
+    procedure ReadElement(AReader: TFPReportStreamer); virtual;
+    function Equals(AFrame: TFPReportFrame): boolean; reintroduce;
+    property ReportElement: TFPReportElement read FReportElement;
+  published
+    { Lines are only drawn if Shape = fsNone }
+    property Lines: TFPReportFrameLines read FFrameLines write SetFrameLines;
+    property Shape: TFPReportFrameShape read FFrameShape write SetFrameShape default fsNone;
+    { The pen color used for stroking of shapes or for lines. }
+    property Color: TFPReportColor read FColor write SetColor default clNone;
+    { The fill color for shapes - where applicable. }
+    property BackgroundColor: TFPReportColor read FBackgroundColor write SetBackgrounColor default clNone;
+    property Pen: TFPPenStyle read FPenStyle write SetPenStyle default psSolid;
+    { The width of the pen. }
+    property Width: integer read FWidth write SetWidth;
+  end;
+
+
+  TFPReportTextAlignment = class(TPersistent)
+  private
+    FReportElement: TFPReportElement;
+    FHorizontal: TFPReportHorzTextAlignment;
+    FVertical: TFPReportVertTextAlignment;
+    FTopMargin: TFPReportUnits;
+    FBottomMargin: TFPReportUnits;
+    FLeftMargin: TFPReportUnits;
+    FRightMargin: TFPReportUnits;
+    procedure   SetHorizontal(AValue: TFPReportHorzTextAlignment);
+    procedure   SetVertical(AValue: TFPReportVertTextAlignment);
+    procedure   SetTopMargin(AValue: TFPReportUnits);
+    procedure   SetBottomMargin(AValue: TFPReportUnits);
+    procedure   SetLeftMargin(AValue: TFPReportUnits);
+    procedure   SetRightMargin(AValue: TFPReportUnits);
+  protected
+    procedure   Changed; // Called whenever the visual properties change.
+  public
+    constructor Create(AElement: TFPReportElement);
+    procedure   Assign(ASource: TPersistent); override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportTextAlignment = nil); virtual;
+    procedure   ReadElement(AReader: TFPReportStreamer); virtual;
+  published
+    property    Horizontal: TFPReportHorzTextAlignment read FHorizontal write SetHorizontal;
+    property    Vertical: TFPReportVertTextAlignment read FVertical write SetVertical;
+    property    TopMargin: TFPReportUnits read FTopMargin write SetTopMargin default 0;
+    property    BottomMargin: TFPReportUnits read FBottomMargin write SetBottomMargin default 0;
+    property    LeftMargin: TFPReportUnits read FLeftMargin write SetLeftMargin default 1.0;
+    property    RightMargin: TFPReportUnits read FRightMargin write SetRightMargin default 1.0;
+  end;
+
+
+  // Position/Size related properties - this class doesn't notify FReportElement about property changes
+
+  { TFPReportCustomLayout }
+
+  TFPReportCustomLayout = class(TPersistent)
+  private
+    FPos: TFPReportRect;
+    function    GetHeight: TFPreportUnits;
+    function    GetWidth: TFPreportUnits;
+    procedure   SetLeft(const AValue: TFPreportUnits);
+    procedure   SetTop(const AValue: TFPreportUnits);
+    procedure   SetWidth(const AValue: TFPreportUnits);
+    procedure   SetHeight(const AValue: TFPreportUnits);
+    function    GetLeft: TFPreportUnits;
+    function    GetTop: TFPreportUnits;
+  protected
+    FReportElement: TFPReportElement;
+    procedure   Changed; virtual; abstract;// Called whenever the visual properties change.
+    property    Height: TFPreportUnits read GetHeight write SetHeight;
+    property    Left: TFPreportUnits read GetLeft write SetLeft;
+    property    Top: TFPreportUnits read GetTop write SetTop;
+    property    Width: TFPreportUnits read GetWidth write SetWidth;
+  public
+    constructor Create(AElement: TFPReportElement);
+    procedure   Assign(Source: TPersistent); override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportCustomLayout = nil); virtual;
+    procedure   ReadElement(AReader: TFPReportStreamer); virtual;
+    function    Equals(ALayout: TFPReportCustomLayout): boolean; reintroduce;
+    procedure   GetBoundsRect(Out ARect: TFPReportRect);
+    { a convenience function to set all four values in one go }
+    procedure   SetPosition(aleft, atop, awidth, aheight: TFPReportUnits);
+    procedure   SetPosition(Const ARect: TFPReportRect);
+    property    ReportElement: TFPReportElement read FReportElement;
+  end;
+
+
+  // Position/Size related properties. Also notifies FReportElement about property changes
+  TFPReportLayout = class(TFPReportCustomLayout)
+  protected
+    procedure Changed; override;
+  published
+    property  Height;
+    property  Left;
+    property  Top;
+    property  Width;
+  end;
+
+
+  // Anything that must be drawn as part of the report descends from this.
+
+  { TFPReportElement }
+
+  TFPReportElement = class(TFPReportComponent)
+  private
+    FFrame: TFPReportFrame;
+    FLayout: TFPReportLayout;
+    FParent: TFPReportElement;
+    FUpdateCount: integer;
+    FVisible: boolean;
+    FRTLayout: TFPReportLayout;
+    FOnBeforePrint: TFPReportBeforePrintEvent;
+    FStretchMode: TFPReportStretchMode;
+    function GetReport: TFPCustomReport;
+    procedure SetFrame(const AValue: TFPReportFrame);
+    procedure SetLayout(const AValue: TFPReportLayout);
+    procedure SetVisible(const AValue: boolean);
+  protected
+    Procedure SaveDataToNames; virtual;
+    Procedure RestoreDataFromNames; virtual;
+    function CreateFrame: TFPReportFrame; virtual;
+    function CreateLayout: TFPReportLayout; virtual;
+    procedure CreateRTLayout; virtual;
+    procedure SetParent(const AValue: TFPReportElement); virtual;
+    procedure Changed; // Called whenever the visual properties change.
+    procedure DoChanged; virtual; // Called when changed and changecount reaches zero.
+    procedure PrepareObject; virtual;
+    { descendants can add any extra properties to output here }
+    procedure DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); virtual;
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    { triggers OnBeforePrint event }
+    procedure BeforePrint; virtual;
+    { this is run against the runtime (RT) version of this element, and before BeforePrint is called. }
+    procedure RecalcLayout; virtual; abstract;
+    property  StretchMode: TFPReportStretchMode read FStretchMode write FStretchMode default smDontStretch;
+    property  OnBeforePrint: TFPReportBeforePrintEvent read FOnBeforePrint write FOnBeforePrint;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    Function CreatePropertyHash : String; virtual;
+    function Equals(AElement: TFPReportElement): boolean; virtual; reintroduce;
+    procedure WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure ReadElement(AReader: TFPReportStreamer); override;
+    procedure Assign(Source: TPersistent); override;
+    procedure BeginUpdate;
+    procedure EndUpdate;
+    property Parent: TFPReportElement read FParent write SetParent;
+    Property Report : TFPCustomReport read GetReport;
+    { Runtime Layout - populated when layouting of report is calculated. }
+    property  RTLayout: TFPReportLayout read FRTLayout write FRTLayout; // TOOD: Maybe we should rename this to PrintLayout?
+  published
+    property Layout: TFPReportLayout read FLayout write SetLayout;
+    property Frame: TFPReportFrame read FFrame write SetFrame;
+    property Visible: boolean read FVisible write SetVisible;
+  end;
+
+
+  { TFPReportElementWithChildren }
+
+  TFPReportElementWithChildren = class(TFPReportElement)
+  private
+    FChildren: TFPList;
+    function GetChild(AIndex: integer): TFPReportElement;
+    function GetChildCount: integer;
+  protected
+    Procedure SaveDataToNames; override;
+    Procedure RestoreDataFromNames; override;
+    procedure RemoveChild(const AChild: TFPReportElement); virtual;
+    procedure AddChild(const AChild: TFPReportElement); virtual;
+    procedure PrepareObjects; virtual;
+    { This should run against the runtime version of the children }
+    procedure RecalcLayout; override;
+  public
+    destructor  Destroy; override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    function    Equals(AElement: TFPReportElement): boolean; override;
+    property    Child[AIndex: integer]: TFPReportElement read GetChild;
+    property    ChildCount: integer read GetChildCount;
+  end;
+
+
+  TFPReportMargins = class(TPersistent)
+  private
+    FTop: TFPReportUnits;
+    FBottom: TFPReportUnits;
+    FLeft: TFPReportUnits;
+    FRight: TFPReportUnits;
+    FPage: TFPReportCustomPage;
+    procedure   SetBottom(const AValue: TFPReportUnits);
+    procedure   SetLeft(const AValue: TFPReportUnits);
+    procedure   SetRight(const AValue: TFPReportUnits);
+    procedure   SetTop(const AValue: TFPReportUnits);
+  protected
+    procedure   Changed; virtual;
+  public
+    constructor Create(APage: TFPReportCustomPage);
+    procedure   Assign(Source: TPersistent); override;
+    function    Equals(AMargins: TFPReportMargins): boolean; reintroduce;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportMargins = nil); virtual;
+    procedure   ReadElement(AReader: TFPReportStreamer); virtual;
+    property    Page: TFPReportCustomPage read FPage;
+  published
+    property    Top: TFPReportUnits read FTop write SetTop;
+    property    Bottom: TFPReportUnits read FBottom write SetBottom;
+    property    Left: TFPReportUnits read FLeft write SetLeft;
+    property    Right: TFPReportUnits read FRight write SetRight;
+  end;
+
+
+  TFPReportPageSize = class(TPersistent)
+  private
+    FHeight: TFPReportUnits;
+    FPage: TFPReportCustomPage;
+    FPaperName: string;
+    FWidth: TFPReportUnits;
+    procedure SetHeight(const AValue: TFPReportUnits);
+    procedure SetPaperName(const AValue: string);
+    procedure SetWidth(const AValue: TFPReportUnits);
+  protected
+    procedure CheckPaperSize;
+    procedure Changed; virtual;
+  public
+    constructor Create(APage: TFPReportCustomPage);
+    procedure Assign(Source: TPersistent); override;
+    property Page: TFPReportCustomPage read FPage;
+  published
+    property PaperName: string read FPaperName write SetPaperName;
+    property Width: TFPReportUnits read FWidth write SetWidth;
+    property Height: TFPReportUnits read FHeight write SetHeight;
+  end;
+
+
+  { Layout is relative to the page.
+    That means that top/left equals top/left margin
+     Width/Height is equals to page height/width minus margins
+     Page orientation is taken into consideration. }
+
+  { TFPReportCustomPage }
+
+  TFPReportCustomPage = class(TFPReportElementWithChildren)
+  private
+    FData: TFPReportData;
+    FDataName : String;
+    FFont: TFPReportFont;
+    FMargins: TFPReportMargins;
+    FOrientation: TFPReportPaperOrientation;
+    FPageSize: TFPReportPageSize;
+    FReport: TFPCustomReport;
+    FBands: TFPList;
+    FColumnLayout: TFPReportColumnLayout;
+    FColumnCount: Byte;
+    FColumnGap: TFPReportUnits;
+    function GetBand(AIndex: integer): TFPReportCustomBand;
+    function GetBandCount: integer;
+    function BandWidthFromColumnCount: TFPReportUnits;
+    procedure ApplyBandWidth(ABand: TFPReportCustomBand);
+    procedure SetFont(AValue: TFPReportFont);
+    procedure SetMargins(const AValue: TFPReportMargins);
+    procedure SetOrientation(const AValue: TFPReportPaperOrientation);
+    procedure SetPageSize(const AValue: TFPReportPageSize);
+    procedure SetReport(const AValue: TFPCustomReport);
+    procedure SetReportData(const AValue: TFPReportData);
+    procedure SetColumnLayout(AValue: TFPReportColumnLayout);
+    procedure SetColumnCount(AValue: Byte);
+    procedure SetColumnGap(AValue: TFPReportUnits);
+  protected
+    Procedure SaveDataToNames; override;
+    Procedure RestoreDataFromNames; override;
+    procedure RemoveChild(const AChild: TFPReportElement); override;
+    procedure AddChild(const AChild: TFPReportElement); override;
+    procedure MarginsChanged; virtual;
+    procedure PageSizeChanged; virtual;
+    procedure RecalcLayout; override;
+    procedure CalcPrintPosition; virtual;
+    procedure PrepareObjects; override;
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    procedure DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Function  PageIndex : Integer;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    function    FindBand(ABand: TFPReportBandClass): TFPReportCustomBand;
+    property    PageSize: TFPReportPageSize read FPageSize write SetPageSize;
+    property    Margins: TFPReportMargins read FMargins write SetMargins;
+    property    Report: TFPCustomReport read FReport write SetReport;
+    property    Bands[AIndex: integer]: TFPReportCustomBand read GetBand;
+    property    BandCount: integer read GetBandCount;
+    property    Orientation: TFPReportPaperOrientation read FOrientation write SetOrientation;
+    property    Data: TFPReportData read FData write SetReportData;
+    property    ColumnCount: Byte read FColumnCount write SetColumnCount default 1;
+    property    ColumnGap: TFPReportUnits read FColumnGap write SetColumnGap default 0;
+    property    ColumnLayout: TFPReportColumnLayout read FColumnLayout write SetColumnLayout default clVertical;
+    property    Font: TFPReportFont read FFont write SetFont;
+  end;
+  TFPReportCustomPageClass = Class of TFPReportCustomPage;
+
+
+  TFPReportPage = class(TFPReportCustomPage)
+  published
+    property ColumnCount;
+    property ColumnGap;
+    property ColumnLayout;
+    property Data;
+    property Font;
+    property Margins;
+    property PageSize;
+    property Orientation;
+  end;
+
+
+  { TFPReportCustomBand }
+
+  TFPReportCustomBand = class(TFPReportElementWithChildren)
+  private
+    FChildBand: TFPReportChildBand;
+    FUseParentFont: boolean;
+    FVisibleOnPage: TFPReportVisibleOnPage;
+    FFont: TFPReportFont;
+    function    GetFont: TFPReportFont;
+    function    GetReportPage: TFPReportCustomPage;
+    function    IsStringValueZero(const AValue: string): boolean;
+    procedure   SetChildBand(AValue: TFPReportChildBand);
+    procedure   ApplyStretchMode;
+    procedure   SetFont(AValue: TFPReportFont);
+    procedure   SetUseParentFont(AValue: boolean);
+    procedure   SetVisibleOnPage(AValue: TFPReportVisibleOnPage);
+  protected
+    function    GetReportBandName: string; virtual;
+    function    GetData: TFPReportData; virtual;
+    procedure   SetDataFromName(AName : String); virtual;
+    function    ExpandMacro(const s: String; const AIsExpr: boolean): TFPReportString; virtual;
+    procedure   SetParent(const AValue: TFPReportElement); override;
+    procedure   CreateRTLayout; override;
+    procedure   PrepareObjects; override;
+    { this is normally run against the runtime version of the Band instance. }
+    procedure   RecalcLayout; override;
+    procedure   BeforePrint; override;
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    property    ChildBand: TFPReportChildBand read FChildBand write SetChildBand;
+    property    Font: TFPReportFont read GetFont write SetFont;
+    property    UseParentFont: boolean read FUseParentFont write SetUseParentFont;
+    property    VisibleOnPage: TFPReportVisibleOnPage read FVisibleOnPage write SetVisibleOnPage;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    procedure   Assign(Source: TPersistent); override;
+    Class Function ReportBandType : TFPReportBandType; virtual;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    property    Page: TFPReportCustomPage read GetReportPage;
+  end;
+  TFPReportCustomBandClass = Class of TFPReportCustomBand;
+
+
+  { TFPReportCustomBandWithData }
+
+  TFPReportCustomBandWithData = class(TFPReportCustomBand)
+  private
+    FData: TFPReportData;
+    FDataName : String;
+    procedure ResolveDataName;
+    procedure   SetData(const AValue: TFPReportData);
+  protected
+    Procedure SaveDataToNames; override;
+    Procedure RestoreDataFromNames; override;
+    function    GetData: TFPReportData; override;
+    Procedure SetDataFromName(AName: String); override;
+    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  published
+    property    Data: TFPReportData read GetData write SetData;
+  end;
+  TFPReportCustomBandWithDataClass = Class of TFPReportCustomBandWithData;
+
+
+  TFPReportCustomDataBand = class(TFPReportCustomBandWithData)
+  private
+    FHeaderBand: TFPReportCustomDataHeaderBand;
+    FFooterBand: TFPReportCustomDataFooterBand;
+    FMasterBand: TFPReportCustomDataBand;
+    FDisplayPosition: Integer;
+  protected
+    property    DisplayPosition: Integer read FDisplayPosition write FDisplayPosition default 0;
+    property    FooterBand: TFPReportCustomDataFooterBand read FFooterBand write FFooterBand;
+    property    HeaderBand: TFPReportCustomDataHeaderBand read FHeaderBand write FHeaderBand;
+    property    MasterBand: TFPReportCustomDataBand read FMasterBand write FMasterBand;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+  TFPReportCustomDataBandClass = Class of TFPReportCustomDataBand;
+
+
+  { Master data band. The report loop happens on this band. }
+  TFPReportDataBand = class(TFPReportCustomDataBand)
+  protected
+    function    GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  published
+    property    ChildBand;
+    property    DisplayPosition;
+    property    Font;
+    property    FooterBand;
+    property    HeaderBand;
+    property    MasterBand;
+    property    StretchMode;
+    property    UseParentFont;
+    property    VisibleOnPage;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomChildBand = class(TFPReportCustomBandWithData)
+  protected
+    function GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportChildBand = class(TFPReportCustomChildBand)
+  published
+    property    ChildBand;
+    property    Font;
+    property    StretchMode;
+    property    UseParentFont;
+    property    VisibleOnPage;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomPageFooterBand = class(TFPReportCustomBand)
+  protected
+    function    GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportPageFooterBand = class(TFPReportCustomPageFooterBand)
+  published
+    property    Font;
+    property    UseParentFont;
+    property    VisibleOnPage;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomPageHeaderBand = class(TFPReportCustomBand)
+  protected
+    function GetReportBandName: string; override;
+  public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportPageHeaderBand = class(TFPReportCustomPageHeaderBand)
+  published
+    property    Font;
+    property    UseParentFont;
+    property    VisibleOnPage;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomColumnHeaderBand = class(TFPReportCustomBandWithData)
+  protected
+    function GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportColumnHeaderBand = class(TFPReportCustomColumnHeaderBand)
+  published
+    property    Data;
+    property    Font;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomColumnFooterBand = class(TFPReportCustomBandWithData)
+  private
+    FFooterPosition: TFPReportFooterPosition;
+    procedure   SetFooterPosition(AValue: TFPReportFooterPosition);
+  protected
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    function    GetReportBandName: string; override;
+    property    FooterPosition: TFPReportFooterPosition read FFooterPosition write SetFooterPosition default fpColumnBottom;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure   Assign(Source: TPersistent); override;
+    Class Function ReportBandType : TFPReportBandType; override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+  end;
+
+
+  TFPReportColumnFooterBand = class(TFPReportCustomColumnFooterBand)
+  published
+    property    Font;
+    property    FooterPosition;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomGroupHeaderBand = class(TFPReportCustomBandWithData)
+  private
+    FGroupHeader: TFPReportCustomGroupHeaderBand;
+    FChildGroupHeader: TFPReportCustomGroupHeaderBand;
+    FGroupFooter: TFPReportCustomGroupFooterBand;
+    FCondition: string;
+    procedure   SetGroupHeader(AValue: TFPReportCustomGroupHeaderBand);
+  protected
+    function    GetReportBandName: string; override;
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
+    { This property defines the hierarchy of nested groups. For the top most group, this property will be nil. }
+    property    GroupHeader: TFPReportCustomGroupHeaderBand read FGroupHeader write SetGroupHeader;
+    { Indicates related GroupFooter band. This will automatically be set by the GroupFooter. }
+    property    GroupFooter: TFPReportCustomGroupFooterBand read FGroupFooter;
+    { can be a field name or an expression }
+    property    GroupCondition: string read FCondition write FCondition;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    function    Evaluate: string;
+    Class Function ReportBandType : TFPReportBandType; override;
+    property    ChildGroupHeader: TFPReportCustomGroupHeaderBand read FChildGroupHeader;
+  end;
+
+
+  TFPReportGroupHeaderBand = class(TFPReportCustomGroupHeaderBand)
+  public
+    property    GroupFooter;
+  published
+    property    Font;
+    property    GroupCondition;
+    property    GroupHeader;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  { Report title band - prints once at the beginning of the report }
+  TFPReportCustomTitleBand = class(TFPReportCustomBand)
+  protected
+    function GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportTitleBand = class(TFPReportCustomTitleBand)
+  published
+    property    Font;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  { Report summary band - prints once at the end of the report }
+  TFPReportCustomSummaryBand = class(TFPReportCustomBand)
+  private
+    FStartNewPage: boolean;
+  protected
+    function    GetReportBandName: string; override;
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    property    StartNewPage: boolean read FStartNewPage write FStartNewPage default False;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportSummaryBand = class(TFPReportCustomSummaryBand)
+  published
+    property    Font;
+    property    StartNewPage;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomGroupFooterBand = class(TFPReportCustomBandWithData)
+  private
+    FGroupHeader: TFPReportCustomGroupHeaderBand;
+    procedure SetGroupHeader(const AValue: TFPReportCustomGroupHeaderBand);
+  protected
+    function  GetReportBandName: string; override;
+    procedure DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    property  GroupHeader: TFPReportCustomGroupHeaderBand read FGroupHeader write SetGroupHeader;
+  public
+    procedure ReadElement(AReader: TFPReportStreamer); override;
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportGroupFooterBand = class(TFPReportCustomGroupFooterBand)
+  published
+    property    Font;
+    property    GroupHeader;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomDataHeaderBand = class(TFPReportCustomBandWithData)
+  protected
+    function GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportDataHeaderBand = class(TFPReportCustomDataHeaderBand)
+  published
+    property    Font;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportCustomDataFooterBand = class(TFPReportCustomBandWithData)
+  protected
+    function GetReportBandName: string; override;
+  Public
+    Class Function ReportBandType : TFPReportBandType; override;
+  end;
+
+
+  TFPReportDataFooterBand = class(TFPReportCustomDataFooterBand)
+  published
+    property    Font;
+    property    UseParentFont;
+    property    OnBeforePrint;
+  end;
+
+
+  TFPReportImageItem = class(TCollectionItem)
+  private
+    FImage: TFPCustomImage;
+    FOwnsImage: Boolean;
+    FStreamed: TBytes;
+    FWidth: Integer;
+    FHeight: Integer;
+    function    GetHeight: Integer;
+    function    GetStreamed: TBytes;
+    function    GetWidth: Integer;
+    procedure   SetImage(AValue: TFPCustomImage);
+    procedure   SetStreamed(AValue: TBytes);
+    procedure   LoadPNGFromStream(AStream: TStream);
+  public
+    constructor Create(ACollection: TCollection); override;
+    destructor  Destroy; override;
+    procedure   CreateStreamedData;
+    function    WriteImageStream(AStream: TStream): UInt64; virtual;
+    function    Equals(AImage: TFPCustomImage): boolean; reintroduce;
+    procedure   WriteElement(AWriter: TFPReportStreamer);
+    procedure   ReadElement(AReader: TFPReportStreamer);
+    property    Image: TFPCustomImage read FImage write SetImage;
+    property    StreamedData: TBytes read GetStreamed write SetStreamed;
+    property    OwnsImage: Boolean read FOwnsImage write FOwnsImage default True;
+    property    Width: Integer read GetWidth;
+    property    Height: Integer read GetHeight;
+  end;
+
+
+  { TFPReportImages }
+
+  TFPReportImages = class(TOwnedCollection)
+  private
+    function    GetImg(AIndex: Integer): TFPReportImageItem;
+    function GetReportOwner: TFPCustomReport;
+  protected
+  public
+    constructor Create(AOwner: TFPCustomReport; AItemClass: TCollectionItemClass);
+    function    AddImageItem: TFPReportImageItem;
+    function    AddFromStream(const AStream: TStream; Handler: TFPCustomImageReaderClass; KeepImage: Boolean = False): Integer;
+    function    AddFromFile(const AFileName: string; KeepImage: Boolean = False): Integer;
+    function    AddFromData(const AImageData: Pointer; const AImageDataSize: LongWord): integer;
+    function    GetIndexFromID(const AID: integer): integer;
+    Function    GetImageFromID(const AID: integer): TFPCustomImage;
+    Function    GetImageItemFromID(const AID: integer): TFPReportImageItem;
+    property    Images[AIndex: Integer]: TFPReportImageItem read GetImg; default;
+    property    Owner: TFPCustomReport read GetReportOwner;
+  end;
+
+  { TFPReportVariable }
+
+  TFPReportVariable = Class(TCollectionItem)
+  private
+    FName: String;
+    FValue: TFPExpressionResult;
+    FSavedValue: TFPExpressionResult;
+    procedure CheckType(aType: TResultType);
+    function GetAsBoolean: Boolean;
+    function GetAsDateTime: TDateTime;
+    function GetAsFloat: TexprFloat;
+    function GetAsInteger: Int64;
+    function GetAsString: String;
+    function GetDataType: TResultType;
+    function GetER: TFPExpressionResult;
+    function GetValue: String;
+    procedure SetAsBoolean(AValue: Boolean);
+    procedure SetAsDateTime(AValue: TDateTime);
+    procedure SetAsFloat(AValue: TExprFloat);
+    procedure SetAsInteger(AValue: Int64);
+    procedure SetAsString(AValue: String);
+    procedure SetDataType(AValue: TResultType);
+    procedure SetER(AValue: TFPExpressionResult);
+    procedure SetName(AValue: String);
+    procedure SetValue(AValue: String);
+    Procedure SaveValue; virtual;
+    Procedure RestoreValue; virtual;
+  Protected
+    Procedure GetRTValue(Var Result : TFPExpressionResult; ConstRef AName : ShortString); virtual;
+  Public
+    Procedure Assign(Source : TPersistent); override;
+    Property AsExpressionResult : TFPExpressionResult Read GetER Write SetER;
+    Property AsString : String Read GetAsString Write SetAsString;
+    Property AsInteger : Int64 Read GetAsInteger Write SetAsInteger;
+    Property AsBoolean : Boolean Read GetAsBoolean Write SetAsBoolean;
+    Property AsFloat : TExprFloat Read GetAsFloat Write SetAsFloat;
+    Property AsDateTime : TDateTime Read GetAsDateTime Write SetAsDateTime;
+  Published
+    Property Name : String Read FName Write SetName;
+    Property DataType : TResultType Read GetDataType Write SetDataType;
+    property Value : String Read GetValue Write SetValue;
+  end;
+
+  { TFPReportVariables }
+
+  TFPReportVariables = Class(TOwnedCollection)
+  private
+    function GetV(aIndex : Integer): TFPReportVariable;
+    procedure SetV(aIndex : Integer; AValue: TFPReportVariable);
+  Protected
+  public
+    Function IndexOfVariable(aName : String)  : Integer;
+    Function FindVariable(aName : String)  : TFPReportVariable;
+    Function AddVariable(aName : String)  : TFPReportVariable;
+    Property Variable[aIndex : Integer] : TFPReportVariable Read GetV Write SetV; default;
+  end;
+
+  { TFPCustomReport }
+
+  TFPCustomReport = class(TFPReportComponent)
+  private
+    FPages: TFPList;
+    FOnBeginReport: TFPReportBeginReportEvent;
+    FOnEndReport: TFPReportEndReportEvent;
+    FReportData: TFPReportDataCollection;
+    FRTObjects: TFPList;  // see property
+    FRTCurPageIdx: integer; // RTObjects index reference to current page being layout
+    FRTCurBand: TFPReportCustomBand;  // current band being layout
+    FExpr: TFPexpressionParser;
+    FTitle: string;
+    FAuthor: string;
+    FPageNumber: integer;
+    FPageCount: integer; // requires two-pass reporting
+    FPageNumberPerDesignerPage: integer; // page number per report designer pages
+    FDateCreated: TDateTime;
+    FReferenceList: TStringList;
+    FImages: TFPReportImages;
+    FOnBeforeRenderReport: TNotifyEvent;
+    FOnAfterRenderReport: TNotifyEvent;
+    FTwoPass: boolean;
+    FIsFirstPass: boolean;
+    FPageData: TFPReportData;
+    FPerDesignerPageCount: array of UInt32;
+    FVariables : TFPReportVariables;
+    function GetPage(AIndex: integer): TFPReportCustomPage;
+    function GetPageCount: integer; { this is designer page count }
+    function GetRenderedPageCount: integer;
+    procedure BuiltinExprRecNo(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+    procedure BuiltinGetPageNumber(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+    procedure BuiltinGetPageNoPerDesignerPage(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 }
+    procedure RecalcBandLayout(ABand: TFPReportCustomBand);
+    procedure EmptyRTObjects;
+    procedure ClearDataBandLastTextValues(ABand: TFPReportCustomBandWithData);
+    procedure ProcessAggregates(const APageIdx: integer; const AData: TFPReportData);
+
+    { these three methods are used to resolve references while reading a report from file. }
+    procedure ClearReferenceList;
+    procedure AddReference(const AParentName, AChildName: string);
+    procedure FixupReferences;
+
+    procedure DoBeforeRenderReport;
+    procedure DoAfterRenderReport;
+    procedure DoProcessTwoPass;
+    procedure DoGetExpressionVariableValue(var Result: TFPExpressionResult; constref AName: ShortString);
+    procedure SetReportData(AValue: TFPReportDataCollection);
+    procedure SetVariables(AValue: TFPReportVariables);
+  protected
+    FBands: TBandList;
+    function CreateVariables: TFPReportVariables; virtual;
+    function CreateImages: TFPReportImages; virtual;
+    function CreateReportData: TFPReportDataCollection; virtual;
+
+    procedure RestoreDefaultVariables; virtual;
+    procedure DoPrepareReport; virtual;
+    procedure DoBeginReport; virtual;
+    procedure DoEndReport; virtual;
+    procedure InitializeDefaultExpressions; virtual;
+    procedure InitializeExpressionVariables(const AData: TFPReportData); virtual;
+    procedure CacheMemoExpressions(const APageIdx: integer; const AData: TFPReportData); virtual;
+    procedure StartRender; override;
+    procedure EndRender; override;
+    // stores object instances for and during layouting
+    property  RTObjects: TFPList read FRTObjects;
+    property Images: TFPReportImages read FImages;
+    property Pages[AIndex: integer]: TFPReportCustomPage read GetPage;
+    property PageCount: integer read GetPageCount;
+    property IsFirstPass: boolean read FIsFirstPass write FIsFirstPass default False;
+    property OnBeginReport: TFPReportBeginReportEvent read FOnBeginReport write FOnBeginReport;
+    property OnEndReport: TFPReportEndReportEvent read FOnEndReport write FOnEndReport;
+    property OnBeforeRenderReport: TNotifyEvent read FOnBeforeRenderReport write FOnBeforeRenderReport;
+    property OnAfterRenderReport: TNotifyEvent read FOnAfterRenderReport write FOnAfterRenderReport;
+
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Procedure   SaveDataToNames;
+    Procedure   RestoreDataFromNames;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    procedure   AddPage(APage: TFPReportCustomPage);
+    procedure   RemovePage(APage: TFPReportCustomPage);
+    function    FindRecursive(const AName: string): TFPReportElement;
+    procedure   RunReport;
+    procedure   RenderReport(const AExporter: TFPReportExporter);
+    Property Variables : TFPReportVariables Read FVariables Write SetVariables;
+    {$IFDEF gdebug}
+    function DebugPreparedPageAsJSON(const APageNo: Byte): string;
+    {$ENDIF}
+    property Author: string read FAuthor write FAuthor;
+    property DateCreated: TDateTime read FDateCreated write FDateCreated;
+    property Title: string read FTitle write FTitle;
+    property TwoPass: boolean read FTwoPass write FTwoPass default False;
+    Property ReportData : TFPReportDataCollection Read FReportData Write SetReportData;
+  end;
+
+
+  TFPReport = class(TFPCustomReport)
+  public
+    property Pages;
+    property PageCount;
+    property Images;
+  published
+    property Author;
+    property Title;
+    property TwoPass;
+    property ReportData;
+    property OnAfterRenderReport;
+    property OnBeforeRenderReport;
+    property OnBeginReport;
+    property OnEndReport;
+  end;
+
+
+  EReportError = class(Exception);
+  EReportExportError = class(EReportError);
+  EReportFontNotFound = class(EReportError);
+
+
+  TFPReportPaperManager = class(TComponent)
+  private
+    FPaperSizes: TStringList;
+    function GetPaperCount: integer;
+    function GetPaperHeight(AIndex: integer): TFPReportUnits;
+    function GetPaperHeightByName(AName: string): TFPReportUnits;
+    function GetPaperName(AIndex: integer): string;
+    function GetPaperWidth(AIndex: integer): TFPReportUnits;
+    function GetPaperWidthByName(AName: string): TFPReportUnits;
+  protected
+    function FindPaper(const AName: string): TFPReportPaperSize;
+    function GetPaperByname(const AName: string): TFPReportPaperSize;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure Clear;
+    procedure RegisterPaper(const AName: string; const AWidth, AHeight: TFPReportUnits);
+    procedure RegisterStandardSizes;
+    { assign registered names to AList - useful to populate ComboBoxes etc }
+    procedure GetRegisteredSizes(var AList: TStringList);
+    function IndexOfPaper(const AName: string): integer;
+    property PaperNames[AIndex: integer]: string read GetPaperName;
+    property PaperHeight[AIndex: integer]: TFPReportUnits read GetPaperHeight;
+    property PaperWidth[AIndex: integer]: TFPReportUnits read GetPaperWidth;
+    property HeightByName[AName: string]: TFPReportUnits read GetPaperHeightByName;
+    property WidthByName[AName: string]: TFPReportUnits read GetPaperWidthByName;
+    property PaperCount: integer read GetPaperCount;
+  end;
+
+
+  TExprNodeInfoRec = record
+    Position: UInt32;
+    ExprNode: TFPExprNode;
+  end;
+
+  TFPReportCustomMemo = class(TFPReportElement)
+  private
+    FText: TFPReportString;
+    FIsExpr: boolean;
+    FTextAlignment: TFPReportTextAlignment;
+    FTextLines: TStrings;
+    FLineSpacing: TFPReportUnits;
+    FCurTextBlock: TFPTextBlock;
+    FTextBlockList: TFPTextBlockList;
+    FParser: THTMLParser;
+    { These six fields are used by PrepareTextBlocks() }
+    FTextBlockState: TFPReportHTMLTagSet;
+    FTextBlockXOffset: TFPReportUnits;
+    FTextBlockYOffset: TFPReportUnits;
+    FLastURL: string;
+    FLastBGColor: TFPReportColor;
+    FLastFGColor: TFPReportColor;
+    FLinkColor: TFPReportColor;
+    FOptions: TFPReportMemoOptions;
+    FLastText: string; // used by moSuppressRepeated
+    FOriginal: TFPReportCustomMemo;
+    ExpressionNodes: array of TExprNodeInfoRec;
+    FFont: TFPReportFont;
+    FUseParentFont: Boolean;
+    function    GetFont: TFPReportFont;
+    procedure   SetText(AValue: TFPReportString);
+    procedure   SetUseParentFont(AValue: Boolean);
+    procedure   WrapText(const AText: String; var ALines: TStrings; const ALineWidth: TFPReportUnits; out AHeight: TFPReportUnits);
+    procedure   ApplyStretchMode(const AHeight: TFPReportUnits);
+    procedure   ApplyHorzTextAlignment;
+    procedure   ApplyVertTextAlignment;
+    function    GetTextLines: TStrings;
+    procedure   SetLineSpacing(AValue: TFPReportUnits);
+    procedure   HTMLOnFoundTag(NoCaseTag, ActualTag: string);
+    procedure   HTMLOnFoundText(Text: string);
+    function    PixelsToMM(APixels: single): single; inline;
+    function    mmToPixels(mm: single): integer; inline;
+    { Result is in millimeters. }
+    function    TextHeight(const AText: string; out ADescender: TFPReportUnits): TFPReportUnits;
+    { Result is in millimeters. }
+    function    TextWidth(const AText: string): TFPReportUnits;
+    procedure   SetLinkColor(AValue: TFPReportColor);
+    procedure   SetTextAlignment(AValue: TFPReportTextAlignment);
+    procedure   SetOptions(const AValue: TFPReportMemoOptions);
+    procedure   ParseText;
+    procedure   ClearExpressionNodes;
+    procedure   AddSingleTextBlock(const AText: string);
+    procedure   AddMultipleTextBlocks(const AText: string);
+    function    IsExprAtArrayPos(const APos: integer): Boolean;
+    procedure   SetFont(const AValue: TFPReportFont);
+  protected
+    function    CreateTextAlignment: TFPReportTextAlignment; virtual;
+    function    GetExpr: TFPExpressionParser; virtual;
+    procedure   PrepareObject; override;
+    procedure   RecalcLayout; override;
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   ExpandExpressions;
+    property    Text: TFPReportString read FText write SetText;
+    property    Font: TFPReportFont read GetFont write SetFont;
+    property    TextAlignment: TFPReportTextAlignment read FTextAlignment write SetTextAlignment;
+    property    LineSpacing: TFPReportUnits read FLineSpacing write SetLineSpacing default 1;
+    property    LinkColor: TFPReportColor read FLinkColor write SetLinkColor default clBlue;
+    { The moUsesHTML enables supports for <b>, <i>, <font color=yxz bgcolor=xyz> and <a href="..."> tags.
+      NOTE: The FONT tag's color attribute will override the FontColor property. }
+    property    Options: TFPReportMemoOptions read FOptions write SetOptions default [];
+    { Used by Runtime Memos - this is a reference back to the original design memo. }
+    property    Original: TFPReportCustomMemo read FOriginal write FOriginal;
+    property    UseParentFont: Boolean read FUseParentFont write SetUseParentFont default True;
+  protected
+    // *****************************
+    //   This block is made Protected simply for Unit Testing purposes.
+    //   Interfaces would have worked nicely for this.
+    // *****************************
+
+    // --------------->  Start <-----------------
+    function    CreateTextBlock(const IsURL: boolean): TFPTextBlock;
+    { HtmlColorToFPReportColor() supports RRGGBB, #RRGGBB and #RGB color formats. }
+    function    HtmlColorToFPReportColor(AColorStr: string; ADefault: TFPReportColor = clBlack): TFPReportColor;
+    procedure   PrepareTextBlocks;
+    { Extract a sub-string within defined delimiters. If AIndex is > 0 then extract the
+      AIndex'th sub-string. AIndex uses 1-based numbering. The AStartPos returns the position
+      of the returned sub-string in ASource. }
+    function    SubStr(const ASource, AStartDelim, AEndDelim: string; AIndex: integer; out AStartPos: integer): string;
+    { Count the number of blocks of text in AValue separated by AToken }
+    function    TokenCount(const AValue: string; const AToken: string = '['): integer;
+    { Return the n-th token defined by APos. APas is 1-based. }
+    function    Token(const AValue, AToken: string; const APos: integer): string;
+    // --------------->  End  <-----------------
+
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    { Only returns the internal FTextLines if StretchMode <> smDontStretch, otherwise it returns nil. Don't free the TStrings result. }
+    property    TextLines: TStrings read GetTextLines;
+    { after layouting, this contains all the memo text and positions they should be displayed at. }
+    property    TextBlockList: TFPTextBlockList read FTextBlockList;
+  end;
+
+
+  TFPReportMemo = class(TFPReportCustomMemo)
+  published
+    property  Font;
+    property  LineSpacing;
+    property  LinkColor;
+    property  Options;
+    property  StretchMode;
+    property  Text;
+    property  TextAlignment;
+    property  UseParentFont;
+    property  OnBeforePrint;
+  end;
+
+
+  { TFPReportCustomShape }
+
+  TFPReportCustomShape = class(TFPReportElement)
+  private
+    FColor: TFPReportColor;
+    FShapeType: TFPReportShapeType;
+    FOrientation: TFPReportOrientation;
+    FCornerRadius: TFPReportUnits;
+    procedure   SetShapeType(AValue: TFPReportShapeType);
+    procedure   SetOrientation(AValue: TFPReportOrientation);
+    procedure   SetCornerRadius(AValue: TFPReportUnits);
+  protected
+    procedure   PrepareObject; override;
+    Procedure   RecalcLayout; override;
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    property    ShapeType: TFPReportShapeType read FShapeType write SetShapeType default stEllipse;
+    property    Orientation: TFPReportOrientation read FOrientation write SetOrientation default orNorth;
+    property    CornerRadius: TFPReportUnits read FCornerRadius write SetCornerRadius;
+    Property    Color : TFPReportColor Read FColor Write FColor default clBlack;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure   Assign(Source: TPersistent); override;
+    Function CreatePropertyHash: String; override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+  end;
+
+
+  TFPReportShape = class(TFPReportCustomShape)
+  published
+    property    ShapeType;
+    property    Orientation;
+    property    CornerRadius;
+    property    Color;
+  end;
+
+
+  { TFPReportCustomImage }
+
+  TFPReportCustomImage = class(TFPReportElement)
+  private
+    FImage: TFPCustomImage;
+    FStretched: boolean;
+    FFieldName: TFPReportString;
+    FImageID: integer;
+    procedure   SetImage(AValue: TFPCustomImage);
+    procedure   SetStretched(AValue: boolean);
+    procedure   SetFieldName(AValue: TFPReportString);
+    procedure   LoadDBData(AData: TFPReportData);
+    procedure   SetImageID(AValue: integer);
+    function    GetImage: TFPCustomImage;
+  protected
+    procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   PrepareObject; override;
+    Procedure   RecalcLayout; override;
+    property    Image: TFPCustomImage read GetImage write SetImage;
+    property    ImageID: integer read FImageID write SetImageID;
+    property    Stretched: boolean read FStretched write SetStretched;
+    property    FieldName: TFPReportString read FFieldName write SetFieldName;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Function    GetRTImageID : Integer;
+    Function    GetRTImage : TFPCustomImage;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   ReadElement(AReader: TFPReportStreamer); override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure   LoadFromFile(const AFileName: string);
+    procedure   LoadPNGFromStream(AStream: TStream);
+    procedure   LoadImage(const AImageData: Pointer; const AImageDataSize: LongWord);
+  end;
+
+
+  TFPReportImage = class(TFPReportCustomImage)
+  published
+    property    Image;
+    property    ImageID;
+    property    Stretched;
+    property    FieldName;
+    property    OnBeforePrint;
+  end;
+
+
+  { TFPReportCustomCheckbox }
+
+  TFPReportCustomCheckbox = class(TFPReportElement)
+  private
+    FExpression: TFPReportString;
+    FFalseImageID: Integer;
+    FTrueImageID: Integer;
+    procedure   SetExpression(AValue: TFPReportString);
+    function    LoadImage(const AImageData: Pointer; const AImageDataSize: LongWord): TFPCustomImage; overload;
+    function    LoadImage(AStream: TStream): TFPCustomImage; overload;
+  protected
+    Class Var
+      ImgTrue: TFPCustomImage;
+      ImgFalse: TFPCustomImage;
+  Protected
+    FTestResult: Boolean;
+    Procedure   RecalcLayout; override;
+    procedure   PrepareObject; override;
+    property    Expression: TFPReportString read FExpression write SetExpression;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    function    GetDefaultImage(Checked: Boolean): TFPCustomImage;
+    Function    GetImage(Checked: Boolean) : TFPCustomImage;
+    Function    GetRTResult : Boolean;
+    Function    GetRTImage : TFPCustomImage;
+    Function    CreatePropertyHash: String; override;
+    procedure   Assign(Source: TPersistent); override;
+    procedure   WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    Property    TrueImageID : Integer Read FTrueImageID Write FTrueImageID;
+    Property    FalseImageID : Integer Read FFalseImageID Write FFalseImageID;
+  end;
+
+
+  TFPReportCheckbox = class(TFPReportCustomCheckbox)
+  published
+    property    Expression;
+    Property    TrueImageID ;
+    Property    FalseImageID ;
+  end;
+
+  { TFPReportElementEditor }
+
+  TFPReportElementEditor = Class(TComponent)
+  private
+    FElement: TFPReportElement;
+  Protected
+    procedure SetElement(AValue: TFPReportElement); virtual;
+  Public
+    Class function DefaultClass : TFPReportElementClass; virtual;
+    Class Procedure RegisterEditor;
+    Class Procedure UnRegisterEditor;
+    Function Execute: Boolean; virtual; abstract;
+    Property Element : TFPReportElement Read FElement Write SetElement;
+  end;
+  TFPReportElementEditorClass = Class of TFPReportElementEditor;
+
+  { A class to hold the TFPReportElement class mappings. The factory maintains
+    a list of these and uses the ReportElementClass property to create the objects. }
+
+  { TFPReportClassMapping }
+
+  TFPReportImageRenderCallBack = Procedure(aElement : TFPReportElement; aImage: TFPCustomImage);
+  TFPReportElementExporterCallBack = Procedure(aPos : TFPReportPoint; aElement : TFPReportElement; AExporter : TFPReportExporter; ADPI: Integer);
+
+  TFPReportElementRenderer = Record
+    aClass : TFPReportExporterClass;
+    aCallback : TFPReportElementExporterCallBack;
+  end;
+  TFPReportElementRendererArray = Array of TFPReportElementRenderer;
+
+  TFPReportClassMapping = class(TObject)
+  private
+    FEditorClass: TFPReportElementEditorClass;
+    FImageRenderCallBack: TFPReportImageRenderCallBack;
+    FMappingName: string;
+    FReportElementClass: TFPReportElementClass;
+    FRenderers : TFPReportElementRendererArray;
+  public
+    Function IndexOfExportRenderer(AClass : TFPReportExporterClass) : Integer;
+    constructor Create(const AMappingName: string; AElementClass: TFPReportElementClass);
+    Function AddRenderer(aExporterClass : TFPReportExporterClass; aCallback : TFPReportElementExporterCallBack) : TFPReportElementExporterCallBack;
+    Function FindRenderer(aClass : TFPReportExporterClass) : TFPReportElementExporterCallBack;
+    property MappingName: string read FMappingName;
+    Property ImageRenderCallback : TFPReportImageRenderCallBack Read FImageRenderCallBack Write FImageRenderCallBack;
+    property ReportElementClass: TFPReportElementClass read FReportElementClass;
+    property EditorClass : TFPReportElementEditorClass Read FEditorClass Write FEditorClass;
+  end;
+
+
+  { Factory pattern - Create a descendant of the TFPReportElement at runtime. }
+
+  { TFPReportElementFactory }
+
+  TFPReportElementFactory = class(TObject)
+  private
+    FList: TFPObjectList;
+    function GetM(Aindex : integer): TFPReportClassMapping;
+  Protected
+    function IndexOfElementClass(const AElementClass: TFPReportElementClass): Integer;
+    Function IndexOfElementName(const AElementName: string) : Integer;
+    Property Mappings[Aindex : integer] : TFPReportClassMapping read GetM;
+  public
+    constructor Create;
+    destructor  Destroy; override;
+    Function    FindRenderer(aClass : TFPReportExporterClass; AElement : TFPReportElementClass) : TFPReportElementExporterCallBack;
+    Function    FindImageRenderer(AElement : TFPReportElementClass) : TFPReportImageRenderCallBack;
+    Function    RegisterImageRenderer(AElement : TFPReportElementClass; ARenderer : TFPReportImageRenderCallBack) : TFPReportImageRenderCallBack;
+    Function    RegisterElementRenderer(AElement : TFPReportElementClass; ARenderClass: TFPReportExporterClass; ARenderer : TFPReportElementExporterCallBack) : TFPReportElementExporterCallBack;
+    procedure   RegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+    procedure   RegisterEditorClass(AReportElementClass: TFPReportElementClass; AEditorClass: TFPReportElementEditorClass);
+    procedure   UnRegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+    procedure   UnRegisterEditorClass(AReportElementClass: TFPReportElementClass; AEditorClass: TFPReportElementEditorClass);
+    procedure   RegisterClass(const AElementName: string; AReportElementClass: TFPReportElementClass);
+    function    CreateInstance(const AElementName: string; AOwner: TComponent): TFPReportElement; overload;
+    Function    FindEditorClassForInstance(AInstance : TFPReportElement) : TFPReportElementEditorClass;
+    Function    FindEditorClassForInstance(AClass : TFPReportElementClass) : TFPReportElementEditorClass ;
+    procedure   AssignReportElementTypes(AStrings: TStrings);
+  end;
+
+  { TFPReportBandFactory }
+
+  TFPReportBandFactory = class(TObject)
+  Private
+    FBandTypes : Array[TFPReportBandType] of TFPReportCustomBandClass;
+    FPageClass: TFPReportCustomPageClass;
+    function getBandClass(aIndex : TFPReportBandType): TFPReportCustomBandClass;
+  Public
+    Constructor Create;
+    Function RegisterBandClass(aBandType : TFPReportBandType; AClass : TFPReportCustomBandClass) : TFPReportCustomBandClass;
+    Function RegisterPageClass(aClass : TFPReportCustomPageClass) : TFPReportCustomPageClass;
+    Property BandClasses [aIndex : TFPReportBandType] : TFPReportCustomBandClass read getBandClass;
+    Property PageClass : TFPReportCustomPageClass Read FPageClass;
+  end;
+  { keeps track of interested bands. eg: a list of page header like bands etc. }
+  TBandList = class(TObject)
+  private
+    FList: TFPList;
+    function    GetCount: Integer;
+    function    GetItems(AIndex: Integer): TFPReportCustomBand;
+    procedure   SetItems(AIndex: Integer; AValue: TFPReportCustomBand);
+  public
+    constructor Create;
+    destructor  Destroy; override;
+    function    Add(AItem: TFPReportCustomBand): Integer;
+    procedure   Clear;
+    procedure   Delete(AIndex: Integer);
+    function    Find(ABand: TFPReportBandClass): TFPReportCustomBand; overload;
+    function    Find(ABand: TFPReportBandClass; out AResult: TFPReportCustomBand): Integer; overload;
+    procedure   Sort(Compare: TListSortCompare);
+    property    Count: Integer read GetCount;
+    property    Items[AIndex: Integer]: TFPReportCustomBand read GetItems write SetItems; default;
+  end;
+
+
+  { TFPReportExportManager }
+
+  TFPReportExportManager = Class(TComponent)
+  Private
+    Flist : TFPObjectList;
+    FOnConfigCallBack: TFPReportExporterConfigHandler;
+    function GetExporter(AIndex : Integer): TFPReportExporterClass;
+    function GetExporterCount: Integer;
+  Protected
+    Procedure RegisterExport(AClass : TFPReportExporterClass); virtual;
+    Procedure UnRegisterExport(AClass : TFPReportExporterClass); virtual;
+    Function ConfigExporter(AExporter : TFPReportExporter) : Boolean; virtual;
+  Public
+    Constructor Create(AOwner : TComponent);override;
+    Destructor Destroy; override;
+    Procedure Clear;
+    Function IndexOfExporter(Const AName : String) : Integer;
+    Function IndexOfExporter(Const AClass : TFPReportExporterClass) : Integer;
+    Function FindExporter(Const AName : String) : TFPReportExporterClass;
+    Function ExporterConfigHandler(Const AClass : TFPReportExporterClass) : TFPReportExporterConfigHandler;
+    Procedure RegisterConfigHandler(Const AName : String; ACallBack : TFPReportExporterConfigHandler);
+    Property Exporter[AIndex : Integer] : TFPReportExporterClass Read GetExporter;
+    Property ExporterCount : Integer Read GetExporterCount;
+    // GLobal one, called when no specific callback is configured
+    Property OnConfigCallBack : TFPReportExporterConfigHandler Read FOnConfigCallBack Write FOnConfigCallBack;
+  end;
+
+
+procedure ReportError(Msg: string); inline;
+procedure ReportError(Fmt: string; Args: array of const);
+function  HorzTextAlignmentToString(AEnum: TFPReportHorzTextAlignment): string; inline;
+function  StringToHorzTextAlignment(AName: string): TFPReportHorzTextAlignment; inline;
+function  VertTextAlignmentToString(AEnum: TFPReportVertTextAlignment): string; inline;
+function  StringToVertTextAlignment(AName: string): TFPReportVertTextAlignment; inline;
+{ Converts R, G, B color channel values into the TFPReportColor (RRGGBB format) type. }
+function RGBToReportColor(R, G, B: Byte): TFPReportColor;
+{ Base64 encode stream data }
+function FPReportStreamToMIMEEncodeString(const AStream: TStream): string;
+{ Base64 decode string to a stream }
+procedure FPReportMIMEEncodeStringToStream(const AString: string; const AStream: TStream);
+
+function PaperManager: TFPReportPaperManager;
+
+// The ElementFactory is a singleton
+function gElementFactory: TFPReportElementFactory;
+function gBandFactory : TFPReportBandFactory;
+
+Function ReportExportManager : TFPReportExportManager;
+
+implementation
+
+uses
+  strutils,
+  typinfo,
+  FPReadPNG,
+  FPWritePNG,
+  base64,
+  fpTTF;
+
+resourcestring
+  { this should probably be more configurable or flexible per platform }
+  cDefaultFont = 'Helvetica';
+  cPageCountMarker = '~PC~';
+
+  SErrInvalidLineWidth   = 'Invalid line width: %d';
+  SErrInvalidParent      = '%s cannot be used as a parent for %s';
+  SErrInvalidChildIndex  = 'Invalid child index : %d';
+  SErrInvalidPageIndex   = 'Invalid page index : %d';
+  SErrNotAReportPage     = '%s (%s) is not a TFPReportCustomPage.';
+  SErrDuplicatePaperName = 'Paper name %s already exists';
+  SErrUnknownPaper       = 'Unknown paper name : "%s"';
+  SErrUnknownField       = '%s: No such field : "%s"';
+  SErrInitFieldsNotAllowedAfterOpen =
+    'Calling InitDataFields to change the Datafields collection after Open() is not allowed.';
+  SErrUnknownMacro       = '**unknown**';
+  SErrNoFileFound        = 'No file found: "%s"';
+  SErrChildBandCircularReference = 'ChildBand circular reference detected and not allowed.';
+  SErrFontNotFound       = 'Font not found: "%s"';
+
+  SErrRegisterEmptyExporter     = 'Attempt to register empty exporter';
+  SErrRegisterDuplicateExporter = 'Attempt to register duplicate exporter: "%s"';
+  SErrRegisterUnknownElement = 'Unable to find registered report element <%s>.';
+  SErrUnknownExporter = 'Unknown exporter: "%s"';
+  SErrMultipleDataBands = 'A report page may not have more than one master databand.';
+  SErrCantAssignReportFont = 'Can''t Assign() report font - Source is not TFPReportFont.';
+  SErrNoStreamInstanceWasSupplied = 'No valid TStream instance was supplied.';
+  SErrIncorrectDescendant = 'AElement is not a TFPReportElementWithChildren descendant.';
+
+  SErrUnknownResultType = 'Unknown result type: "%s"';
+  SErrInvalidFloatingPointValue = '%s is not a valid floating point value';
+  SErrResultTypeMisMatch = 'Result type is %s, expected %s';
+  SErrDuplicateVariable = 'Duplicate variable name : %s';
+  SErrInvalidVariableName = 'Invalid variable name: "%s"';
+  SErrUnknownBandType = 'Unknown band type : %d';
+  SErrInInvalidISO8601DateTime = '%s is an invalid ISO datetime value';
+  SErrCouldNotGetDefaultBandType = 'Could not get default band class for type '
+    +'%s';
+  SErrBandClassMustDescendFrom = 'Band class for band type %s must descend '
+    +'from %s';
+  SErrPageClassMustDescendFrom = 'Page class for must descend from %s';
+  SErrCannotRegisterWithoutDefaultClass = 'Cannot register/unregister editor without default element class.';
+  SErrUnknownElementName = 'Unknown element name : %s';
+  SErrUnknownElementClass = 'Unknown element class : %s';
+
+{ includes Report Checkbox element images }
+{$I fpreportcheckbox.inc}
+
+var
+  uPaperManager: TFPReportPaperManager;
+  uElementFactory: TFPReportElementFactory;
+  uBandFactory : TFPReportBandFactory;
+
+{ Auxiliary routines }
+
+procedure ReportError(Msg: string); inline;
+begin
+  raise EReportError.Create(Msg);
+end;
+
+procedure ReportError(Fmt: string; Args: array of const);
+begin
+  raise EReportError.CreateFmt(Fmt, Args);
+end;
+
+function PaperManager: TFPReportPaperManager;
+begin
+  if uPaperManager = nil then
+    uPaperManager := TFPReportPaperManager.Create(nil);
+  Result := uPaperManager;
+end;
+
+function gElementFactory: TFPReportElementFactory;
+begin
+  if uElementFactory = nil then
+    uElementFactory := TFPReportElementFactory.Create;
+  Result := uElementFactory;
+end;
+
+function gBandFactory: TFPReportBandFactory;
+begin
+  if uBandFactory = nil then
+    uBandFactory := TFPReportBandFactory.Create;
+  Result := uBandFactory;
+end;
+
+Var
+  EM : TFPReportExportManager;
+
+function ReportExportManager: TFPReportExportManager;
+begin
+  If EM=Nil then
+    EM:=TFPReportExportManager.Create(Nil);
+  Result:=EM;
+end;
+
+// TODO: See if the following generic function can replace the multiple enum-to-string functions
+//generic function EnumValueAsName<T>(v: T): String;
+//begin
+//  Result := GetEnumName(TypeInfo(T), LongInt(v));
+//end;
+
+function HorzTextAlignmentToString(AEnum: TFPReportHorzTextAlignment): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportHorzTextAlignment), Ord(AEnum));
+end;
+
+function StringToHorzTextAlignment(AName: string): TFPReportHorzTextAlignment; inline;
+begin
+  Result := TFPReportHorzTextAlignment(GetEnumValue(TypeInfo(TFPReportHorzTextAlignment), AName));
+end;
+
+function VertTextAlignmentToString(AEnum: TFPReportVertTextAlignment): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportVertTextAlignment), Ord(AEnum));
+end;
+
+function StringToVertTextAlignment(AName: string): TFPReportVertTextAlignment; inline;
+begin
+  Result := TFPReportVertTextAlignment(GetEnumValue(TypeInfo(TFPReportVertTextAlignment), AName));
+end;
+
+function RGBToReportColor(R, G, B: Byte): TFPReportColor;
+begin
+  Result := (R shl 16) or (G shl 8) or B;
+end;
+
+function StretchModeToString(AEnum: TFPReportStretchMode): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportStretchMode), Ord(AEnum));
+end;
+
+function StringToStretchMode(AName: string): TFPReportStretchMode; inline;
+begin
+  Result := TFPReportStretchMode(GetEnumValue(TypeInfo(TFPReportStretchMode), AName));
+end;
+
+function ShapeTypeToString(AEnum: TFPReportShapeType): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportShapeType), Ord(AEnum));
+end;
+
+function StringToShapeType(AName: string): TFPReportShapeType; inline;
+begin
+  Result := TFPReportShapeType(GetEnumValue(TypeInfo(TFPReportShapeType), AName));
+end;
+
+function FrameShapeToString(AEnum: TFPReportFrameShape): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportFrameShape), Ord(AEnum));
+end;
+
+function StringToFrameShape(AName: string): TFPReportFrameShape; inline;
+begin
+  Result := TFPReportFrameShape(GetEnumValue(TypeInfo(TFPReportFrameShape), AName));
+end;
+
+function FramePenToString(AEnum: TFPPenStyle): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPPenStyle), Ord(AEnum));
+end;
+
+function StringToFramePen(AName: string): TFPPenStyle; inline;
+begin
+  Result := TFPPenStyle(GetEnumValue(TypeInfo(TFPPenStyle), AName));
+end;
+
+function OrientationToString(AEnum: TFPReportOrientation): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportOrientation), Ord(AEnum));
+end;
+
+function StringToOrientation(AName: string): TFPReportOrientation; inline;
+begin
+  Result := TFPReportOrientation(GetEnumValue(TypeInfo(TFPReportOrientation), AName));
+end;
+
+function PaperOrientationToString(AEnum: TFPReportPaperOrientation): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportPaperOrientation), Ord(AEnum));
+end;
+
+function StringToPaperOrientation(AName: string): TFPReportPaperOrientation; inline;
+begin
+  Result := TFPReportPaperOrientation(GetEnumValue(TypeInfo(TFPReportPaperOrientation), AName));
+end;
+
+function ColumnFooterPositionToString(AEnum: TFPReportFooterPosition): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportFooterPosition), Ord(AEnum));
+end;
+
+function StringToColumnFooterPosition(AName: string): TFPReportFooterPosition; inline;
+begin
+  Result := TFPReportFooterPosition(GetEnumValue(TypeInfo(TFPReportFooterPosition), AName));
+end;
+
+function VisibleOnPageToString(AEnum: TFPReportVisibleOnPage): string; inline;
+begin
+  result := GetEnumName(TypeInfo(TFPReportVisibleOnPage), Ord(AEnum));
+end;
+
+function StringToVisibleOnPage(AName: string): TFPReportVisibleOnPage; inline;
+begin
+  Result := TFPReportVisibleOnPage(GetEnumValue(TypeInfo(TFPReportVisibleOnPage), AName));
+end;
+
+function StringToMemoOptions(const AValue: string): TFPReportMemoOptions;
+var
+  lList: TStrings;
+  lIndex: integer;
+begin
+  Result := [];
+  lList := nil;
+  lList := TStringList.Create;
+  try
+    lList.Delimiter := ',';
+    lList.DelimitedText := AValue;
+    for lIndex := 0 to lList.Count - 1 do
+      Include(Result, TFPReportMemoOption(GetEnumValue(TypeInfo(TFPReportMemoOption), lList[lIndex])));
+  finally
+    lList.Free;
+  end;
+end;
+
+function MemoOptionsToString(const AValue: TFPReportMemoOptions): String;
+var
+  lIndex: integer;
+begin
+  Result := '';
+  for lIndex := Ord(Low(TFPReportMemoOption)) to Ord(High(TFPReportMemoOption)) do
+  begin
+    if TFPReportMemoOption(lIndex) in AValue then
+    begin
+      if Result = '' then
+        Result := GetEnumName(TypeInfo(TFPReportMemoOption), lIndex)
+      else
+        Result := Result + ',' + GetEnumName(TypeInfo(TFPReportMemoOption), lIndex);
+    end;
+  end;
+end;
+
+function FPReportStreamToMIMEEncodeString(const AStream: TStream): string;
+var
+  OutStream: TStringStream;
+  b64encoder: TBase64EncodingStream;
+  LPos: integer;
+begin
+  if not Assigned(AStream) then
+    ReportError(SErrNoStreamInstanceWasSupplied);
+  LPos:= AStream.Position;
+  try
+    OutStream := TStringStream.Create('');
+    try
+      AStream.Position := 0;
+
+      b64encoder := TBase64EncodingStream.Create(OutStream);
+      b64encoder.CopyFrom(AStream, AStream.Size);
+    finally
+      b64encoder.Free;
+      result := OutStream.DataString;
+      OutStream.Free;
+    end;
+  finally
+    AStream.Position:= LPos;
+  end;
+end;
+
+procedure FPReportMIMEEncodeStringToStream(const AString: string; const AStream: TStream);
+var
+  InputStream: TStringStream;
+  b64decoder: TBase64DecodingStream;
+begin
+  if not Assigned(AStream) then
+    ReportError(SErrNoStreamInstanceWasSupplied);
+  InputStream:= TStringStream.Create(AString);
+  try
+    AStream.Size := 0;
+    b64decoder := TBase64DecodingStream.Create(InputStream, bdmMIME);
+    try
+      AStream.CopyFrom(b64decoder, b64decoder.Size);
+      AStream.Position:=0;
+    finally
+      b64decoder.Free;
+    end;
+  finally
+    InputStream.Free;
+  end;
+end;
+
+procedure FillMem(Dest: pointer; Size: longint; Data: Byte );
+begin
+  FillChar(Dest^, Size, Data);
+end;
+
+function SortDataBands(Item1, Item2: Pointer): Integer;
+begin
+  if TFPReportCustomDataBand(Item1).DisplayPosition < TFPReportCustomDataBand(Item2).DisplayPosition then
+    Result := -1
+  else if TFPReportCustomDataBand(Item1).DisplayPosition > TFPReportCustomDataBand(Item2).DisplayPosition then
+    Result := 1
+  else
+    Result := 0;
+end;
+
+Type
+
+  { TFPReportReg }
+
+  TFPReportExportReg = Class(TObject)
+  private
+    FClass: TFPReportExporterClass;
+    FonConfig: TFPReportExporterConfigHandler;
+  Public
+    Constructor Create(AClass : TFPReportExporterClass);
+    Property TheClass : TFPReportExporterClass Read FClass;
+    Property OnConfig : TFPReportExporterConfigHandler Read FonConfig Write FOnConfig;
+  end;
+
+{ TFPReportBandFactory }
+
+Function GetDefaultBandType(AType : TFPReportBandType) : TFPReportCustomBandClass;
+
+begin
+  Case AType of
+    btUnknown       : Result:=Nil;
+    btPageHeader    : Result:=TFPReportPageHeaderBand;
+    btReportTitle   : Result:=TFPReportTitleBand;
+    btColumnHeader  : Result:=TFPReportColumnHeaderBand;
+    btDataHeader    : Result:=TFPReportDataHeaderBand;
+    btGroupHeader   : Result:=TFPReportGroupHeaderBand;
+    btDataband      : Result:=TFPReportDataBand;
+    btGroupFooter   : Result:=TFPReportGroupFooterBand;
+    btDataFooter    : Result:=TFPReportDataFooterBand;
+    btColumnFooter  : Result:=TFPReportColumnFooterBand;
+    btReportSummary : Result:=TFPReportSummaryBand;
+    btPageFooter    : Result:=TFPReportPageFooterBand;
+    btChild         : Result:=TFPReportChildBand;
+  else
+    raise EReportError.CreateFmt(SErrUnknownBandType, [Ord(AType)]);
+  end;
+end;
+
+const
+  { Summary of ISO 8601  http://www.cl.cam.ac.uk/~mgk25/iso-time.html }
+  ISO8601DateFormat = 'yyyymmdd"T"hhnnss';    // for storage
+
+function DateTimeToISO8601(const ADateTime: TDateTime): string;
+begin
+  Result := FormatDateTime(ISO8601DateFormat,ADateTime);
+  if Pos('18991230', Result) = 1 then
+    begin
+    Delete(Result,1,8);
+    Result:='00000000'+Result;
+    end;
+end;
+
+function ISO8601ToDateTime(const AValue: string): TDateTime;
+var
+  lY, lM, lD, lH, lMi, lS: Integer;
+begin
+  if Trim(AValue) = '' then
+  begin
+    Result := 0;
+    Exit; //==>
+  end;
+
+    //          1         2
+    // 12345678901234567890123
+    // yyyymmddThhnnss
+  if not (TryStrToInt(Copy(AValue, 1, 4),lY)
+      and TryStrToInt(Copy(AValue, 5, 2),lM)
+      and TryStrToInt(Copy(AValue, 7, 2),lD)
+      and TryStrToInt(Copy(AValue, 10, 2),lH)
+      and TryStrToInt(Copy(AValue, 12, 2),lMi)
+      and TryStrToInt(Copy(AValue, 14, 2),lS)) then
+      raise EConvertError.CreateFmt(SErrInInvalidISO8601DateTime, [AValue]);
+
+  { Cannot EncodeDate if any part equals 0. EncodeTime is okay. }
+  if (lY = 0) or (lM = 0) or (lD = 0) then
+    Result := EncodeTime(lH, lMi, lS, 0)
+  else
+    Result := EncodeDate(lY, lM, lD) + EncodeTime(lH, lMi, lS, 0);
+end;
+
+{ TFPReportElementEditor }
+
+procedure TFPReportElementEditor.SetElement(AValue: TFPReportElement);
+begin
+  if FElement=AValue then Exit;
+  FElement:=AValue;
+end;
+
+class function TFPReportElementEditor.DefaultClass: TFPReportElementClass;
+begin
+  Result:=Nil;
+end;
+
+class procedure TFPReportElementEditor.RegisterEditor;
+
+Var
+  C : TFPReportElementClass;
+
+begin
+  C:=DefaultClass;
+  If C=Nil then
+    Raise EReportError.Create(SErrCannotRegisterWithoutDefaultClass);
+  gElementFactory.RegisterEditorClass(C,Self);
+end;
+
+class procedure TFPReportElementEditor.UnRegisterEditor;
+Var
+  C : TFPReportElementClass;
+
+begin
+  C:=DefaultClass;
+  If C=Nil then
+    Raise EReportError.Create(SErrCannotRegisterWithoutDefaultClass);
+  gElementFactory.UnRegisterEditorClass(C,Self);
+end;
+
+{ TFPReportDataCollection }
+
+function TFPReportDataCollection.GetData(AIndex : Integer): TFPReportDataItem;
+begin
+  Result:=Items[Aindex] as TFPReportDataItem;
+end;
+
+procedure TFPReportDataCollection.SetData(AIndex : Integer;
+  AValue: TFPReportDataItem);
+begin
+  Items[Aindex]:=AValue;
+end;
+
+function TFPReportDataCollection.IndexOfReportData(AData: TFPReportData
+  ): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) and (GetData(Result).Data<>AData) do
+    Dec(Result);
+end;
+
+function TFPReportDataCollection.IndexOfReportData(const ADataName: String
+  ): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) and ((GetData(Result).Data=Nil) or (CompareText(GetData(Result).Data.Name,ADataName)<>0)) do
+    Dec(Result);
+end;
+
+function TFPReportDataCollection.FindReportDataItem(const ADataName: String): TFPReportDataItem;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfReportData(ADataName);
+  if I=-1 then
+    Result:=Nil
+  else
+    Result:=GetData(I);
+end;
+
+function TFPReportDataCollection.FindReportDataItem(AData: TFPReportData): TFPReportDataItem;
+Var
+  I : Integer;
+begin
+  I:=IndexOfReportData(AData);
+  if I=-1 then
+    Result:=Nil
+  else
+    Result:=GetData(I);
+end;
+
+function TFPReportDataCollection.FindReportData(const ADataName: String): TFPReportData;
+Var
+  I : TFPReportDataItem;
+begin
+  I:=FindReportDataItem(aDataName);
+  If Assigned(I) then
+    Result:=I.Data
+  else
+    Result:=Nil;
+end;
+
+function TFPReportDataCollection.AddReportData(AData: TFPReportData ): TFPReportDataItem;
+begin
+  Result:=Add as TFPReportDataItem;
+  Result.Data:=AData;
+end;
+
+{ TFPReportDataItem }
+
+procedure TFPReportDataItem.SetData(AValue: TFPReportData);
+begin
+  if FData=AValue then Exit;
+  FData:=AValue;
+end;
+
+function TFPReportDataItem.GetDisplayName: string;
+begin
+  if Assigned(Data) then
+    Result:=Data.Name
+  else
+    Result:=inherited GetDisplayName;
+end;
+
+procedure TFPReportDataItem.Assign(Source: TPersistent);
+begin
+  if Source is TFPReportDataItem then
+    FData:=TFPReportDataItem(Source).Data
+  else
+    inherited Assign(Source);
+end;
+
+{ TFPReportVariables }
+
+function TFPReportVariables.GetV(aIndex : Integer): TFPReportVariable;
+begin
+  Result:=Items[aIndex] as TFPReportVariable;
+end;
+
+procedure TFPReportVariables.SetV(aIndex : Integer; AValue: TFPReportVariable);
+begin
+  Items[aIndex]:=AValue;
+end;
+
+function TFPReportVariables.IndexOfVariable(aName: String): Integer;
+begin
+  Result:=Count-1;
+  While (Result>=0) and (CompareText(getV(Result).Name,aName)<>0) do
+    Dec(Result);
+end;
+
+function TFPReportVariables.FindVariable(aName: String): TFPReportVariable;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfVariable(aName);
+  if I=-1 then
+    Result:=nil
+  else
+    Result:=getV(I);
+end;
+
+function TFPReportVariables.AddVariable(aName: String): TFPReportVariable;
+begin
+  if (IndexOfVariable(aName)<>-1) then
+    raise EReportError.CreateFmt(SErrDuplicateVariable, [aName]);
+  Result:=add as TFPReportVariable;
+  Result.Name:=aName;
+  Result.DataType:=rtString;
+  Result.AsString:='';
+end;
+
+{ TFPReportVariable }
+
+procedure TFPReportVariable.SetValue(AValue: String);
+
+Var
+  C : Integer;
+  f : TExprFloat;
+
+begin
+  if GetValue=AValue then
+    Exit;
+  if (AValue<>'') then
+    Case DataType of
+      rtBoolean  : AsBoolean:=StrToBool(AValue);
+      rtInteger  : AsInteger:=StrToInt(AValue);
+      rtFloat    : begin
+                   Val(AValue,F,C);
+                   if C<>0 then
+                     raise EConvertError.CreateFmt(
+                       SErrInvalidFloatingPointValue, [AValue]);
+                   ASFloat:=F;
+                   end;
+      rtDateTime : asDateTime:=ISO8601ToDateTime(AValue);
+      rtString   : AsString:=AValue;
+    else
+      raise EConvertError.CreateFmt(SErrUnknownResultType, [GetEnumName(TypeInfo
+        (TResultType), Ord(DataType))])
+    end;
+end;
+
+procedure TFPReportVariable.GetRTValue(Var Result: TFPExpressionResult; ConstRef AName: ShortString);
+begin
+  if (Result.ResultType=Self.DataType) then
+    Result:=FValue;
+end;
+
+procedure TFPReportVariable.SaveValue;
+begin
+  FSavedValue:=FValue;
+end;
+
+procedure TFPReportVariable.RestoreValue;
+begin
+  FValue:=FSavedValue;
+end;
+
+
+
+function TFPReportVariable.GetValue: String;
+begin
+  Case DataType of
+    rtBoolean  : Result:=BoolToStr(AsBoolean,True);
+    rtInteger  : Result:=IntToStr(AsInteger);
+    rtFloat    : Str(AsFloat,Result);
+    rtDateTime : Result:=DateTimeToISO8601(AsDateTime);
+    rtString   : Result:=AsString
+  else
+    Raise EConvertError.CreateFmt(SErrUnknownResultType,[GetEnumName(TypeInfo(TResultType),Ord(DataType))])
+  end;
+end;
+
+function TFPReportVariable.GetER: TFPExpressionResult;
+
+begin
+  Result:=FValue;
+end;
+
+procedure TFPReportVariable.CheckType(aType: TResultType);
+begin
+  if DataType<>aType then
+    raise EConvertError.CreateFmt(SErrResultTypeMisMatch, [
+       GetEnumName(TypeInfo(TResultType),Ord(DataType)),
+       GetEnumName(TypeInfo(TResultType),Ord(aType))
+    ]);
+end;
+
+function TFPReportVariable.GetAsInteger: Int64;
+begin
+  CheckType(rtInteger);
+  Result:=FValue.ResInteger;
+end;
+
+function TFPReportVariable.GetAsBoolean: Boolean;
+begin
+  CheckType(rtBoolean);
+  Result:=FValue.Resboolean;
+end;
+
+function TFPReportVariable.GetAsDateTime: TDateTime;
+begin
+  CheckType(rtDateTime);
+  Result:=FValue.ResDateTime;
+end;
+
+function TFPReportVariable.GetAsFloat: TexprFloat;
+begin
+  CheckType(rtFloat);
+  Result:=FValue.ResFloat;
+end;
+
+function TFPReportVariable.GetAsString: String;
+begin
+  CheckType(rtString);
+  Result:=FValue.ResString;
+end;
+
+function TFPReportVariable.GetDataType: TResultType;
+begin
+  Result:=FValue.ResultType;
+end;
+
+procedure TFPReportVariable.SetAsInteger(AValue: Int64);
+begin
+  DataType:=rtinteger;
+  FValue.ResInteger:=AValue;
+end;
+
+procedure TFPReportVariable.SetAsString(AValue: String);
+begin
+  DataType:=rtString;
+  FValue.resString:=AValue;
+end;
+
+procedure TFPReportVariable.SetAsBoolean(AValue: Boolean);
+begin
+  FValue.ResultType:=rtBoolean;
+  FValue.resBoolean:=AValue;
+end;
+
+procedure TFPReportVariable.SetAsDateTime(AValue: TDateTime);
+begin
+  FValue.ResultType:=rtDateTime;
+  FValue.ResDateTime:=AValue;
+end;
+
+procedure TFPReportVariable.SetAsFloat(AValue: TExprFloat);
+begin
+  FValue.ResultType:=rtFloat;
+  FValue.ResFloat:=AValue;
+end;
+
+procedure TFPReportVariable.SetDataType(AValue: TResultType);
+begin
+  if FValue.ResultType=AValue then
+    exit;
+  if FValue.ResultType=rtString then
+    FValue.resString:='';
+  FValue.ResultType:=AValue;
+end;
+
+procedure TFPReportVariable.SetER(AValue: TFPExpressionResult);
+
+begin
+  FValue:=AValue;
+end;
+
+procedure TFPReportVariable.SetName(AValue: String);
+begin
+  if FName=AValue then Exit;
+  {$IF FPC_FULLVERSION < 30002}
+  if Not IsValidIdent(aValue) then
+  {$ELSE}
+  if Not IsValidIdent(aValue,True,true) then
+  {$ENDIF}
+    raise EReportError.CreateFmt(SErrInvalidVariableName, [aValue]);
+  if (Collection is TFPReportVariables) then
+    If ((Collection as TFPReportVariables).FindVariable(AValue)<>Nil) then
+      raise EReportError.CreateFmt(SErrDuplicateVariable, [aValue]);
+
+  FName:=AValue;
+end;
+
+procedure TFPReportVariable.Assign(Source: TPersistent);
+
+Var
+  V : TFPReportVariable;
+
+begin
+  if Source is TFPReportVariable then
+    begin
+    V:=Source as TFPReportVariable;
+    FName:=V.Name;
+    FValue:=V.FValue;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+function TFPReportBandFactory.getBandClass(aIndex : TFPReportBandType
+  ): TFPReportCustomBandClass;
+begin
+  Result:=FBandTypes[aIndex];
+end;
+
+constructor TFPReportBandFactory.Create;
+
+Var
+  T : TFPReportBandType;
+
+begin
+  FPageClass:=TFPReportPage;
+  for T in TFPReportBandType do
+    begin
+    FBandTypes[T]:=GetDefaultBandType(T);
+    end;
+end;
+
+function TFPReportBandFactory.RegisterBandClass(aBandType: TFPReportBandType;
+  AClass: TFPReportCustomBandClass): TFPReportCustomBandClass;
+
+Var
+  D : TFPReportCustomBandClass;
+  N : String;
+
+
+begin
+  D:=GetDefaultBandType(aBandtype);
+  N:=GetEnumName(TypeInfo(TFPReportBandType),Ord(ABandType));
+  if (D=Nil) then
+    raise EReportError.CreateFmt(SErrCouldNotGetDefaultBandType, [N]);
+  If Not AClass.InheritsFrom(D) then
+    raise EReportError.CreateFmt(SErrBandClassMustDescendFrom, [N, D.ClassName]
+      );
+  Result:=FBandTypes[aBandType];
+  FBandTypes[aBandType]:=AClass;
+end;
+
+function TFPReportBandFactory.RegisterPageClass(aClass: TFPReportCustomPageClass
+  ): TFPReportCustomPageClass;
+begin
+  If Not AClass.InheritsFrom(TFPReportCustomPage) then
+    raise EReportError.CreateFmt(SErrPageClassMustDescendFrom, [
+      TFPReportCustomPageClass.ClassName]);
+  FPageClass:=AClass;
+end;
+
+{ TFPReportExportReg }
+
+constructor TFPReportExportReg.Create(AClass: TFPReportExporterClass);
+begin
+  FCLass:=AClass;
+end;
+
+
+{ TFPReportExportManager }
+
+
+function TFPReportExportManager.GetExporter(AIndex : Integer
+  ): TFPReportExporterClass;
+begin
+  Result:=TFPReportExportReg(FList.Items[AIndex]).TheClass;
+end;
+
+function TFPReportExportManager.GetExporterCount: Integer;
+
+begin
+  Result:=FList.Count;
+end;
+
+procedure TFPReportExportManager.RegisterExport(AClass: TFPReportExporterClass);
+begin
+  if AClass=Nil then
+    Raise EReportError.Create(SErrRegisterEmptyExporter);
+  If IndexOfExporter(AClass.Name)<>-1 then
+    Raise EReportError.CreateFmt(SErrRegisterDuplicateExporter,[AClass.Name]);
+  FList.Add(TFPReportExportReg.Create(AClass));
+end;
+
+procedure TFPReportExportManager.UnRegisterExport(AClass: TFPReportExporterClass);
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfExporter(AClass);
+  if I<>-1 then
+    FList.Delete(i);
+end;
+
+function TFPReportExportManager.ConfigExporter(AExporter: TFPReportExporter
+  ): Boolean;
+
+Var
+  H : TFPReportExporterConfigHandler;
+  I : Integer;
+
+begin
+  H:=ExporterConfigHandler(TFPReportExporterClass(AExporter.ClassType));
+  if (H=Nil) then
+    H:=AExporter.DefaultConfig;
+  if H=Nil then
+    H:=OnConfigCallBack;
+  Result:=False;
+  If Assigned(H) then
+    H(Self,AExporter,Result);
+  Result:=Not Result;
+end;
+
+constructor TFPReportExportManager.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FList:=TFPObjectList.Create(True);
+end;
+
+destructor TFPReportExportManager.Destroy;
+begin
+  Clear;
+  FList.Free;
+  inherited Destroy;
+end;
+
+procedure TFPReportExportManager.Clear;
+begin
+  FList.Clear;
+end;
+
+function TFPReportExportManager.IndexOfExporter(const AName: String): Integer;
+begin
+  Result:=ExporterCount-1;
+  While (Result>=0) and (CompareText(AName,Exporter[Result].Name)<>0) do
+    Dec(Result);
+end;
+
+function TFPReportExportManager.IndexOfExporter(
+  const AClass: TFPReportExporterClass): Integer;
+begin
+  Result:=ExporterCount-1;
+  While (Result>=0) and (AClass<>Exporter[Result]) do
+    Dec(Result);
+end;
+
+function TFPReportExportManager.FindExporter(const AName: String): TFPReportExporterClass;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfExporter(AName);
+  If I<>-1 then
+    Result:=Exporter[i]
+  else
+    Result:=Nil;
+end;
+
+function TFPReportExportManager.ExporterConfigHandler(
+  const AClass: TFPReportExporterClass): TFPReportExporterConfigHandler;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfExporter(AClass);
+  if I<>-1 then
+    Result:=TFPReportExportReg(FList[i]).OnConfig
+  else
+    Result:=nil;
+end;
+
+procedure TFPReportExportManager.RegisterConfigHandler(const AName: String;
+  ACallBack: TFPReportExporterConfigHandler);
+
+Var
+  I : integer;
+
+begin
+  I:=IndexOfExporter(AName);
+  If (I=-1) Then
+    Raise EReportError.CreateFmt(SErrUnknownExporter,[AName]);
+  TFPReportExportReg(FList[i]).OnConfig:=ACallBack;
+end;
+
+{ TBandList }
+
+function TBandList.GetCount: Integer;
+begin
+  Result := FList.Count;
+end;
+
+function TBandList.GetItems(AIndex: Integer): TFPReportCustomBand;
+begin
+  Result := TFPReportCustomBand(FList.Items[AIndex]);
+end;
+
+procedure TBandList.SetItems(AIndex: Integer; AValue: TFPReportCustomBand);
+begin
+  FList.Items[AIndex] := AValue;
+end;
+
+constructor TBandList.Create;
+begin
+  FList := TFPList.Create;
+end;
+
+destructor TBandList.Destroy;
+begin
+  FList.Clear;
+  FreeAndNil(FList);
+  inherited Destroy;
+end;
+
+function TBandList.Add(AItem: TFPReportCustomBand): Integer;
+begin
+  Result := -1;
+  if Assigned(AItem) then
+    if FList.IndexOf(AItem) = -1 then { we don't add duplications }
+      Result := FList.Add(AItem);
+end;
+
+procedure TBandList.Clear;
+begin
+  FList.Clear;
+end;
+
+procedure TBandList.Delete(AIndex: Integer);
+begin
+  FList.Delete(AIndex);
+end;
+
+function TBandList.Find(ABand: TFPReportBandClass): TFPReportCustomBand;
+begin
+  Find(ABand, Result);
+end;
+
+function TBandList.Find(ABand: TFPReportBandClass; out AResult: TFPReportCustomBand): Integer;
+var
+  i: integer;
+begin
+  AResult := nil;
+  Result := -1;
+  for i := 0 to Count-1 do
+  begin
+    if Items[i] is ABand then
+    begin
+      Result := i;
+      AResult := Items[i];
+      Break;
+    end;
+  end;
+end;
+
+procedure TBandList.Sort(Compare: TListSortCompare);
+begin
+  FList.Sort(Compare);
+end;
+
+{ TFPReportCustomMemo }
+
+procedure TFPReportCustomMemo.SetText(AValue: TFPReportString);
+begin
+  if FText = AValue then
+    Exit;
+  FText := AValue;
+  Changed;
+end;
+
+function TFPReportCustomMemo.GetFont: TFPReportFont;
+begin
+  if UseParentFont then
+  begin
+    if Assigned(Owner) then
+      Result := TFPReportCustomBand(Owner).Font
+    else
+    begin
+      if not Assigned(FFont) then
+        FFont := TFPReportFont.Create;
+      Result := FFont;
+    end;
+  end
+  else
+    Result := FFont;
+end;
+
+procedure TFPReportCustomMemo.SetUseParentFont(AValue: Boolean);
+begin
+  if FUseParentFont = AValue then
+    Exit;
+  FUseParentFont := AValue;
+  if FUseParentFont then
+    FreeAndNil(FFont)
+  else
+  begin
+    FFont := TFPReportFont.Create;
+    if Assigned(Owner) then
+      FFont.Assign(TFPReportCustomBand(Owner).Font);
+  end;
+  Changed;
+end;
+
+procedure TFPReportCustomMemo.WrapText(const AText: String; var ALines: TStrings; const ALineWidth: TFPReportUnits; out
+  AHeight: TFPReportUnits);
+var
+  maxw: single; // value in pixels
+  n: integer;
+  s: string;
+  c: char;
+  lWidth: single;
+  lFC: TFPFontCacheItem;
+  lDescenderHeight: single;
+  lHeight: single;
+
+  // -----------------
+  { All = True) indicates that if the text is split over multiple lines the last
+    line must also be processed before continuing. If All = False, then double
+    CR can be ignored. }
+  procedure AddLine(all: boolean);
+  var
+    w: single;
+    m: integer;
+    s2, s3: string;
+  begin
+    s2  := s;
+    w   := lFC.TextWidth(s2, Font.Size);
+    if (Length(s2) > 1) and (w > maxw) then
+    begin
+      while w > maxw do
+      begin
+        m := Length(s);
+        repeat
+          Dec(m);
+          s2  := Copy(s,1,m);
+          w   := lFC.TextWidth(s2, Font.Size);
+        until w <= maxw;
+
+        s3 := s2; // we might need the value of s2 later again
+
+        // are we in the middle of a word. If so find the beginning of word.
+        while (m > 0) and (Copy(s2, m, m+1) <> ' ') do
+        begin
+          Dec(m);
+          s2  := Copy(s,1,m);
+        end;
+
+        if s2 = '' then
+        begin
+          s2 := s3;
+          m := Length(s2);
+          { We reached the beginning of the line without finding a word that fits the maxw.
+            So we are forced to use a longer than maxw word. We were in the middle of
+            a word, so now find the end of the current word. }
+          while (m < Length(s)) and (Copy(s2, m, m+1) <> ' ') do
+          begin
+            Inc(m);
+            s2  := Copy(s,1,m);
+          end;
+        end;
+        ALines.Add(s2);
+        s   := Copy(s, m+1, Length(s));
+        s2  := s;
+        w   := lFC.TextWidth(s2, Font.Size);
+      end; { while }
+      if all then
+      begin
+        if s2 <> '' then
+          ALines.Add(s2);
+        s := '';
+      end;
+    end
+    else
+    begin
+      if s2 <> '' then
+        ALines.Add(s2);
+      s := '';
+    end; { if/else }
+  end;
+
+begin
+  if AText = '' then
+    Exit;
+
+  if ALineWidth = 0 then
+    Exit;
+
+  { We are doing a PostScript Name lookup (it contains Bold, Italic info) }
+  lFC := gTTFontCache.Find(Font.Name);
+  if not Assigned(lFC) then
+    raise EReportFontNotFound.CreateFmt(SErrFontNotFound, [Font.Name]);
+  { result is in pixels }
+  lWidth := lFC.TextWidth(Text, Font.Size);
+  lHeight := lFC.TextHeight(Text, Font.Size, lDescenderHeight);
+  { convert pixels to mm as our Reporting Units are defined as mm. }
+  AHeight := PixelsToMM(lHeight+lDescenderHeight);
+
+  s := '';
+  ALines.Clear;
+  n := 1;
+  maxw := mmToPixels(ALineWidth - TextAlignment.LeftMargin - TextAlignment.RightMargin);
+  { Do we really need to do text wrapping? There must be no linefeed characters and lWidth must be less than maxw. }
+  if ((Pos(#13, AText) = 0) and (Pos(#10, AText) = 0)) and (lWidth <= maxw) then
+  begin
+    ALines.Add(AText);
+    Exit;
+  end;
+
+  { We got here, so wrapping is needed. First process line wrapping as indicated
+    by LineEnding characters in the text. }
+  while n <= Length(AText) do
+  begin
+    c := AText[n];
+    if (c = #13) or (c = #10) then
+    begin
+      { See code comment of AddLine() for the meaning of the True argument. }
+      AddLine(true);
+      if (c = #13) and (n < Length(AText)) and (AText[n+1] = #10) then
+        Inc(n);
+    end
+    else
+      s := s + c;
+    Inc(n);
+  end; { while }
+
+  { Now wrap lines that are longer than ALineWidth }
+  AddLine(true);
+end;
+
+procedure TFPReportCustomMemo.ApplyStretchMode(const AHeight: TFPReportUnits);
+var
+  j: TFPReportUnits;
+begin
+  if Assigned(RTLayout) then
+  begin
+    j :=((AHeight + LineSpacing) * TextLines.Count) + TextAlignment.TopMargin + TextAlignment.BottomMargin;
+    if j > RTLayout.Height then { only grow height if needed. We don't shrink. }
+      RTLayout.Height := j;
+  end;
+end;
+
+{ this affects only X coordinate of text blocks }
+procedure TFPReportCustomMemo.ApplyHorzTextAlignment;
+var
+  i: integer;
+  tb: TFPTextBlock;
+  lList: TFPList;
+  lLastYPos: TFPReportUnits;
+
+  procedure ProcessLeftJustified;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+    lXOffset: TFPReportUnits;
+  begin
+    if TextAlignment.LeftMargin = 0 then
+      exit;
+    { All the text blocks must move by LeftMargin to the right. }
+    lXOffset := TextAlignment.LeftMargin;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Left := lXOffset + b.Pos.Left
+    end;
+  end;
+
+  procedure ProcessRightJustified;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+    lXOffset: TFPReportUnits;
+  begin
+    lXOffset := Layout.Width - TextAlignment.RightMargin;
+    for idx := lList.Count-1 downto 0 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Left := lXOffset - b.Width;
+      lXOffset := b.Pos.Left;
+    end;
+  end;
+
+  procedure ProcessCentered;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+    lXOffset: TFPReportUnits;
+    lTotalWidth: TFPReportUnits;
+  begin
+    lTotalWidth := 0;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      lTotalWidth := lTotalWidth + b.Width;
+    end;
+    lXOffset := (Layout.Width - lTotalWidth) / 2;
+    if lXOffset < 0.0 then { it should never be, but lets play it safe }
+      lXOffset := 0.0;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Left := lXOffset;
+      lXOffset := lXOffset + b.Width;
+    end;
+  end;
+
+  procedure ProcessWidth;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+    lXOffset: TFPReportUnits;
+    lSpace: TFPReportUnits;
+    lTotalWidth: TFPReportUnits;
+  begin
+    lTotalWidth := 0;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      lTotalWidth := lTotalWidth + b.Width;
+    end;
+    lSpace := (Layout.Width - TextAlignment.LeftMargin - TextAlignment.RightMargin - lTotalWidth) / (lList.Count-1);
+    { All the text blocks must move by LeftMargin to the right. }
+    lXOffset := TextAlignment.LeftMargin;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Left := lXOffset;
+      lXOffset := lXOffset + b.Width + lSpace;
+    end;
+  end;
+
+begin
+  lList := TFPList.Create;
+  lLastYPos := 0;
+  for i := 0 to FTextBlockList.Count-1 do
+  begin
+    tb := FTextBlockList[i];
+    if tb.Pos.Top = lLastYPos then // still on the same text line
+      lList.Add(tb)
+    else
+    begin
+      { a new line has started - process what we have collected in lList }
+      case TextAlignment.Horizontal of
+        taLeftJustified:   ProcessLeftJustified;
+        taRightJustified:  ProcessRightJustified;
+        taCentered:        ProcessCentered;
+        taWidth:           ProcessWidth;
+      end;
+      lList.Clear;
+      lLastYPos := tb.Pos.Top;
+      lList.Add(tb)
+    end; { if..else }
+  end; { for i }
+
+  { process the last text line's items }
+  if lList.Count > 0 then
+  begin
+    case TextAlignment.Horizontal of
+      taLeftJustified:   ProcessLeftJustified;
+      taRightJustified:  ProcessRightJustified;
+      taCentered:        ProcessCentered;
+      taWidth:           ProcessWidth;
+    end;
+  end;
+  lList.Free;
+end;
+
+{ this affects only Y coordinate of text blocks }
+procedure TFPReportCustomMemo.ApplyVertTextAlignment;
+var
+  i: integer;
+  tb: TFPTextBlock;
+  lList: TFPList;
+  lLastYPos: TFPReportUnits;
+  lTotalHeight: TFPReportUnits;
+  lYOffset: TFPReportUnits;
+
+  procedure ProcessTop;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+  begin
+    if lList.Count = 0 then
+      Exit;
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Top := lYOffset;
+    end;
+    lYOffset := lYOffset + LineSpacing + b.Height + b.Descender;
+  end;
+
+  procedure ProcessCenter;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+  begin
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Top := lYOffset;
+    end;
+    lYOffset := lYOffset + LineSpacing + b.Height + b.Descender;
+  end;
+
+  procedure ProcessBottom;
+  var
+    idx: integer;
+    b: TFPTextBlock;
+  begin
+    for idx := 0 to lList.Count-1 do
+    begin
+      b := TFPTextBlock(lList[idx]);
+      b.Pos.Top := lYOffset;
+    end;
+    lYOffset := lYOffset - LineSpacing - b.Height - b.Descender;
+  end;
+
+begin
+  if FTextBlockList.Count = 0 then
+    Exit;
+  lList := TFPList.Create;
+  try
+  lLastYPos := FTextBlockList[FTextBlockList.Count-1].Pos.Top;  // last textblock's Y coordinate
+  lTotalHeight := 0;
+
+  if TextAlignment.Vertical = tlTop then
+  begin
+    if TextAlignment.TopMargin = 0 then
+      Exit; // nothing to do
+    lYOffset := TextAlignment.TopMargin;
+    for i := 0 to FTextBlockList.Count-1 do
+    begin
+      tb := FTextBlockList[i];
+      if tb.Pos.Top = lLastYPos then // still on the same text line
+        lList.Add(tb)
+      else
+      begin
+        { a new line has started - process what we have collected in lList }
+        ProcessTop;
+
+        lList.Clear;
+        lLastYPos := tb.Pos.Top;
+        lList.Add(tb)
+      end; { if..else }
+    end; { for i }
+  end
+
+  else if TextAlignment.Vertical = tlBottom then
+  begin
+    lYOffset := Layout.Height;
+    for i := FTextBlockList.Count-1 downto 0 do
+    begin
+      tb := FTextBlockList[i];
+      if i = FTextBlockList.Count-1 then
+        lYOffset := lYOffset - tb.Height - tb.Descender - TextAlignment.BottomMargin;  // only need to do this for one line
+      if tb.Pos.Top = lLastYPos then // still on the same text line
+        lList.Add(tb)
+      else
+      begin
+        { a new line has started - process what we have collected in lList }
+        ProcessBottom;
+
+        lList.Clear;
+        lLastYPos := tb.Pos.Top;
+        lList.Add(tb)
+      end; { if..else }
+    end; { for i }
+  end
+
+  else if TextAlignment.Vertical = tlCenter then
+  begin
+    { First, collect the total height of all the text lines }
+    lTotalHeight := 0;
+    lLastYPos := 0;
+    for i := 0 to FTextBlockList.Count-1 do
+    begin
+      tb := FTextBlockList[i];
+      if i = 0 then  // do this only for the first block
+        lTotalHeight := tb.Height + tb.Descender;
+      if tb.Pos.Top = lLastYPos then // still on the same text line
+        Continue
+      else
+      begin
+        { a new line has started - process what we have collected in lList }
+        lTotalHeight := lTotalHeight + LineSpacing + tb.Height + tb.Descender;
+      end; { if..else }
+      lLastYPos := tb.Pos.Top;
+    end; { for i }
+
+    { Now process them line-by-line }
+    lList.Clear;
+    lYOffset := (Layout.Height - lTotalHeight) / 2;
+    lLastYPos := 0;
+    for i := 0 to FTextBlockList.Count-1 do
+    begin
+      tb := FTextBlockList[i];
+      if tb.Pos.Top = lLastYPos then // still on the same text line
+        lList.Add(tb)
+      else
+      begin
+        { a new line has started - process what we have collected in lList }
+        ProcessCenter;
+
+        lList.Clear;
+        lLastYPos := tb.Pos.Top;
+        lList.Add(tb)
+      end; { if..else }
+    end; { for i }
+  end;
+
+  { process the last text line's items }
+  if lList.Count > 0 then
+  begin
+    case TextAlignment.Vertical of
+      tlTop:     ProcessTop;
+      tlCenter:  ProcessCenter;
+      tlBottom:  ProcessBottom;
+    end;
+  end;
+
+  finally
+    lList.Free;
+  end;
+end;
+
+{ package the text into TextBlock objects. We don't apply Memo Margins here - that
+  gets done in the Apply*TextAlignment() methods. }
+procedure TFPReportCustomMemo.PrepareTextBlocks;
+var
+  i: integer;
+begin
+  { blockstate is cleared outside the FOR loop because the font state could
+    roll over to multiple lines. }
+  FTextBlockState := [];
+  FTextBlockYOffset := 0;
+  FLastURL := '';
+  FLastFGColor := clNone;
+  FLastBGColor := clNone;
+
+  for i := 0 to FTextLines.Count-1 do
+  begin
+    FTextBlockXOffset := 0;
+    if Assigned(FCurTextBlock) then
+      FTextBlockYOffset := FTextBlockYOffset + FCurTextBlock.Height + FCurTextBlock.Descender + LineSpacing;
+
+    if moAllowHTML in Options then
+    begin
+      FParser := THTMLParser.Create(FTextLines[i]);
+      try
+        FParser.OnFoundTag := @HTMLOnFoundTag;
+        FParser.OnFoundText := @HTMLOnFoundText;
+        FParser.Exec;
+      finally
+        FParser.Free;
+      end;
+    end
+    else
+    begin
+      if TextAlignment.Horizontal <> taWidth then
+        AddSingleTextBlock(FTextLines[i])
+      else
+        AddMultipleTextBlocks(FTextLines[i]);
+    end;
+  end; { for i }
+end;
+
+function TFPReportCustomMemo.GetTextLines: TStrings;
+begin
+  if StretchMode <> smDontStretch then
+    Result := FTextLines
+  else
+    Result := nil;
+end;
+
+procedure TFPReportCustomMemo.SetLineSpacing(AValue: TFPReportUnits);
+begin
+  if FLineSpacing = AValue then
+    Exit;
+  FLineSpacing := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomMemo.HTMLOnFoundTag(NoCaseTag, ActualTag: string);
+var
+  v: string;
+begin
+  if NoCaseTag = '<B>' then
+    Include(FTextBlockState, htBold)
+  else if NoCaseTag = '</B>' then
+    Exclude(FTextBlockState, htBold)
+  else if NoCaseTag = '<I>' then
+    Include(FTextBlockState, htItalic)
+  else if NoCaseTag = '</I>' then
+    Exclude(FTextBlockState, htItalic)
+  else if (FParser.GetTagName(NoCaseTag) = 'A') then
+    FLastURL := FParser.GetVal(ActualTag, 'href')
+  else if (FParser.GetTagName(NoCaseTag) = '/A') then
+    FLastURL := ''
+  else if FParser.GetTagName(NoCaseTag) = 'FONT' then
+  begin
+    { process the opening tag }
+    v := FParser.GetVal(NoCaseTag, 'color');
+    if v <> '' then
+      FLastFGColor := HtmlColorToFPReportColor(v);
+    v := FParser.GetVal(NoCaseTag, 'bgcolor');
+    if v <> '' then
+      FLastBGColor := HtmlColorToFPReportColor(v);
+  end
+  else if FParser.GetTagName(NoCaseTag) = '/FONT' then
+  begin
+    { process the closing tag }
+    FLastFGColor := clNone;
+    FLastBGColor := clNone;
+  end;
+end;
+
+procedure TFPReportCustomMemo.HTMLOnFoundText(Text: string);
+var
+  lNewFontName: string;
+  lDescender: TFPReportUnits;
+  lHasURL: boolean;
+begin
+  lHasURL := FLastURL <> '';
+
+  FCurTextBlock := CreateTextBlock(lHasURL);
+  if lHasURL then
+  begin
+    TFPHTTPTextBlock(FCurTextBlock).URL := FLastURL;
+    FCurTextBlock.FGColor := LinkColor;
+  end;
+
+  try
+    FCurTextBlock.Text := Text;
+
+    if FLastFGColor <> clNone then
+      FCurTextBlock.FGColor := FLastFGColor;
+    if FLastBGColor <> clNone then
+      FCurTextBlock.BGColor := FLastBGColor;
+
+    lNewFontName := Font.Name;
+    if [htBold, htItalic] <= FTextBlockState then // test if it is a sub-set of FTextBlockState
+      lNewFontName := lNewFontName + '-BoldItalic'
+    else if htBold in FTextBlockState then
+      lNewFontName := lNewFontName + '-Bold'
+    else if htItalic in FTextBlockState then
+      lNewFontName := lNewFontName + '-Italic';
+    FCurTextBlock.FontName := lNewFontName;
+
+    FCurTextBlock.Width := TextWidth(FCurTextBlock.Text);
+    FCurTextBlock.Height := TextHeight(FCurTextBlock.Text, lDescender);
+    FCurTextBlock.Descender := lDescender;
+
+    // get X offset from previous textblocks
+    FCurTextBlock.Pos.Left := FTextBlockXOffset;
+    FCurTextBlock.Pos.Top := FTextBlockYOffset;
+    FTextBlockXOffset := FTextBlockXOffset + FCurTextBlock.Width;
+  except
+    on E: EReportFontNotFound do
+    begin
+      FCurTextBlock.Free;
+      raise;
+    end;
+  end;
+  FTextBlockList.Add(FCurTextBlock);
+end;
+
+function TFPReportCustomMemo.PixelsToMM(APixels: single): single;
+begin
+  Result := (APixels * cMMperInch) / gTTFontCache.DPI;
+end;
+
+function TFPReportCustomMemo.mmToPixels(mm: single): integer;
+begin
+  Result := Round(mm * (gTTFontCache.DPI / cMMperInch));
+end;
+
+function TFPReportCustomMemo.TextHeight(const AText: string; out ADescender: TFPReportUnits): TFPReportUnits;
+var
+  lHeight: single;
+  lDescenderHeight: single;
+  lFC: TFPFontCacheItem;
+begin
+  // TODO: FontName might need to change to TextBlock.FontName.
+  lFC := gTTFontCache.Find(Font.Name); // we are doing a PostScript Name lookup (it contains Bold, Italic info)
+  if not Assigned(lFC) then
+    raise EReportFontNotFound.CreateFmt(SErrFontNotFound, [Font.Name]);
+  { Both lHeight and lDescenderHeight are in pixels }
+  lHeight := lFC.TextHeight(AText, Font.Size, lDescenderHeight);
+
+  { convert pixels to mm. }
+  ADescender := PixelsToMM(lDescenderHeight);
+  Result := PixelsToMM(lHeight);
+end;
+
+function TFPReportCustomMemo.TextWidth(const AText: string): TFPReportUnits;
+var
+  lWidth: single;
+  lFC: TFPFontCacheItem;
+begin
+  // TODO: FontName might need to change to TextBlock.FontName.
+  lFC := gTTFontCache.Find(Font.Name); // we are doing a PostScript Name lookup (it contains Bold, Italic info)
+  if not Assigned(lFC) then
+    raise EReportFontNotFound.CreateFmt(SErrFontNotFound, [Font.Name]);
+  { result is in pixels }
+  lWidth := lFC.TextWidth(AText, Font.Size);
+
+  { convert pixels to mm. }
+  Result := PixelsToMM(lWidth);
+end;
+
+procedure TFPReportCustomMemo.SetLinkColor(AValue: TFPReportColor);
+begin
+  if FLinkColor = AValue then
+    Exit;
+  FLinkColor := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomMemo.SetTextAlignment(AValue: TFPReportTextAlignment);
+begin
+  if FTextAlignment = AValue then
+    Exit;
+  BeginUpdate;
+  try
+    FTextAlignment.Assign(AValue);
+  finally
+    EndUpdate;
+  end;
+end;
+
+procedure TFPReportCustomMemo.SetOptions(const AValue: TFPReportMemoOptions);
+begin
+  if FOptions = AValue then
+    Exit;
+  FOptions := AValue;
+  { If Options conflicts with StretchMode, then remove moDisableWordWrap from Options. }
+  if StretchMode <> smDontStretch then
+    Exclude(FOptions, moDisableWordWrap);
+  Changed;
+end;
+
+function TFPReportCustomMemo.SubStr(const ASource, AStartDelim, AEndDelim: string; AIndex: integer; out
+  AStartPos: integer): string;
+var
+  liStart : integer;
+  liEnd  : integer;
+  i: integer;
+begin
+  liStart := 0;
+  if AIndex < 1 then
+    AIndex := 1;
+  for i := 1 to AIndex do
+    liStart := PosEx(AStartDelim, ASource, liStart+1);
+
+  result := '';
+  AStartPos := -1;
+
+  if liStart <> 0 then
+    liStart := liStart + Length(AStartDelim);
+
+  liEnd := PosEx(AEndDelim, ASource, liStart);
+  if liEnd <> 0 then
+    liEnd := liEnd - 1;
+
+  if (liStart = 0) or (liEnd = 0) then
+    Exit; //==>
+
+  result := Copy(ASource, liStart, liEnd - liStart + 1);
+  AStartPos := liStart;
+end;
+
+procedure TFPReportCustomMemo.ParseText;
+var
+  lCount: integer;
+  n: TFPExprNode;
+  i: integer;
+  str: string;
+  lStartPos: integer;
+begin
+  { clear array and then set the correct array size }
+  ClearExpressionNodes;
+  if Pos('[', Text) > 0 then
+    lCount := TokenCount(Text)-1
+  else
+    exit;
+
+  SetLength(ExpressionNodes, lCount);
+
+  str := '';
+  n := nil;
+  for i := 1 to lCount do
+  begin
+    str := SubStr(Text, '[', ']', i, lStartPos);
+    if str <> '' then
+    begin
+      GetExpr.Expression := str;
+      GetExpr.ExtractNode(n);
+      if n.HasAggregate then
+        n.InitAggregate;
+      ExpressionNodes[i-1].Position := lStartPos;
+      ExpressionNodes[i-1].ExprNode := n;
+    end;
+  end;
+end;
+
+procedure TFPReportCustomMemo.ClearExpressionNodes;
+var
+  i: integer;
+begin
+  for i := 0 to Length(ExpressionNodes)-1 do
+    ExpressionNodes[i].ExprNode.Free;
+  SetLength(ExpressionNodes, 0);
+end;
+
+procedure TFPReportCustomMemo.AddSingleTextBlock(const AText: string);
+var
+  lDescender: TFPReportUnits;
+begin
+  if AText = '' then
+    Exit;  //==>
+  FCurTextBlock := CreateTextBlock(false);
+  try
+    FCurTextBlock.Text := AText;
+    FCurTextBlock.FontName := Font.Name;
+    FCurTextBlock.Width := TextWidth(FCurTextBlock.Text);
+    FCurTextBlock.Height := TextHeight(FCurTextBlock.Text, lDescender);
+    FCurTextBlock.Descender := lDescender;
+
+    // get X offset from previous textblocks
+    FCurTextBlock.Pos.Left := FTextBlockXOffset;
+    FCurTextBlock.Pos.Top := FTextBlockYOffset;
+    FTextBlockXOffset := FTextBlockXOffset + FCurTextBlock.Width;
+  except
+    on E: EReportFontNotFound do
+    begin
+      FCurTextBlock.Free;
+      raise;
+    end;
+  end;
+  FTextBlockList.Add(FCurTextBlock);
+end;
+
+procedure TFPReportCustomMemo.AddMultipleTextBlocks(const AText: string);
+var
+  lCount: integer;
+  i: integer;
+begin
+  lCount := TokenCount(AText, ' ');
+  for i := 1 to lCount do
+    AddSingleTextBlock(Token(AText, ' ', i));
+end;
+
+function TFPReportCustomMemo.TokenCount(const AValue: string; const AToken: string): integer;
+var
+  i, iCount : integer;
+  lsValue : string;
+begin
+  Result := 0;
+  if AValue = '' then
+    Exit; //==>
+
+  iCount := 0;
+  lsValue := AValue;
+  i := pos(AToken, lsValue);
+  while i <> 0 do
+  begin
+    delete(lsValue, i, length(AToken));
+    inc(iCount);
+    i := pos(AToken, lsValue);
+  end;
+  Result := iCount+1;
+end;
+
+function TFPReportCustomMemo.Token(const AValue, AToken: string; const APos: integer): string;
+var
+  i, iCount, iNumToken: integer;
+  lsValue: string;
+begin
+  result := '';
+
+  iNumToken := TokenCount(AValue, AToken);
+  if APos = 1 then
+  begin
+    if pos(AToken, AValue) = 0 then
+      result := AValue
+    else
+      result := copy(AValue, 1, pos(AToken, AValue)-1);
+  end
+  else if (iNumToken < APos-1) or (APos<1) then
+  begin
+    result := '';
+  end
+  else
+  begin
+    { Remove leading blocks }
+    iCount := 1;
+    lsValue := AValue;
+    i := pos(AToken, lsValue);
+    while (i<>0) and (iCount<APos) do
+    begin
+      delete(lsValue, 1, i + length(AToken) - 1);
+      inc(iCount);
+      i := pos(AToken, lsValue);
+    end;
+
+    if (i=0) and (iCount=APos) then
+      result := lsValue
+    else if (i=0) and (iCount<>APos) then
+      result := ''
+    else
+      result := copy(lsValue, 1, i-1);
+  end;
+end;
+
+function TFPReportCustomMemo.IsExprAtArrayPos(const APos: integer): Boolean;
+var
+  i: integer;
+begin
+  Result := False;
+  for i := 0 to Length(Original.ExpressionNodes)-1 do
+  begin
+    if Original.ExpressionNodes[i].Position = APos then
+    begin
+      if Original.ExpressionNodes[i].ExprNode <> nil then
+      begin
+        Result := True;
+        Break;
+      end;
+    end;
+  end;
+end;
+
+procedure TFPReportCustomMemo.SetFont(const AValue: TFPReportFont);
+begin
+  if UseParentFont then
+    UseParentFont := False;
+  FFont.Assign(AValue);
+  Changed;
+end;
+
+function TFPReportCustomMemo.CreateTextAlignment: TFPReportTextAlignment;
+begin
+  Result := TFPReportTextAlignment.Create(self);
+end;
+
+function TFPReportCustomMemo.GetExpr: TFPExpressionParser;
+begin
+  Result := TFPReportCustomBand(Parent).Page.Report.FExpr;
+end;
+
+function TFPReportCustomMemo.CreateTextBlock(const IsURL: boolean): TFPTextBlock;
+begin
+  if IsURL then
+    result := TFPHTTPTextBlock.Create
+  else
+    result := TFPTextBlock.Create;
+  result.FontName := Font.Name;
+  result.FGColor := Font.Color;
+  result.BGColor := clNone;
+end;
+
+function TFPReportCustomMemo.HtmlColorToFPReportColor(AColorStr: string; ADefault: TFPReportColor): TFPReportColor;
+var
+  N1, N2, N3: integer;
+  i: integer;
+  Len: integer;
+
+  function IsCharWord(ch: char): boolean;
+  begin
+    Result := ch in ['a'..'z', 'A'..'Z', '_', '0'..'9'];
+  end;
+
+  function IsCharHex(ch: char): boolean;
+  begin
+    Result := ch in ['0'..'9', 'a'..'f', 'A'..'F'];
+  end;
+
+begin
+  Result := ADefault;
+  Len := 0;
+  if (AColorStr <> '') and (AColorStr[1] = '#') then
+    Delete(AColorStr, 1, 1);
+  if (AColorStr = '') then
+    exit;
+
+  //delete after first nonword char
+  i := 1;
+  while (i <= Length(AColorStr)) and IsCharWord(AColorStr[i]) do
+    Inc(i);
+  Delete(AColorStr, i, Maxint);
+
+  //allow only #rgb, #rrggbb
+  Len := Length(AColorStr);
+  if (Len <> 3) and (Len <> 6) then
+    Exit;
+
+  for i := 1 to Len do
+    if not IsCharHex(AColorStr[i]) then
+      Exit;
+
+  if Len = 6 then
+  begin
+    N1 := StrToInt('$'+Copy(AColorStr, 1, 2));
+    N2 := StrToInt('$'+Copy(AColorStr, 3, 2));
+    N3 := StrToInt('$'+Copy(AColorStr, 5, 2));
+  end
+  else
+  begin
+    N1 := StrToInt('$'+AColorStr[1]+AColorStr[1]);
+    N2 := StrToInt('$'+AColorStr[2]+AColorStr[2]);
+    N3 := StrToInt('$'+AColorStr[3]+AColorStr[3]);
+  end;
+
+  Result := RGBToReportColor(N1, N2, N3);
+end;
+
+procedure TFPReportCustomMemo.PrepareObject;
+var
+  lBand: TFPReportCustomBand;
+  lMemo: TFPReportCustomMemo;
+begin
+  if not self.Visible then
+    Exit;
+  if Parent is TFPReportCustomBand then
+  begin
+    // find reference to the band we are layouting
+    lBand := TFPReportCustomBand(Parent).Page.Report.FRTCurBand;
+    lMemo := TFPReportCustomMemo.Create(lBand);
+    lMemo.Assign(self);
+    lMemo.CreateRTLayout;
+  end;
+  //inherited PrepareObject;
+end;
+
+procedure TFPReportCustomMemo.RecalcLayout;
+var
+  h: TFPReportUnits;
+begin
+  FTextBlockList.Clear;
+  FCurTextBlock := nil;
+  if not Assigned(FTextLines) then
+    FTextLines := TStringList.Create
+  else
+    FTextLines.Clear;
+
+  if not (moDisableWordWrap in Options) then
+    WrapText(Text, FTextLines, Layout.Width, h)
+  else
+    FTextLines.Add(Text);
+
+  if StretchMode <> smDontStretch then
+    ApplyStretchMode(h);
+
+  PrepareTextBlocks;
+  ApplyVertTextAlignment;
+  ApplyHorzTextAlignment;
+end;
+
+procedure TFPReportCustomMemo.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteString('Text', Text);
+
+  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;
+
+  AWriter.WriteFloat('LineSpacing', LineSpacing);
+  AWriter.WriteInteger('LinkColor', LinkColor);
+  AWriter.WriteString('Options', MemoOptionsToString(Options));
+end;
+
+procedure TFPReportCustomMemo.ExpandExpressions;
+var
+  lCount: integer;
+  str: string;
+  n: TFPExprNode;
+  i: integer;
+  lStartPos: integer;
+  lResult: string;
+  s: string;
+begin
+  lCount := TokenCount(Text);
+  if lCount = 0 then
+    Exit;
+  lResult := Text;
+  str := '';
+  n := nil;
+  for i := 0 to lCount-1 do
+  begin
+    str := SubStr(Text, '[', ']', i+1, lStartPos);
+    if str <> '' then
+    begin
+      if IsExprAtArrayPos(lStartPos) then
+      begin
+        n := Original.ExpressionNodes[i].ExprNode;
+        case n.NodeValue.ResultType of
+          rtString  : s := n.NodeValue.ResString;
+          rtInteger : s := IntToStr(n.NodeValue.ResInteger);
+          rtFloat   : s := FloatToStr(n.NodeValue.ResFloat);
+          rtBoolean : s := BoolToStr(n.NodeValue.ResBoolean, True);
+          rtDateTime : s := FormatDateTime('yyyy-mm-dd', n.NodeValue.ResDateTime);
+        end;
+        lResult := StringReplace(lResult, '[' + str + ']', s, [rfReplaceAll]);
+      end;
+    end;
+  end;
+  Text := lResult;
+end;
+
+constructor TFPReportCustomMemo.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FIsExpr := False;
+  FLinkColor := clBlue;
+  FTextAlignment := CreateTextAlignment;
+  FTextLines := TStringList.Create;
+  FLineSpacing := 1; // millimeters
+  FTextBlockList := TFPTextBlockList.Create;
+  FOptions := [];
+  FOriginal := nil;
+  FUseParentFont := True;
+  FFont := nil
+end;
+
+destructor TFPReportCustomMemo.Destroy;
+begin
+  FreeAndNil(FTextLines);
+  FreeAndNil(FTextBlockList);
+  FreeAndNil(FTextAlignment);
+  ClearExpressionNodes;
+  FreeAndNil(FFont);
+  inherited Destroy;
+end;
+
+procedure TFPReportCustomMemo.Assign(Source: TPersistent);
+var
+  E: TFPReportCustomMemo;
+begin
+  inherited Assign(Source);
+  if (Source is TFPReportCustomMemo) then
+  begin
+    E := Source as TFPReportCustomMemo;
+    Text := E.Text;
+    UseParentFont := E.UseParentFont;
+    if not UseParentFont then
+      Font.Assign(E.Font);
+    LineSpacing := E.LineSpacing;
+    LinkColor := E.LinkColor;
+    TextAlignment.Assign(E.TextAlignment);
+    Options := E.Options;
+    Original := E;
+  end;
+end;
+
+procedure TFPReportCustomMemo.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+begin
+  inherited ReadElement(AReader);
+  E := AReader.FindChild('TextAlignment');
+  if Assigned(E) then
+  begin
+    AReader.PushElement(E);
+    try
+      FTextAlignment.ReadElement(AReader);
+    finally
+      AReader.PopElement;
+    end;
+  end;
+  FText := AReader.ReadString('Text', '');
+  FUseParentFont := AReader.ReadBoolean('UseParentFont', UseParentFont);
+  if not FUseParentFont then
+  begin
+    Font.Name := AReader.ReadString('FontName', Font.Name);
+    Font.Size := AReader.ReadInteger('FontSize', Font.Size);
+    Font.Color := AReader.ReadInteger('FontColor', Font.Color);
+  end;
+  FLineSpacing := AReader.ReadFloat('LineSpacing', LineSpacing);
+  FLinkColor := AReader.ReadInteger('LinkColor', LinkColor);
+  Options := StringToMemoOptions(AReader.ReadString('Options', ''));
+  Changed;
+end;
+
+procedure TFPReportCustomMemo.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+var
+  T: TFPReportTextAlignment;
+begin
+  AWriter.PushElement('Memo');
+  try
+    inherited WriteElement(AWriter, AOriginal);
+    if (AOriginal <> nil) then
+      T := TFPReportCustomMemo(AOriginal).TextAlignment
+    else
+      T := nil;
+    AWriter.PushElement('TextAlignment');
+    try
+      FTextAlignment.WriteElement(AWriter, T);
+    finally
+      AWriter.PopElement;
+    end;
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
+{ TFPReportCustomShape }
+
+procedure TFPReportCustomShape.SetShapeType(AValue: TFPReportShapeType);
+begin
+  if FShapeType = AValue then
+    Exit;
+  FShapeType := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomShape.SetOrientation(AValue: TFPReportOrientation);
+begin
+  if FOrientation = AValue then
+    Exit;
+  FOrientation := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomShape.SetCornerRadius(AValue: TFPReportUnits);
+begin
+  if FCornerRadius = AValue then
+    Exit;
+  FCornerRadius := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomShape.PrepareObject;
+var
+  lBand: TFPReportCustomBand;
+  lShape: TFPReportCustomShape;
+begin
+  if not self.Visible then
+    Exit;
+  if Parent is TFPReportCustomBand then
+    begin
+    // find reference to the band we are layouting
+    lBand := TFPReportCustomBand(Parent).Page.Report.FRTCurBand;
+    lShape := TFPReportCustomShape.Create(lBand);
+    lShape.Parent := lBand;
+    lShape.Assign(self);
+    lShape.CreateRTLayout;
+    end;
+  //inherited PrepareObject;
+end;
+
+procedure TFPReportCustomShape.RecalcLayout;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportCustomShape.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteString('ShapeType', ShapeTypeToString(ShapeType));
+  AWriter.WriteString('Orientation', OrientationToString(Orientation));
+  AWriter.WriteFloat('CornerRadius', CornerRadius);
+  AWriter.WriteInteger('Color', Color);
+end;
+
+constructor TFPReportCustomShape.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FOrientation := orNorth;
+  FCornerRadius := 5.0;
+  FShapeType := stEllipse;
+  FColor:=clBlack;
+end;
+
+procedure TFPReportCustomShape.Assign(Source: TPersistent);
+
+Var
+  S :  TFPReportCustomShape;
+
+begin
+  inherited Assign(Source);
+  if Source is TFPReportCustomShape then
+    begin
+    S:=Source as TFPReportCustomShape;
+    FOrientation:=S.Orientation;
+    FCornerRadius:=S.CornerRadius;
+    FShapeType:=S.ShapeType;
+    FColor:=S.Color;
+    end;
+end;
+
+function TFPReportCustomShape.CreatePropertyHash: String;
+begin
+  Result:=inherited CreatePropertyHash;
+  Result:=Result+'-'+IntToStr(Ord(ShapeType))
+                +'-'+IntToStr(Ord(Orientation))
+                +'-'+IntToStr(Color)
+                +'-'+FormatFloat('000.###',CornerRadius);
+end;
+
+procedure TFPReportCustomShape.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  AWriter.PushElement('Shape');
+  try
+    inherited WriteElement(AWriter, AOriginal);
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
+{ TFPReportCustomImage }
+
+procedure TFPReportCustomImage.SetImage(AValue: TFPCustomImage);
+begin
+  if FImage = AValue then
+    Exit;
+  if Assigned(FImage) then
+    FImage.Free;
+  FImage := AValue;
+  if Assigned(FImage) then
+    FImageID := -1; { we are not using the global Report.Images here }
+  Changed;
+end;
+
+procedure TFPReportCustomImage.SetStretched(AValue: boolean);
+begin
+  if FStretched = AValue then
+    Exit;
+  FStretched := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomImage.SetFieldName(AValue: TFPReportString);
+begin
+  if FFieldName = AValue then
+    Exit;
+  FFieldName := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomImage.LoadDBData(AData: TFPReportData);
+var
+  s: string;
+  lStream: TMemoryStream;
+begin
+  s := AData.FieldValues[FFieldName];
+  lStream := TMemoryStream.Create;
+  try
+    FPReportMIMEEncodeStringToStream(s, lStream);
+    LoadPNGFromStream(lStream)
+  finally
+    lStream.Free;
+  end;
+end;
+
+procedure TFPReportCustomImage.SetImageID(AValue: integer);
+begin
+  if FImageID = AValue then
+    Exit;
+  FImageID := AValue;
+  Changed;
+end;
+
+function TFPReportCustomImage.GetImage: TFPCustomImage;
+var
+  c: integer;
+  i: integer;
+  img: TFPReportImageItem;
+begin
+  Result := nil;
+  if ImageID = -1 then { images comes from report data }
+    result := FImage
+  else
+  begin { image comes from global report.images list }
+    c := Report.Images.Count-1;
+    for i := 0 to c do
+    begin
+      img := Report.Images[i];
+      if ImageID = img.ID then
+      begin
+         Result := TFPCustomImage(img.FImage);
+         Exit;
+      end;
+    end; { for i }
+  end;
+end;
+
+procedure TFPReportCustomImage.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+var
+  idx: integer;
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  { Even though we work with CollectionItem ID values, write the CollectionItem
+    Index value instead. Why? Because when we read the report back, the Index
+    and ID values will match. }
+  idx := TFPReportCustomBand(Parent).Page.Report.Images.GetIndexFromID(ImageID);
+  AWriter.WriteInteger('ImageIndex', idx);
+  AWriter.WriteBoolean('Stretched', Stretched);
+  AWriter.WriteString('FieldName', FieldName);
+end;
+
+procedure TFPReportCustomImage.PrepareObject;
+var
+  lBand: TFPReportCustomBand;
+  lImage: TFPReportCustomImage;
+begin
+  if not self.Visible then
+    Exit;
+  if Parent is TFPReportCustomBand then
+  begin
+    // find reference to the band we are layouting
+    lBand := TFPReportCustomBand(Parent).Page.Report.FRTCurBand;
+    lImage := TFPReportCustomImage.Create(lBand);
+    lImage.Parent := lBand;
+    lImage.Assign(self);
+    lImage.CreateRTLayout;
+  end;
+end;
+
+procedure TFPReportCustomImage.RecalcLayout;
+begin
+  // Do nothing
+end;
+
+constructor TFPReportCustomImage.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FImage := nil;
+  FStretched := False;
+  FImageID := -1;
+end;
+
+destructor TFPReportCustomImage.Destroy;
+begin
+  FImage.Free;
+  inherited Destroy;
+end;
+
+function TFPReportCustomImage.GetRTImageID: Integer;
+begin
+  Result:=ImageID;
+end;
+
+function TFPReportCustomImage.GetRTImage: TFPCustomImage;
+begin
+  Result:=Image;
+end;
+
+procedure TFPReportCustomImage.Assign(Source: TPersistent);
+var
+  i: TFPReportCustomImage;
+begin
+  inherited Assign(Source);
+  if Source is TFPReportCustomImage then
+  begin
+    i := (Source as TFPReportCustomImage);
+    if Assigned(i.Image) then
+    begin
+      if not Assigned(FImage) then
+        FImage := TFPCompactImgRGBA8Bit.Create(0, 0);
+      FImage.Assign(i.Image);
+    end;
+    FStretched := i.Stretched;
+    FFieldName := i.FieldName;
+    FImageID := i.ImageID;
+  end;
+end;
+
+procedure TFPReportCustomImage.ReadElement(AReader: TFPReportStreamer);
+begin
+  inherited ReadElement(AReader);
+  { See code comments in DoWriteLocalProperties() }
+  ImageID := AReader.ReadInteger('ImageIndex', -1);
+  Stretched := AReader.ReadBoolean('Stretched', Stretched);
+  FieldName := AReader.ReadString('FieldName', FieldName);
+end;
+
+procedure TFPReportCustomImage.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  AWriter.PushElement('Image');
+  try
+    inherited WriteElement(AWriter, AOriginal);
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
+procedure TFPReportCustomImage.LoadFromFile(const AFileName: string);
+
+var
+  R : TFPCustomReport;
+  i : integer;
+begin
+  R:=Report;
+  I:=R.Images.AddFromFile(AFileName,True);
+  ImageID:=R.Images[I].ID;
+end;
+
+procedure TFPReportCustomImage.LoadPNGFromStream(AStream: TStream);
+
+var
+  R : TFPCustomReport;
+  i : integer;
+
+begin
+  R:=Report;
+  I:=R.Images.AddFromStream(AStream,TFPReaderPNG,true);
+  ImageID:=R.Images[I].ID;
+end;
+
+procedure TFPReportCustomImage.LoadImage(const AImageData: Pointer; const AImageDataSize: LongWord);
+
+var
+  s: TMemoryStream;
+
+begin
+  s := TMemoryStream.Create;
+  try
+    s.Write(AImageData^, AImageDataSize);
+    s.Position := 0;
+    LoadPNGFromStream(s);
+  finally
+    s.Free;
+  end;
+end;
+
+{ TFPReportCustomCheckbox }
+
+procedure TFPReportCustomCheckbox.SetExpression(AValue: TFPReportString);
+begin
+  if FExpression = AValue then
+    Exit;
+  FExpression := AValue;
+  Changed;
+end;
+
+function TFPReportCustomCheckbox.LoadImage(const AImageData: Pointer; const AImageDataSize: LongWord): TFPCustomImage;
+var
+  s: TMemoryStream;
+begin
+  s := TMemoryStream.Create;
+  try
+    s.Write(AImageData^, AImageDataSize);
+    Result := LoadImage(s);
+  finally
+    s.Free;
+  end;
+end;
+
+function TFPReportCustomCheckbox.LoadImage(AStream: TStream): TFPCustomImage;
+var
+  img: TFPCompactImgRGBA8Bit;
+  reader: TFPReaderPNG;
+begin
+  Result := nil;
+  if AStream = nil then
+    Exit;
+
+  img := TFPCompactImgRGBA8Bit.Create(0, 0);
+  try
+    try
+      AStream.Position := 0;
+      reader := TFPReaderPNG.Create;
+      img.LoadFromStream(AStream, reader); // auto sizes image
+      Result := img;
+    except
+      on e: Exception do
+      begin
+        Result := nil;
+      end;
+    end;
+  finally
+    reader.Free;
+  end;
+end;
+
+procedure TFPReportCustomCheckbox.RecalcLayout;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportCustomCheckbox.PrepareObject;
+var
+  lBand: TFPReportCustomBand;
+  lCB: TFPReportCustomCheckbox;
+begin
+  if not self.Visible then
+    Exit;
+  if Parent is TFPReportCustomBand then
+  begin
+    // find reference to the band we are layouting
+    lBand := TFPReportCustomBand(Parent).Page.Report.FRTCurBand;
+    lCB := TFPReportCustomCheckbox.Create(lBand);
+    lCB.Parent := lBand;
+    lCB.Assign(self);
+    lCB.CreateRTLayout;
+  end;
+end;
+
+constructor TFPReportCustomCheckbox.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  { 3x3 millimeters }
+  Layout.Width := 3;
+  Layout.Height := 3;
+  FTrueImageID:=-1;
+  FFalseImageID:=-1;
+end;
+
+destructor TFPReportCustomCheckbox.Destroy;
+begin
+  inherited Destroy;
+end;
+
+function TFPReportCustomCheckbox.GetImage(Checked: Boolean): TFPCustomImage;
+
+begin
+  Result:=nil;
+  if Checked then
+    Result:=Report.Images.GetImageFromID(TrueImageID)
+  else
+    Result:=Report.Images.GetImageFromID(FalseImageID);
+  if (Result=Nil) then
+    Result:=GetDefaultImage(Checked);
+end;
+
+function TFPReportCustomCheckbox.GetRTResult: Boolean;
+begin
+  Result:=FTestResult;
+end;
+
+function TFPReportCustomCheckbox.GetRTImage: TFPCustomImage;
+begin
+  Result:=GetImage(GetRTResult);
+end;
+
+function TFPReportCustomCheckbox.GetDefaultImage(Checked: Boolean): TFPCustomImage;
+
+begin
+  if Checked then
+    begin
+    if (ImgTrue=Nil) then
+      ImgTrue:=LoadImage(@fpreport_checkbox_true, SizeOf(fpreport_checkbox_true));
+    Result:=ImgTrue;
+    end
+  else
+    begin
+    if (ImgFalse=Nil) then
+      ImgFalse:=LoadImage(@fpreport_checkbox_false, SizeOf(fpreport_checkbox_false));
+    Result:=ImgFalse;
+    end;
+end;
+
+function TFPReportCustomCheckbox.CreatePropertyHash: String;
+begin
+  Result:=inherited CreatePropertyHash;
+  Result:=Result+IntToStr(TrueImageID)+IntToStr(FalseImageID);
+  Result:=Result+IntToStr(Ord(GetRTResult));
+end;
+
+procedure TFPReportCustomCheckbox.Assign(Source: TPersistent);
+var
+  cb: TFPReportCustomCheckbox;
+begin
+  inherited Assign(Source);
+  if Source is TFPReportCustomCheckbox then
+  begin
+    cb := (Source as TFPReportCustomCheckbox);
+    FTrueImageID:=cb.FTrueImageID;
+    FFalseImageID:=cb.FFalseImageID;
+    FExpression := cb.Expression;
+  end;
+end;
+
+procedure TFPReportCustomCheckbox.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  AWriter.PushElement('Checkbox');
+  try
+    inherited WriteElement(AWriter, AOriginal);
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
+
+{ TFPReportUserData }
+
+procedure TFPReportUserData.DoGetValue(const AFieldName: string; var AValue: variant);
+begin
+  inherited DoGetValue(AFieldName, AValue);
+  if Assigned(FOnGetValue) then
+    FOnGetValue(Self, AFieldName, AValue);
+end;
+
+procedure TFPReportUserData.DoInitDataFields;
+var
+  sl: TStringList;
+  i: integer;
+begin
+  inherited DoInitDataFields;
+
+  if Assigned(FOnGetNames) then
+  begin
+    sl := TStringList.Create;
+    FOnGetNames(self, sl);
+    for i := 0 to sl.Count-1 do
+      if (Datafields.IndexOfField(sl[i])=-1) then
+        DataFields.AddField(sl[i], rfkString);
+    sl.Free;
+  end;
+end;
+
+
+{ TFPReportCustomDataBand }
+
+constructor TFPReportCustomDataBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDisplayPosition := 0;
+end;
+
+{ TFPReportDataBand }
+
+function TFPReportDataBand.GetReportBandName: string;
+begin
+  Result := 'DataBand';
+end;
+
+class function TFPReportDataBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btDataband;
+end;
+
+{ TFPReportCustomChildBand }
+
+function TFPReportCustomChildBand.GetReportBandName: string;
+begin
+  Result := 'ChildBand';
+end;
+
+class function TFPReportCustomChildBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btChild;
+end;
+
+{ TFPReportCustomPageFooterBand }
+
+function TFPReportCustomPageFooterBand.GetReportBandName: string;
+begin
+  Result := 'PageFooterBand';
+end;
+
+class function TFPReportCustomPageFooterBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btPageFooter;
+end;
+
+{ TFPReportCustomPageHeaderBand }
+
+function TFPReportCustomPageHeaderBand.GetReportBandName: string;
+begin
+  Result := 'PageHeaderBand';
+end;
+
+class function TFPReportCustomPageHeaderBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btPageHeader;
+end;
+
+{ TFPReportCustomColumnHeaderBand }
+
+function TFPReportCustomColumnHeaderBand.GetReportBandName: string;
+begin
+  Result := 'ColumnHeaderBand';
+end;
+
+class function TFPReportCustomColumnHeaderBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btColumnHeader;
+end;
+
+{ TFPReportCustomColumnFooterBand }
+
+procedure TFPReportCustomColumnFooterBand.SetFooterPosition(AValue: TFPReportFooterPosition);
+begin
+  if FFooterPosition = AValue then
+    Exit;
+  FFooterPosition := AValue;
+end;
+
+procedure TFPReportCustomColumnFooterBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteString('FooterPosition', ColumnFooterPositionToString(FFooterPosition));
+end;
+
+function TFPReportCustomColumnFooterBand.GetReportBandName: string;
+begin
+  Result := 'ColumnFooterBand';
+end;
+
+constructor TFPReportCustomColumnFooterBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FFooterPosition := fpColumnBottom;
+end;
+
+procedure TFPReportCustomColumnFooterBand.Assign(Source: TPersistent);
+begin
+  inherited Assign(Source);
+  FFooterPosition := TFPReportCustomColumnFooterBand(Source).FooterPosition;
+end;
+
+class function TFPReportCustomColumnFooterBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btColumnFooter;
+end;
+
+procedure TFPReportCustomColumnFooterBand.ReadElement(AReader: TFPReportStreamer);
+begin
+  inherited ReadElement(AReader);
+  FFooterPosition := StringToColumnFooterPosition(AReader.ReadString('FooterPosition', 'fpColumnBottom'));
+end;
+
+{ TFPReportCustomGroupHeaderBand }
+
+procedure TFPReportCustomGroupHeaderBand.SetGroupHeader(AValue: TFPReportCustomGroupHeaderBand);
+begin
+  if FGroupHeader = AValue then
+    Exit;
+  if Assigned(FGroupHeader) then
+  begin
+    FGroupHeader.FChildGroupHeader := nil;
+    FGroupHeader.RemoveFreeNotification(Self);
+  end;
+  FGroupHeader := AValue;
+  if Assigned(FGroupHeader) then
+  begin
+    FGroupHeader.FChildGroupHeader := Self;
+    FGroupHeader.FreeNotification(Self);
+  end;
+end;
+
+function TFPReportCustomGroupHeaderBand.GetReportBandName: string;
+begin
+  Result := 'GroupHeaderBand';
+end;
+
+procedure TFPReportCustomGroupHeaderBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteString('GroupCondition', FCondition);
+end;
+
+procedure TFPReportCustomGroupHeaderBand.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  if (Operation = opRemove) and (AComponent = FChildGroupHeader) then
+    FChildGroupHeader := nil;
+  inherited Notification(AComponent, Operation);
+end;
+
+constructor TFPReportCustomGroupHeaderBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FGroupHeader := nil;
+  FChildGroupHeader := nil;
+  FGroupFooter := nil;
+end;
+
+procedure TFPReportCustomGroupHeaderBand.Assign(Source: TPersistent);
+begin
+  inherited Assign(Source);
+  FCondition := TFPReportCustomGroupHeaderBand(Source).GroupCondition;
+end;
+
+procedure TFPReportCustomGroupHeaderBand.ReadElement(AReader: TFPReportStreamer);
+begin
+  inherited ReadElement(AReader);
+  FCondition := AReader.ReadString('GroupCondition', '');
+end;
+
+function TFPReportCustomGroupHeaderBand.Evaluate: string;
+begin
+  Result := ExpandMacro(GroupCondition, True);
+end;
+
+class function TFPReportCustomGroupHeaderBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btGroupHeader;
+end;
+
+{ TFPReportCustomTitleBand }
+
+function TFPReportCustomTitleBand.GetReportBandName: string;
+begin
+  Result := 'ReportTitleBand';
+end;
+
+class function TFPReportCustomTitleBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btReportTitle;
+end;
+
+{ TFPReportCustomSummaryBand }
+
+function TFPReportCustomSummaryBand.GetReportBandName: string;
+begin
+  Result := 'ReportSummaryBand';
+end;
+
+procedure TFPReportCustomSummaryBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteBoolean('StartNewPage', StartNewPage);
+end;
+
+constructor TFPReportCustomSummaryBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FStartNewPage := False;
+end;
+
+procedure TFPReportCustomSummaryBand.Assign(Source: TPersistent);
+begin
+  inherited Assign(Source);
+  if Source is TFPReportCustomSummaryBand then
+    FStartNewPage := TFPReportCustomSummaryBand(Source).StartNewPage;
+end;
+
+procedure TFPReportCustomSummaryBand.ReadElement(AReader: TFPReportStreamer);
+begin
+  inherited ReadElement(AReader);
+  FStartNewPage := AReader.ReadBoolean('StartNewPage', False);
+end;
+
+class function TFPReportCustomSummaryBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btReportSummary;
+end;
+
+{ TFPReportComponent }
+
+procedure TFPReportComponent.StartLayout;
+begin
+  FReportState := rsLayout;
+end;
+
+procedure TFPReportComponent.EndLayout;
+begin
+  FReportState := rsDesign;
+end;
+
+procedure TFPReportComponent.StartRender;
+begin
+  FReportState := rsRender;
+end;
+
+procedure TFPReportComponent.EndRender;
+begin
+  FReportState := rsDesign;
+end;
+
+procedure TFPReportComponent.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  AWriter.WriteString('Name', Name);
+end;
+
+procedure TFPReportComponent.ReadElement(AReader: TFPReportStreamer);
+begin
+  Name := AReader.ReadString('Name', 'UnknownName');
+end;
+
+{ TFPReportRect }
+
+procedure TFPReportRect.SetRect(aleft, atop, awidth, aheight: TFPReportUnits);
+begin
+  Left   := aleft;
+  Top    := atop;
+  Width  := awidth;
+  Height := aheight;
+end;
+
+procedure TFPReportRect.OffsetRect(aLeft, ATop: TFPReportUnits);
+begin
+  Left:=Left+ALeft;
+  Top:=Top+ATop;
+end;
+
+function TFPReportRect.IsEmpty: Boolean;
+begin
+  Result:=(Width=0) and (Height=0);
+end;
+
+function TFPReportRect.Bottom: TFPReportUnits;
+begin
+  Result := Top + Height;
+end;
+
+function TFPReportRect.Right: TFPReportUnits;
+begin
+  Result := Left + Width;
+end;
+
+function TFPReportRect.AsString: String;
+begin
+  Result:=Format('(x: %5.2f, y: %5.2f, w: %5.2f, h: %5.2f)',[Left,Top,Width,Height]);
+end;
+
+{ TFPReportFrame }
+
+procedure TFPReportFrame.SetColor(const AValue: TFPReportColor);
+begin
+  if FColor = AValue then
+    Exit;
+  FColor := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.SetFrameLines(const AValue: TFPReportFrameLines);
+begin
+  if FFrameLines = AValue then
+    Exit;
+  FFrameLines := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.SetFrameShape(const AValue: TFPReportFrameShape);
+begin
+  if FFrameShape = AValue then
+    Exit;
+  FFrameShape := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.SetPenStyle(const AValue: TFPPenStyle);
+begin
+  if FPenStyle = AValue then
+    Exit;
+  FPenStyle := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.SetWidth(const AValue: integer);
+begin
+  if FWidth = AValue then
+    Exit;
+  if (AValue < 0) then
+    ReportError(SErrInvalidLineWidth, [AValue]);
+  FWidth := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.SetBackgrounColor(AValue: TFPReportColor);
+begin
+  if FBackgroundColor = AValue then
+    Exit;
+  FBackgroundColor := AValue;
+  Changed;
+end;
+
+procedure TFPReportFrame.Changed;
+begin
+  if Assigned(FReportElement) then
+    FReportElement.Changed;
+end;
+
+constructor TFPReportFrame.Create(AElement: TFPReportElement);
+begin
+  inherited Create;
+  FReportElement := AElement;
+  FPenStyle := psSolid;
+  FWidth := 1;
+  FColor := clNone;
+  FBackgroundColor := clNone;
+  FFrameShape := fsNone;
+end;
+
+procedure TFPReportFrame.Assign(ASource: TPersistent);
+var
+  F: TFPReportFrame;
+begin
+  if (ASource is TFPReportFrame) then
+  begin
+    F := (ASource as TFPReportFrame);
+    FFrameLines := F.FFrameLines;
+    FFrameShape := F.FFrameShape;
+    FColor := F.FColor;
+    FBackgroundColor := F.BackgroundColor;
+    FPenStyle := F.FPenStyle;
+    FWidth := F.FWidth;
+    Changed;
+  end
+  else
+    inherited Assign(ASource);
+end;
+
+function TFPReportFrame.Equals(AFrame: TFPReportFrame): boolean;
+begin
+  Result := (AFrame = Self) or ((Color = AFrame.Color) and (Pen = AFrame.Pen) and
+    (Width = AFrame.Width) and (Shape = AFrame.Shape) and (Lines = AFrame.Lines) and
+    (BackgroundColor = AFrame.BackgroundColor));
+end;
+
+
+procedure TFPReportFrame.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportFrame);
+var
+  I, J: integer;
+begin
+  if (AOriginal = nil) then
+  begin
+    AWriter.WriteInteger('Color', Color);
+    AWriter.WriteString('Pen', FramePenToString(Pen));
+    AWriter.WriteInteger('Width', Ord(Width));
+    AWriter.WriteString('Shape', FrameShapeToString(Shape));
+    //TODO Write out the enum values instead of the Integer value.
+    I := integer(Lines);
+    AWriter.WriteInteger('Lines', I);
+    AWriter.WriteInteger('BackgroundColor', BackgroundColor);
+  end
+  else
+  begin
+    AWriter.WriteIntegerDiff('Color', Color, AOriginal.Color);
+    AWriter.WriteStringDiff('Pen', FramePenToString(Pen), FramePenToString(AOriginal.Pen));
+    AWriter.WriteIntegerDiff('Width', Ord(Width), AOriginal.Width);
+    AWriter.WriteStringDiff('Shape', FrameShapeToString(Shape), FrameShapeToString(AOriginal.Shape));
+    I := integer(Lines);
+    J := integer(Aoriginal.Lines);
+    AWriter.WriteIntegerDiff('Lines', I, J);
+    AWriter.WriteIntegerDiff('BackgroundColor', BackgroundColor, AOriginal.BackgroundColor);
+  end;
+end;
+
+procedure TFPReportFrame.ReadElement(AReader: TFPReportStreamer);
+var
+  I: integer;
+begin
+  Color := AReader.ReadInteger('Color', Color);
+  Pen := StringToFramePen(AReader.ReadString('Pen', 'psSolid'));
+  Width := AReader.ReadInteger('Width', Ord(Width));
+  Shape := StringToFrameShape(AReader.ReadString('Shape', 'fsNone'));
+  I := integer(Lines);
+  Lines := TFPReportFrameLines(AReader.ReadInteger('Lines', I));
+  BackgroundColor := AReader.ReadInteger('BackgroundColor', BackgroundColor);
+end;
+
+{ TFPReportTextAlignment }
+
+procedure TFPReportTextAlignment.SetHorizontal(AValue: TFPReportHorzTextAlignment);
+begin
+  if FHorizontal = AValue then
+    Exit;
+  FHorizontal := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.SetVertical(AValue: TFPReportVertTextAlignment);
+begin
+  if FVertical = AValue then
+    Exit;
+  FVertical := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.SetTopMargin(AValue: TFPReportUnits);
+begin
+  if FTopMargin = AValue then
+    Exit;
+  FTopMargin := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.SetBottomMargin(AValue: TFPReportUnits);
+begin
+  if FBottomMargin = AValue then
+    Exit;
+  FBottomMargin := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.SetLeftMargin(AValue: TFPReportUnits);
+begin
+  if FLeftMargin = AValue then
+    Exit;
+  FLeftMargin := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.SetRightMargin(AValue: TFPReportUnits);
+begin
+  if FRightMargin = AValue then
+    Exit;
+  FRightMargin := AValue;
+  Changed;
+end;
+
+procedure TFPReportTextAlignment.Changed;
+begin
+  if Assigned(FReportElement) then
+    FReportElement.Changed;
+end;
+
+constructor TFPReportTextAlignment.Create(AElement: TFPReportElement);
+begin
+  inherited Create;
+  FReportElement := AElement;
+  FHorizontal := taLeftJustified;
+  FVertical := tlTop;
+  FLeftMargin := 1.0;
+  FRightMargin := 1.0;
+  FTopMargin := 0;
+  FBottomMargin := 0;
+end;
+
+procedure TFPReportTextAlignment.Assign(ASource: TPersistent);
+var
+  F: TFPReportTextAlignment;
+begin
+  if (ASource is TFPReportTextAlignment) then
+  begin
+    F := (ASource as TFPReportTextAlignment);
+    FHorizontal   := F.Horizontal;
+    FVertical     := F.Vertical;
+    FLeftMargin   := F.LeftMargin;
+    FRightMargin  := F.RightMargin;
+    FTopMargin    := F.TopMargin;
+    FBottomMargin := F.BottomMargin;
+    Changed;
+  end
+  else
+    inherited Assign(ASource);
+end;
+
+procedure TFPReportTextAlignment.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportTextAlignment);
+begin
+  if (AOriginal = nil) then
+  begin
+    AWriter.WriteString('Horizontal', HorzTextAlignmentToString(Horizontal));
+    AWriter.WriteString('Vertical', VertTextAlignmentToString(Vertical));
+    AWriter.WriteFloat('LeftMargin', LeftMargin);
+    AWriter.WriteFloat('RightMargin', RightMargin);
+    AWriter.WriteFloat('TopMargin', TopMargin);
+    AWriter.WriteFloat('BottomMargin', BottomMargin);
+  end
+  else
+  begin
+    AWriter.WriteStringDiff('Horizontal', HorzTextAlignmentToString(Horizontal), HorzTextAlignmentToString(AOriginal.Horizontal));
+    AWriter.WriteStringDiff('Vertical', VertTextAlignmentToString(Vertical), VertTextAlignmentToString(AOriginal.Vertical));
+    AWriter.WriteFloatDiff('LeftMargin', LeftMargin, AOriginal.LeftMargin);
+    AWriter.WriteFloatDiff('RightMargin', RightMargin, AOriginal.RightMargin);
+    AWriter.WriteFloatDiff('TopMargin', TopMargin, AOriginal.TopMargin);
+    AWriter.WriteFloatDiff('BottomMargin', BottomMargin, AOriginal.BottomMargin);
+  end;
+end;
+
+procedure TFPReportTextAlignment.ReadElement(AReader: TFPReportStreamer);
+begin
+  Horizontal := StringToHorzTextAlignment(AReader.ReadString('Horizontal', 'taLeftJustified'));
+  Vertical := StringToVertTextAlignment(AReader.ReadString('Vertical', 'tlTop'));
+  LeftMargin := AReader.ReadFloat('LeftMargin', LeftMargin);
+  RightMargin := AReader.ReadFloat('RightMargin', RightMargin);
+  TopMargin := AReader.ReadFloat('TopMargin', TopMargin);
+  BottomMargin := AReader.ReadFloat('BottomMargin', BottomMargin);
+end;
+
+{ TFPReportCustomLayout }
+
+function TFPReportCustomLayout.GetWidth: TFPreportUnits;
+begin
+  Result := FPos.Width;
+end;
+
+function TFPReportCustomLayout.GetHeight: TFPreportUnits;
+begin
+  Result := FPos.Height;
+end;
+
+procedure TFPReportCustomLayout.SetLeft(const AValue: TFPreportUnits);
+begin
+  if (AValue = FPos.Left) then
+    Exit;
+  FPos.Left := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomLayout.SetTop(const AValue: TFPreportUnits);
+begin
+  if (AValue = FPos.Top) then
+    Exit;
+  FPos.Top := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomLayout.SetWidth(const AValue: TFPreportUnits);
+begin
+  if (AValue = FPos.Width) then
+    Exit;
+  FPos.Width := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomLayout.SetHeight(const AValue: TFPreportUnits);
+begin
+  if (AValue = FPos.Height) then
+    Exit;
+  FPos.Height := AValue;
+  Changed;
+end;
+
+function TFPReportCustomLayout.GetLeft: TFPreportUnits;
+begin
+  Result := FPos.Left;
+end;
+
+function TFPReportCustomLayout.GetTop: TFPreportUnits;
+begin
+  Result := FPos.Top;
+end;
+
+constructor TFPReportCustomLayout.Create(AElement: TFPReportElement);
+begin
+  FReportElement := AElement;
+end;
+
+procedure TFPReportCustomLayout.Assign(Source: TPersistent);
+var
+  l: TFPReportCustomLayout;
+begin
+  if Source is TFPReportCustomLayout then
+  begin
+    l := (Source as TFPReportCustomLayout);
+    FPos.Height := l.Height;
+    FPos.Left := l.Left;
+    FPos.Top := l.Top;
+    FPos.Width := l.Width;
+  end
+  else
+    inherited Assign(Source);
+end;
+
+procedure TFPReportCustomLayout.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportCustomLayout);
+begin
+  if (AOriginal = nil) then
+  begin
+    AWriter.WriteFloat('Top', Top);
+    AWriter.WriteFloat('Left', Left);
+    AWriter.WriteFloat('Width', Width);
+    AWriter.WriteFloat('Height', Height);
+  end
+  else
+  begin
+    AWriter.WriteFloatDiff('Top', Top, AOriginal.Top);
+    AWriter.WriteFloatDiff('Left', Left, AOriginal.Left);
+    AWriter.WriteFloatDiff('Width', Width, AOriginal.Width);
+    AWriter.WriteFloatDiff('Height', Height, AOriginal.Height);
+  end;
+end;
+
+procedure TFPReportCustomLayout.ReadElement(AReader: TFPReportStreamer);
+begin
+  FPos.Top := AReader.ReadFloat('Top', Top);
+  FPos.Left := AReader.ReadFloat('Left', Left);
+  FPos.Width := AReader.ReadFloat('Width', Width);
+  FPos.Height := AReader.ReadFloat('Height', Height);
+  Changed;
+end;
+
+function TFPReportCustomLayout.Equals(ALayout: TFPReportCustomLayout): boolean;
+begin
+  Result := (ALayout = Self) or ((ALayout.FPos.Top = FPos.Top) and (ALayout.FPos.Left = FPos.Left) and
+    (ALayout.FPos.Right = FPos.Right) and (ALayout.FPos.Bottom = FPos.Bottom));
+end;
+
+procedure TFPReportCustomLayout.GetBoundsRect(out ARect: TFPReportRect);
+begin
+  ARect := FPos;
+end;
+
+procedure TFPReportCustomLayout.SetPosition(aleft, atop, awidth, aheight: TFPReportUnits);
+begin
+  FPos.SetRect(aleft,aTop,aWidth,aHeight);
+  Changed;
+end;
+
+procedure TFPReportCustomLayout.SetPosition(const ARect: TFPReportRect);
+begin
+  FPos.SetRect(ARect.Left,ARect.Top,ARect.Width,ARect.Height);
+  Changed;
+end;
+
+{ TFPReportLayout }
+
+procedure TFPReportLayout.Changed;
+begin
+  if Assigned(FReportElement) then
+    FReportElement.Changed;
+end;
+
+{ TFPReportElement }
+
+procedure TFPReportElement.SetFrame(const AValue: TFPReportFrame);
+begin
+  if FFrame = AValue then
+    Exit;
+  BeginUpdate;
+  try
+    FFrame.Assign(AValue);
+  finally
+    EndUpdate;
+  end;
+end;
+
+function TFPReportElement.GetReport: TFPCustomReport;
+
+Var
+  El : TFpReportElement;
+
+begin
+  Result:=Nil;
+  El:=Self;
+  While Assigned(El) and not El.InheritsFrom(TFPReportCustomPage) do
+    El:=El.Parent;
+  if Assigned(El) then
+    Result:=TFPReportCustomPage(el).Report;
+end;
+
+procedure TFPReportElement.SetLayout(const AValue: TFPReportLayout);
+begin
+  if FLayout = AValue then
+    Exit;
+  BeginUpdate;
+  try
+    FLayout.Assign(AValue);
+  finally
+    EndUpdate;
+  end;
+end;
+
+procedure TFPReportElement.SetParent(const AValue: TFPReportElement);
+begin
+  if FParent = AValue then
+    Exit;
+  if (AValue <> nil) and not (AValue is TFPReportElementWithChildren) then
+    ReportError(SErrInvalidParent, [AValue.Name, Self.Name]);
+  if Assigned(FParent) then
+    TFPReportElementWithChildren(FParent).RemoveChild(Self);
+  FParent := AValue;
+  if Assigned(FParent) then
+  begin
+    TFPReportElementWithChildren(FParent).AddChild(Self);
+    FParent.FreeNotification(self);
+  end;
+  Changed;
+end;
+
+procedure TFPReportElement.SetVisible(const AValue: boolean);
+begin
+  if FVisible = AValue then
+    Exit;
+  FVisible := AValue;
+  Changed;
+end;
+
+procedure TFPReportElement.SaveDataToNames;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportElement.RestoreDataFromNames;
+begin
+  // Do nothing
+end;
+
+function TFPReportElement.CreateFrame: TFPReportFrame;
+begin
+  Result := TFPReportFrame.Create(Self);
+end;
+
+function TFPReportElement.CreateLayout: TFPReportLayout;
+begin
+  Result := TFPReportLayout.Create(Self);
+end;
+
+procedure TFPReportElement.CreateRTLayout;
+begin
+  FRTLayout := TFPReportLayout.Create(self);
+  FRTLayout.Assign(FLayout);
+end;
+
+procedure TFPReportElement.Changed;
+begin
+  if (FUpdateCount = 0) then
+    DoChanged;
+end;
+
+procedure TFPReportElement.DoChanged;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportElement.PrepareObject;
+var
+  lBand: TFPReportCustomBand;
+  EL : TFPReportElement;
+
+begin
+  if not self.Visible then
+    Exit;
+  if Parent is TFPReportCustomBand then
+    begin
+    // find reference to the band we are layouting
+    lBand := TFPReportCustomBand(Parent).Page.Report.FRTCurBand;
+    EL:=TFPReportElementClass(Self.ClassType).Create(lBand);
+    EL.Assign(self);
+    el.CreateRTLayout;
+    end;
+end;
+
+procedure TFPReportElement.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  // will be implemented by descendant classes
+end;
+
+constructor TFPReportElement.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FLayout := CreateLayout;
+  FFrame := CreateFrame;
+  FVisible := True;
+  FStretchMode := smDontStretch;
+  if AOwner is TFPReportElement then
+    Parent := TFPReportElement(AOwner);
+end;
+
+destructor TFPReportElement.Destroy;
+begin
+  FreeAndNil(FLayout);
+  FreeAndNil(FFrame);
+  if Assigned(FParent) then
+    (FParent as TFPReportElementWithChildren).RemoveChild(Self);
+  FreeAndNil(FRTLayout);
+  inherited Destroy;
+end;
+
+function TFPReportElement.CreatePropertyHash: String;
+
+Var
+  L : TFPReportCustomLayout;
+
+begin
+  if Assigned(RTLayout) then
+    L:=RTLayout
+  else
+    L:=Layout;
+  if Assigned(L) then
+    Result:=Format('%6.3f%6.3f',[L.Width,L.Height]);
+end;
+
+procedure TFPReportElement.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  if Operation = opRemove then
+  begin
+    if AComponent = FParent then
+      FParent := nil;
+  end;
+  inherited Notification(AComponent, Operation);
+end;
+
+procedure TFPReportElement.BeforePrint;
+begin
+  if Assigned(FOnBeforePrint) then
+    FOnBeforePrint(self);
+end;
+
+function TFPReportElement.Equals(AElement: TFPReportElement): boolean;
+begin
+  Result := (AElement = Self) or ((AElement.ClassType = AElement.ClassType) and
+    (AElement.Frame.Equals(Self.Frame)) and (AElement.Layout.Equals(Self.Layout))
+    and (AElement.Visible = Self.Visible));
+end;
+
+procedure TFPReportElement.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+var
+  L: TFPReportCustomLayout;
+  F: TFPReportFrame;
+begin
+  inherited WriteElement(AWriter, AOriginal);
+  if (AOriginal <> nil) then
+  begin
+    L := AOriginal.Layout;
+    F := AOriginal.Frame;
+  end
+  else
+  begin
+    F := nil;
+    L := nil;
+  end;
+  // Always write Layout.
+  AWriter.PushElement('Layout');
+  try
+    FLayout.WriteElement(AWriter, L);
+  finally
+    AWriter.PopElement;
+  end;
+  // now for the Frame
+  if (not Assigned(F)) or (not F.Equals(FFrame)) then
+  begin
+    AWriter.PushElement('Frame');
+    try
+      FFrame.WriteElement(AWriter, F);
+    finally
+      AWriter.PopElement;
+    end;
+  end;
+  AWriter.WriteBoolean('Visible', FVisible);
+  AWriter.WriteString('StretchMode', StretchModeToString(StretchMode));
+  DoWriteLocalProperties(AWriter, AOriginal);
+end;
+
+procedure TFPReportElement.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+begin
+  inherited ReadElement(AReader);
+  E := AReader.FindChild('Layout');
+  if Assigned(E) then
+  begin
+    AReader.PushElement(E);
+    try
+      FLayout.ReadElement(AReader);
+    finally
+      AReader.PopElement;
+    end;
+  end;
+  E := AReader.FindChild('Frame');
+  if Assigned(E) then
+  begin
+    AReader.PushElement(E);
+    try
+      FFrame.ReadElement(AReader);
+    finally
+      AReader.PopElement;
+    end;
+  end;
+  FVisible := AReader.ReadBoolean('Visible', Visible);
+  FStretchMode := StringToStretchMode(AReader.ReadString('StretchMode', 'smDontStretch'));
+  // TODO: implement reading OnBeforePrint information
+end;
+
+procedure TFPReportElement.Assign(Source: TPersistent);
+var
+  E: TFPReportElement;
+begin
+  { Don't call inherited here. }
+//  inherited Assign(Source);
+  if (Source is TFPReportElement) then
+  begin
+    E := Source as TFPReportElement;
+    Frame.Assign(E.Frame);
+    Layout.Assign(E.Layout);
+    FParent := E.Parent;
+    Visible := E.Visible;
+    StretchMode := E.StretchMode;
+    OnBeforePrint := E.OnBeforePrint;
+  end;
+end;
+
+procedure TFPReportElement.BeginUpdate;
+begin
+  Inc(FUpdateCount);
+end;
+
+procedure TFPReportElement.EndUpdate;
+begin
+  if (FUpdateCount > 0) then
+    Dec(FUpdateCount);
+  if (FUpdateCount = 0) then
+    DoChanged;
+end;
+
+{ TFPReportElementWithChildren }
+
+function TFPReportElementWithChildren.GetChild(AIndex: integer): TFPReportElement;
+begin
+  if Assigned(FChildren) then
+    Result := TFPReportElement(FChildren[AIndex])
+  else
+    ReportError(SErrInvalidChildIndex, [0]);
+end;
+
+function TFPReportElementWithChildren.GetChildCount: integer;
+begin
+  if Assigned(FChildren) then
+    Result := FChildren.Count
+  else
+    Result := 0;
+end;
+
+procedure TFPReportElementWithChildren.SaveDataToNames;
+
+Var
+  I :Integer;
+
+begin
+  inherited SaveDataToNames;
+  For I:=0 to ChildCount-1 do
+    Child[i].SaveDataToNames;
+end;
+
+procedure TFPReportElementWithChildren.RestoreDataFromNames;
+
+Var
+  I :Integer;
+begin
+  inherited RestoreDataFromNames;
+  For I:=0 to ChildCount-1 do
+    Child[i].RestoreDataFromNames;
+end;
+
+procedure TFPReportElementWithChildren.RemoveChild(const AChild: TFPReportElement);
+begin
+  if Assigned(FChildren) then
+  begin
+    FChildren.Remove(AChild);
+    if FChildren.Count = 0 then
+      FreeAndNil(FChildren);
+  end;
+  if not (csDestroying in ComponentState) then
+    Changed;
+end;
+
+procedure TFPReportElementWithChildren.AddChild(const AChild: TFPReportElement);
+begin
+  if not Assigned(FChildren) then
+    FChildren := TFPList.Create;
+  FChildren.Add(AChild);
+  if not (csDestroying in ComponentState) then
+    Changed;
+end;
+
+procedure TFPReportElementWithChildren.PrepareObjects;
+var
+  i: integer;
+begin
+  for i := 0 to ChildCount - 1 do
+  begin
+    Child[i].PrepareObject;
+  end;
+end;
+
+procedure TFPReportElementWithChildren.RecalcLayout;
+var
+  i: integer;
+begin
+  if Assigned(FChildren) then
+  begin
+    for i := 0 to FChildren.Count -1 do
+      Child[i].RecalcLayout;
+  end;
+end;
+
+destructor TFPReportElementWithChildren.Destroy;
+//var
+//  i: integer;
+begin
+  if Assigned(FChildren) then
+  begin
+//    for i := 0 to FChildren.Count - 1 do
+//      Child[i].FParent := nil;
+    FreeAndNil(FChildren);
+  end;
+  inherited Destroy;
+end;
+
+procedure TFPReportElementWithChildren.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+var
+  i: integer;
+begin
+  inherited WriteElement(AWriter, AOriginal);
+  if Assigned(FChildren) then
+  begin
+    AWriter.PushElement('Children');
+    try
+      for i := 0 to FChildren.Count-1 do
+      begin
+        AWriter.PushElement(IntToStr(i)); // use child index as identifier
+        try
+          TFPReportElement(FChildren[i]).WriteElement(AWriter, AOriginal);
+        finally
+          AWriter.PopElement;
+        end;
+      end;
+    finally
+      AWriter.PopElement;
+    end;
+  end;
+end;
+
+procedure TFPReportElementWithChildren.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+  i: integer;
+  c: TFPReportElement;
+  lName: string;
+begin
+  inherited ReadElement(AReader);
+  E := AReader.FindChild('Children');
+  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
+        lName := AReader.CurrentElementName;
+        c := gElementFactory.CreateInstance(lName, self);
+        c.ReadElement(AReader);
+      finally
+        AReader.PopElement;
+      end;
+    end;  { for i }
+    AReader.PopElement;
+  end; { children }
+end;
+
+function TFPReportElementWithChildren.Equals(AElement: TFPReportElement): boolean;
+var
+  lRE: TFPReportElementWithChildren;
+  i: integer;
+begin
+  if not (AElement is TFPReportElementWithChildren) then
+    ReportError(SErrIncorrectDescendant);
+  lRE := TFPReportElementWithChildren(AElement);
+  Result := inherited Equals(lRE);
+  if Result then
+  begin
+    Result := ChildCount = lRe.ChildCount;
+    if Result then
+    begin
+      for i := 0 to ChildCount-1 do
+      begin
+        Result := self.Child[i].Equals(lRE.Child[i]);
+        if not Result then
+          Break;
+      end;
+    end;
+  end;
+end;
+
+{ TFPReportCustomPage }
+
+procedure TFPReportCustomPage.SetReport(const AValue: TFPCustomReport);
+begin
+  if FReport = AValue then
+    Exit;
+  if Assigned(FReport) then
+  begin
+    FReport.RemoveFreeNotification(self);
+    FReport.RemovePage(Self);
+  end;
+  FReport := AValue;
+  if Assigned(FReport) then
+  begin
+    FReport.AddPage(Self);
+    FReport.FreeNotification(Self);
+  end;
+end;
+
+procedure TFPReportCustomPage.SetReportData(const AValue: TFPReportData);
+begin
+  if FData = AValue then
+    Exit;
+  if Assigned(FData) then
+    FData.RemoveFreeNotification(Self);
+  FData := AValue;
+  if Assigned(FData) then
+    FData.FreeNotification(Self);
+end;
+
+procedure TFPReportCustomPage.SetColumnLayout(AValue: TFPReportColumnLayout);
+begin
+  if FColumnLayout = AValue then
+    Exit;
+  FColumnLayout := AValue;
+end;
+
+procedure TFPReportCustomPage.SetColumnCount(AValue: Byte);
+begin
+  if FColumnCount = AValue then
+    Exit;
+  if AValue < 1 then
+    FColumnCount := 1
+  else
+    FColumnCount := AValue;
+  RecalcLayout;
+end;
+
+procedure TFPReportCustomPage.SetColumnGap(AValue: TFPReportUnits);
+begin
+  if FColumnGap = AValue then
+    Exit;
+  if AValue < 0 then
+    FColumnGap := 0
+  else
+    FColumnGap := AValue;
+  RecalcLayout;
+end;
+
+procedure TFPReportCustomPage.SaveDataToNames;
+begin
+  inherited ;
+  If Assigned(Data) then
+    FDataName:=Data.Name;
+end;
+
+procedure TFPReportCustomPage.RestoreDataFromNames;
+begin
+  Inherited;
+  if FDataName<>'' then
+    Data:=Report.ReportData.FindReportData(FDataName)
+  else
+    Data:=Nil;
+end;
+
+procedure TFPReportCustomPage.RemoveChild(const AChild: TFPReportElement);
+begin
+  inherited RemoveChild(AChild);
+  if (AChild is TFPReportCustomBand) and Assigned(FBands) then
+  begin
+    FBands.Remove(AChild);
+    if (FBands.Count = 0) then
+      FreeAndNil(FBands);
+  end;
+end;
+
+procedure TFPReportCustomPage.AddChild(const AChild: TFPReportElement);
+var
+  lBand: TFPReportCustomBand;
+begin
+  inherited AddChild(AChild);
+  if (AChild is TFPReportCustomBand) then
+  begin
+    lBand := TFPReportCustomBand(AChild);
+    if not Assigned(FBands) then
+      FBands := TFPList.Create;
+    FBands.Add(lBand);
+    ApplyBandWidth(lBand);
+    if (AChild is TFPReportCustomBandWithData) then
+      TFPReportCustomBandWithData(AChild).Data := self.Data;
+  end;
+end;
+
+procedure TFPReportCustomPage.RecalcLayout;
+var
+  W, H: TFPReportunits;
+  b: integer;
+begin
+  if (Pagesize.Width = 0) and (Pagesize.Height = 0) then
+    Exit;
+  case Orientation of
+    poPortrait:
+    begin
+      W := PageSize.Width;
+      H := PageSize.Height;
+    end;
+    poLandscape:
+    begin
+      W := PageSize.Height;
+      H := PageSize.Width;
+    end;
+  end;
+  BeginUpdate;
+  try
+    Layout.Left := Margins.Left;
+    Layout.Width := W - Margins.Left - Margins.Right;
+    Layout.Top := Margins.Top;
+    Layout.Height := H - Margins.Top - Margins.Bottom;
+
+    for b := 0 to BandCount-1 do
+      ApplyBandWidth(Bands[b]);
+  finally
+    EndUpdate;
+  end;
+end;
+
+procedure TFPReportCustomPage.CalcPrintPosition;
+var
+  i: integer;
+  b: TFPReportCustomBand;
+  ly: TFPReportUnits;
+begin
+  if not Assigned(RTLayout) then
+    exit;
+  ly := Layout.Top;
+  for i := 0 to BandCount - 1 do
+  begin
+    b := Bands[i];
+    b.RTLayout.Top := ly;
+    ly := ly + b.Layout.Height;
+  end;
+end;
+
+procedure TFPReportCustomPage.PrepareObjects;
+var
+  lPage: TFPReportCustomPage;
+begin
+  lPage := TFPReportCustomPage.Create(nil);
+  lPage.Assign(self);
+  lPage.CreateRTLayout;
+  Report.FRTCurPageIdx := Report.RTObjects.Add(lPage);
+end;
+
+procedure TFPReportCustomPage.MarginsChanged;
+begin
+  RecalcLayout;
+end;
+
+procedure TFPReportCustomPage.PageSizeChanged;
+begin
+  RecalcLayout;
+end;
+
+constructor TFPReportCustomPage.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FMargins := TFPReportMargins.Create(Self);
+  FPageSize := TFPReportPageSize.Create(Self);
+  if AOwner is TFPCustomReport then
+    Report := AOwner as TFPCustomReport;
+  FColumnCount := 1;
+  FColumnLayout := clVertical;
+  FFont := TFPReportFont.Create;
+end;
+
+destructor TFPReportCustomPage.Destroy;
+begin
+  Report := nil;
+  FreeAndNil(FMargins);
+  FreeAndNil(FPageSize);
+  FreeAndNil(FBands);
+  FreeAndNil(FFont);
+  inherited Destroy;
+end;
+
+procedure TFPReportCustomPage.Assign(Source: TPersistent);
+var
+  E: TFPReportCustomPage;
+begin
+  if (Source is TFPReportCustomPage) then
+  begin
+    E := Source as TFPReportCustomPage;
+    PageSize.Assign(E.PageSize);
+    Orientation := E.Orientation;
+    Report := E.Report;
+    Font.Assign(E.Font);
+  end;
+  inherited Assign(Source);
+end;
+
+procedure TFPReportCustomPage.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+  N : String;
+  D : TFPReportDataItem;
+
+begin
+  inherited ReadElement(AReader);
+  E := AReader.FindChild('Margins');
+  if Assigned(E) then
+  begin
+    AReader.PushElement(E);
+    try
+      FMargins.ReadElement(AReader);
+    finally
+      AReader.PopElement;
+    end;
+  end;
+  Orientation := StringToPaperOrientation(AReader.ReadString('Orientation', 'poPortrait'));
+  Pagesize.PaperName := AReader.ReadString('PageSize.PaperName', 'A4');
+  Pagesize.Width := AReader.ReadFloat('PageSize.Width', 210);
+  Pagesize.Height := AReader.ReadFloat('PageSize.Height', 297);
+  Font.Name := AReader.ReadString('FontName', Font.Name);
+  Font.Size := AReader.ReadInteger('FontSize', Font.Size);
+  Font.Color := AReader.ReadInteger('FontColor', Font.Color);
+  FDataName:=AReader.ReadString('Data','');
+  if FDataName<>'' then
+    RestoreDataFromNames;
+end;
+
+function TFPReportCustomPage.FindBand(ABand: TFPReportBandClass): TFPReportCustomBand;
+var
+  i: integer;
+begin
+  Result := nil;
+  for i := 0 to BandCount-1 do
+  begin
+    if Bands[i] is ABand then
+    begin
+      Result := Bands[i];
+      Break;
+    end;
+  end;
+end;
+
+procedure TFPReportCustomPage.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  if (Operation = opRemove) then
+    if (AComponent = FReport) then
+      FReport := nil
+    else if (AComponent = FData) then
+      FData := nil;
+  inherited Notification(AComponent, Operation);
+end;
+
+procedure TFPReportCustomPage.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteString('Orientation', PaperOrientationToString(Orientation));
+  AWriter.WriteString('PageSize.PaperName', PageSize.PaperName);
+  AWriter.WriteFloat('PageSize.Width', PageSize.Width);
+  AWriter.WriteFloat('PageSize.Height', PageSize.Height);
+  AWriter.WriteString('FontName', Font.Name);
+  AWriter.WriteInteger('FontSize', Font.Size);
+  AWriter.WriteInteger('FontColor', Font.Color);
+  if Assigned(FData) then
+    AWriter.WriteString('Data',FData.Name);
+  AWriter.PushElement('Margins');
+  try
+    FMargins.WriteElement(AWriter, nil);
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
+function TFPReportCustomPage.PageIndex: Integer;
+begin
+  Result:=-1;
+  If (Owner<>Nil) then
+    Result:=ComponentIndex;
+end;
+
+function TFPReportCustomPage.GetBand(AIndex: integer): TFPReportCustomBand;
+begin
+  if Assigned(FBands) then
+    Result := TFPReportCustomBand(FBands[AIndex]);
+end;
+
+function TFPReportCustomPage.GetBandCount: integer;
+begin
+  if Assigned(FBands) then
+    Result := FBands.Count
+  else
+    Result := 0;
+end;
+
+function TFPReportCustomPage.BandWidthFromColumnCount: TFPReportUnits;
+var
+  lTotalColumnGap: TFPReportUnits;
+begin
+  if ColumnCount = 1 then
+    Result := Layout.Width
+  else
+  begin
+    if ColumnGap > 0.0 then
+      lTotalColumnGap := ColumnGap * (ColumnCount-1)
+    else
+      lTotalColumnGap := 0.0;
+    Result := (1 / ColumnCount) * (Layout.Width - lTotalColumnGap);
+  end;
+end;
+
+procedure TFPReportCustomPage.ApplyBandWidth(ABand: TFPReportCustomBand);
+begin
+  { set Band Width appropriately - certain bands are not affected by ColumnCount }
+  if (ABand is TFPReportCustomTitleBand)
+      or (ABand is TFPReportCustomSummaryBand)
+      or (ABand is TFPReportCustomPageHeaderBand)
+      or (ABand is TFPReportCustomPageFooterBand) then
+    ABand.Layout.Width := Layout.Width
+  else
+    ABand.Layout.Width := BandWidthFromColumnCount;
+end;
+
+procedure TFPReportCustomPage.SetFont(AValue: TFPReportFont);
+begin
+  if Assigned(FFont) then
+    FreeAndNil(FFont);
+  FFont := AValue;
+  Changed;
+end;
+
+procedure TFPReportCustomPage.SetMargins(const AValue: TFPReportMargins);
+begin
+  if FMargins = AValue then
+    Exit;
+  FMargins.Assign(AValue);
+end;
+
+procedure TFPReportCustomPage.SetOrientation(const AValue: TFPReportPaperOrientation);
+begin
+  if FOrientation = AValue then
+    Exit;
+  FOrientation := AValue;
+  RecalcLayout;
+end;
+
+procedure TFPReportCustomPage.SetPageSize(const AValue: TFPReportPageSize);
+begin
+  if FPageSize = AValue then
+    Exit;
+  FPageSize.Assign(AValue);
+end;
+
+{ TFPCustomReport }
+
+function TFPCustomReport.GetPage(AIndex: integer): TFPReportCustomPage;
+begin
+  if Assigned(FPages) then
+    Result := TFPReportCustomPage(FPages[AIndex])
+  else
+    ReportError(SErrInValidPageIndex, [AIndex]);
+end;
+
+function TFPCustomReport.GetPageCount: integer;
+begin
+  if Assigned(FPages) then
+    Result := FPages.Count
+  else
+    Result := 0;
+end;
+
+function TFPCustomReport.GetRenderedPageCount: integer;
+var
+  i: integer;
+begin
+  Result := 0;
+  for i := Low(FPerDesignerPageCount) to High(FPerDesignerPageCount) do
+    inc(Result, FPerDesignerPageCount[i]);
+end;
+
+procedure TFPCustomReport.BuiltinExprRecNo(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+begin
+  Result.ResInteger := FPageData.RecNo;
+end;
+
+procedure TFPCustomReport.BuiltinGetPageNumber(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+begin
+  Result.ResInteger := FPageNumber;
+end;
+
+procedure TFPCustomReport.BuiltinGetPageNoPerDesignerPage(var Result: TFPExpressionResult;
+  const Args: TExprParameterArray);
+begin
+  Result.ResInteger := FPageNumberPerDesignerPage;
+end;
+
+procedure TFPCustomReport.BuiltinGetPageCount(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+begin
+  Result.ResInteger := FPageCount;
+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.Visible then
+    begin
+      ABand.RemoveChild(e);
+      FreeAndNil(e);
+    end;
+  end;
+end;
+
+procedure TFPCustomReport.EmptyRTObjects;
+begin
+  while RTObjects.Count > 0 do
+  begin
+    TFPReportElement(RTObjects[0]).Free;
+    RTObjects.Delete(0);
+  end;
+end;
+
+procedure TFPCustomReport.ClearDataBandLastTextValues(ABand: TFPReportCustomBandWithData);
+var
+  i: integer;
+  m: TFPReportCustomMemo;
+begin
+  for i := 0 to ABand.ChildCount-1 do
+  begin
+    if ABand.Child[i] is TFPReportCustomMemo then
+    begin
+      m := TFPReportCustomMemo(ABand.Child[i]);
+      m.FLastText := '';
+    end;
+  end;
+end;
+
+procedure TFPCustomReport.ProcessAggregates(const APageIdx: integer; const AData: TFPReportData);
+var
+  b: integer;
+  c: integer;
+  i: 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;
+    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]);
+        for i := 0 to Length(m.ExpressionNodes)-1 do
+        begin
+          if Assigned(m.ExpressionNodes[i].ExprNode) then
+          begin
+            if m.ExpressionNodes[i].ExprNode.HasAggregate then
+              m.ExpressionNodes[i].ExprNode.UpdateAggregate;
+          end;
+        end;  { for ... }
+      end; { children of band }
+  end; { bands }
+end;
+
+procedure TFPCustomReport.ClearReferenceList;
+begin
+  if not Assigned(FReferenceList) then
+    FReferenceList := TStringList.Create
+  else
+    FReferenceList.Clear;
+end;
+
+procedure TFPCustomReport.AddReference(const AParentName, AChildName: string);
+begin
+  FReferenceList.Values[AParentName] := AChildName;
+end;
+
+procedure TFPCustomReport.FixupReferences;
+var
+  i: integer;
+  p: TFPReportElement;
+  c: TFPReportElement;
+begin
+  if FReferenceList.Count = 1 then
+    Exit;
+  for i := 0 to FReferenceList.Count-1 do
+  begin
+    p := FindRecursive(FReferenceList.Names[i]);
+    if not Assigned(p) then
+      Continue; // failded to find the Parent
+    c := FindRecursive(FReferenceList.ValueFromIndex[i]);
+    if not Assigned(c) then
+      Continue; // failded to find the Child
+    if not (c is TFPReportCustomChildBand) then
+      Continue; // wrong type - unexpected
+    if p is TFPReportCustomBand then
+      TFPReportCustomBand(p).ChildBand := TFPReportChildBand(c)
+    else if p is TFPReportCustomChildBand then
+      TFPReportCustomChildBand(p).ChildBand := TFPReportChildBand(c);
+  end;
+end;
+
+procedure TFPCustomReport.DoBeforeRenderReport;
+begin
+  if Assigned(FOnBeforeRenderReport) then
+    FOnBeforeRenderReport(self);
+end;
+
+procedure TFPCustomReport.DoAfterRenderReport;
+begin
+  if Assigned(FOnAfterRenderReport) then
+    FOnAfterRenderReport(self);
+end;
+
+procedure TFPCustomReport.DoProcessTwoPass;
+var
+  p, b, m: integer; // page, band, memo
+  i: integer;
+  rpage: TFPReportCustomPage;
+  rband: TFPReportCustomBand;
+  rmemo: TFPReportCustomMemo;
+  txtblk: TFPTextBlock;
+begin
+  for p := 0 to RTObjects.Count-1 do  // page
+  begin
+    rpage := TFPReportPage(RTObjects[p]);
+    for b := 0 to rpage.BandCount-1 do
+    begin
+      rband := rpage.Bands[b];
+      for m := 0 to rband.ChildCount-1 do // band
+      begin
+        if rband.Child[m] is TFPReportCustomMemo then // memo
+        begin
+          rmemo := TFPReportCustomMemo(rband.Child[m]);
+          for i := 0 to rmemo.TextBlockList.Count-1 do
+          begin
+            txtblk := rmemo.TextBlockList[i];
+            txtblk.Text := StringReplace(txtblk.Text, cPageCountMarker, IntToStr(FPageCount), [rfReplaceAll, rfIgnoreCase]);
+          end;
+        end;
+      end; { m }
+    end; { b }
+  end;
+end;
+
+procedure TFPCustomReport.DoGetExpressionVariableValue(var Result: TFPExpressionResult; constref AName: ShortString);
+var
+  p: integer;
+  b: integer;
+  lBand: TFPReportCustomDataBand;
+  lDataName: string;
+  lFieldName: string;
+  lField: TFPReportDataField;
+
+  procedure SetResultValue(const AData: TFPReportData);
+  begin
+    case FExpr.Identifiers.FindIdentifier(AName).ResultType of
+      rtBoolean:    Result.ResBoolean   := AData.FieldValues[lFieldName];
+      rtInteger:    Result.ResInteger   := AData.FieldValues[lFieldName];
+      rtFloat:      Result.ResFloat     := AData.FieldValues[lFieldName];
+      rtDateTime:   Result.ResDateTime  := AData.FieldValues[lFieldName];
+      rtString:     Result.ResString    := AData.FieldValues[lFieldName];
+    end;
+  end;
+
+begin
+  {$ifdef gdebug}
+  writeln('TFPCustomReport.DoGetExpressionVariableValue():  AName = ' + AName);
+  {$endif}
+  Result.ResString := '';
+  lDataName := '';
+  lFieldName := AName;
+  lField := nil;
+  p := Pos('.', lFieldName);
+  if p > 0 then
+  begin
+    lDataName := Copy(AName, 1, p-1);
+    lFieldName := Copy(AName, p+1, Length(AName));
+  end;
+
+  { look for data bands }
+  for p := 0 to PageCount-1 do
+  begin
+    for b := 0 to Pages[p].BandCount-1 do
+    begin
+      if Pages[p].Bands[b] is TFPReportCustomDataBand then
+      begin
+        lBand := TFPReportCustomDataBand(Pages[p].Bands[b]);
+        if (lDataName = '') then // try every databand
+        begin
+          lField := lBand.Data.DataFields.FindField(lFieldName);
+        end
+        else
+        begin // try only databands where Name is a match
+          if lBand.Data.Name = lDataName then
+            lField := lBand.Data.DataFields.FindField(lFieldName);
+        end;
+        if lField <> nil then
+        begin
+          SetResultValue(lBand.Data);
+          Exit;
+        end;
+      end; { databand types only }
+    end; { band count }
+  end; { page count }
+end;
+
+procedure TFPCustomReport.SetReportData(AValue: TFPReportDataCollection);
+begin
+  if FReportData=AValue then Exit;
+  FReportData.Assign(AValue);
+end;
+
+procedure TFPCustomReport.SetVariables(AValue: TFPReportVariables);
+begin
+  if FVariables=AValue then Exit;
+  FVariables.Assign(AValue);
+end;
+
+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;
+
+  procedure RemoveColumnFooterFromFooterList;
+  var
+    idx: integer;
+    lBand: TFPReportCustomBand;
+  begin
+    idx := lFooterList.Find(TFPReportCustomColumnFooterBand, lBand);
+    if idx > -1 then
+      lFooterList.Delete(idx);
+  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 CommonRuntimeBandProcessing(const ADsgnBand: TFPReportCustomBand);
+  begin
+    ADsgnBand.PrepareObjects;
+    lRTBand := FRTCurBand;
+    lRTBand.RecalcLayout;
+    lRTBand.BeforePrint;
+    lRTBand.RTLayout.Top := lLastYPos;
+    lRTBand.RTLayout.Left := lLastXPos;
+  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;
+
+  { 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;
+
+    if DoVisibleOnPageChecks(ADsgnBand as TFPReportCustomPageHeaderBand) then
+      Exit;
+
+    CommonRuntimeBandProcessing(ADsgnBand);
+    Result := False;
+  end;
+
+  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;
+
+  function LayoutColumnFooterBand(APage: TFPReportCustomPage; ABand: TFPReportCustomColumnFooterBand): TFPReportCustomColumnFooterBand;
+  var
+    lBandCount: integer;
+    lOverflowBand: TFPReportCustomBand;
+  begin
+    lBandCount := lRTPage.BandCount - 1;
+    lOverflowBand := lRTPage.Bands[lBandCount]; { store reference to band that caused the new column }
+
+    CommonRuntimeBandProcessing(ABand);
+    Result := TFPReportCustomColumnFooterBand(lRTBand);
+
+    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
+      else
+        lRTBand.RTLayout.Top := lLastYPos;
+    end;
+  end;
+
+  function NoSpaceRemaining: boolean;
+  begin
+    if lSpaceLeft <= 0 then
+    begin
+      Result := True;
+      if lMultiColumn and (lCurrentColumn < Pages[lPageIdx].ColumnCount) then
+      begin
+        lNewColumn := True;
+      end
+      else
+      begin
+        lOverflowed := True;
+        lNewPage := True;
+      end;
+    end
+    else
+      Result := False;
+  end;
+
+  procedure StartNewColumn;
+  var
+    lIdx: integer;
+    lBandCount: integer;
+    lBand: TFPReportCustomBand;
+    lOverflowBand: TFPReportCustomBand;
+  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
+    begin
+      lBand := lHeaderList[lIdx];
+
+      if lBand is TFPReportCustomPageHeaderBand then
+      begin
+        if DoVisibleOnPageChecks(lBand) then
+          Continue;
+      end
+      else if lBand is TFPReportCustomColumnHeaderBand then
+      begin
+        if ShowColumnHeaderBand(lBand) then
+          Continue;
+      end;
+
+      lSpaceLeft := lSpaceLeft - lBand.Layout.Height;
+      lLastYPos := lLastYPos + lBand.Layout.Height;
+    end;
+
+    inc(lCurrentColumn);
+
+    { If footer band exists, reduce available space }
+    lBand := lRTPage.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 := lLastXPos;
+    lOverflowBand.RTLayout.Top := lLastYPos;
+    { Adjust the next starting point of the next data band. }
+    UpdateSpaceRemaining(lOverflowBand);
+
+    lNewColumn := False;
+  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);
+
+    { Fix position of last band that caused the overflow }
+    lOverflowBand.RTLayout.Top := lLastYPos;
+    lOverflowBand.RTLayout.Left := lLastXPos;
+    UpdateSpaceRemaining(lOverflowBand);
+  end;
+
+  procedure LayoutFooterBand;
+  var
+    lBand: TFPReportCustomPageFooterBand;
+  begin
+    lPageFooterYPos := -1;
+    if not (lDsgnBand is TFPReportCustomPageFooterBand) then
+      Exit;
+
+    lBand := TFPReportCustomPageFooterBand(lDsgnBand);
+    if DoVisibleOnPageChecks(lBand) then
+      Exit;
+
+    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;
+
+  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;
+
+  procedure PopulateFooterList(APage: TFPReportCustomPage);
+  begin
+    lFooterList.Clear;
+    lFooterList.Add(APage.FindBand(TFPReportCustomPageFooterBand));
+  end;
+
+  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
+    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;
+
+    { Show footer band if it exists }
+    lDsgnBand := lFooterList.Find(TFPReportCustomPageFooterBand);
+    if Assigned(lDsgnBand) then
+      LayoutFooterBand;
+
+    lNewPage := False;
+  end;
+
+  procedure ShowDataBand(const ADsgnBand: TFPReportCustomDataBand);
+  var
+    lDsgnChildBand: TFPReportChildBand;
+    lLastDataBand: TFPReportCustomBand;
+  begin
+    lLastDsgnDataBand := ADsgnBand;
+    CommonRuntimeBandProcessing(ADsgnBand);
+    if lRTBand.Visible 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;
+
+        if lNewColumn then
+          StartNewColumn;
+
+        if lNewPage then
+          StartNewPage;
+
+        { 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
+      begin
+        CommonRuntimeBandProcessing(lDsgnChildBand);
+        if lRTBand.Visible 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);
+        end;
+        lDsgnChildBand := lDsgnChildBand.ChildBand;
+      end;  { while ChildBand <> nil }
+    end
+    else
+    begin
+      // remove invisible band
+      lRTPage.RemoveChild(lRTBand);
+      FreeAndNil(lRTBand);
+    end;
+  end;
+
+  procedure ShowDataHeaderBand(const ADsgnBand: TFPReportCustomDataHeaderBand);
+  begin
+    if lDataHeaderPrinted then
+      Exit; // nothing further to do
+
+    CommonRuntimeBandProcessing(ADsgnBand);
+    if lRTBand.Visible then
+    begin
+      lDataHeaderPrinted := True;
+      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; { 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.Visible 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;
+  end;
+
+  procedure ShowDetailBand(const AMasterBand: TFPReportCustomDataBand);
+  var
+    lDsgnDetailBand: TFPReportCustomDataBand;
+    lDetailBandList: TBandList;
+    lDetailBand: TFPReportCustomBand;
+    lData: TFPReportData;
+    i: integer;
+  begin
+    if AMasterBand = nil then
+      Exit;
+    lDsgnDetailBand := nil;
+    lDetailBandList := TBandList.Create;
+    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);
+
+      { 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;
+          InitializeExpressionVariables(lData);
+          CacheMemoExpressions(lPageIdx, lData);
+        end;
+        lData.First;
+
+        if (not lData.EOF) and (lDsgnDetailBand.HeaderBand <> nil) then
+          ShowDataHeaderBand(lDsgnDetailBand.HeaderBand);
+        while not lData.EOF do
+        begin
+          ProcessAggregates(lPageIdx, lData);
+          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);
+
+        lDsgnDetailBand := nil;
+      end;
+    finally
+      lDetailBandList.Free;
+    end;
+  end;
+
+begin
+  EmptyRTObjects;
+  lHeaderList := Nil;
+  lFooterList := Nil;
+  FBands := Nil;
+  try
+    lHeaderList := TBandList.Create;
+    lFooterList := TBandList.Create;
+    FBands := TBandList.Create;
+    SetLength(FPerDesignerPageCount, PageCount);
+
+
+
+  if TwoPass then
+    lPassCount := 2
+  else
+    lPassCount := 1;
+
+  for lPassIdx := 1 to lPassCount do
+  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;
+    FPageCount := 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;
+      InitializeExpressionVariables(FPageData);
+      CacheMemoExpressions(lPageIdx, FPageData);
+
+      FPageData.First;
+
+      // Create a list of band that need to be printed as page headers
+      PopulateHeaderList(Pages[lPageIdx]);
+
+      // Create a list of bands that need to be printed as page footers
+      PopulateFooterList(Pages[lPageIdx]);
+
+      // 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
+        ProcessAggregates(lPageIdx, FPageData);
+
+        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
+                s := TFPReportCustomGroupHeaderBand(lDsgnBand).Evaluate;
+                if (lLastGroupCondition <> s) then
+                begin
+                  lNewGroupHeader := True;
+                  { process group footer }
+                  if Assigned(TFPReportCustomGroupHeaderBand(lDsgnBand).GroupFooter) then
+                  begin
+                    lDsgnBand := TFPReportCustomGroupHeaderBand(lDsgnBand).GroupFooter;
+                    CommonRuntimeBandProcessing(lDsgnBand);
+                    UpdateSpaceRemaining(lRTBand);
+                    if NoSpaceRemaining then
+                      Break;
+                  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).GroupCondition;
+                if lDsgnBand.Visible = 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 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 TFPReportCustomGroupFooterBand 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 lDsgnBand.Layout.Height > lSpaceLeft then
+              StartNewPage;
+            CommonRuntimeBandProcessing(lDsgnBand);
+            UpdateSpaceRemaining(lRTBand);
+            Break;
+          end;
+        end; { for FBands }
+      end;  { lHasGroupFooter }
+
+      FPageData.Close;
+
+    end;  { if Assigned(FPageData) }
+
+    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 }
+
+  end; { for ... pages }
+
+  if TwoPass and (lPassIdx = 1) then
+  begin
+    FPageCount := RTObjects.Count;
+    EmptyRTObjects;
+  end;
+
+  end; { for ... lPassCount }
+
+  if TwoPass then
+    DoProcessTwoPass;
+
+  finally
+    FreeAndNil(lHeaderList);
+    FreeAndNil(lFooterList);
+    FreeAndNil(FBands);
+    SetLength(FPerDesignerPageCount, 0);
+  end;
+end;
+
+procedure TFPCustomReport.DoBeginReport;
+begin
+  if Assigned(FOnBeginReport) then
+    FOnBeginReport;
+end;
+
+procedure TFPCustomReport.DoEndReport;
+begin
+  if Assigned(FOnEndReport) then
+    FOnEndReport;
+end;
+
+procedure TFPCustomReport.RestoreDefaultVariables;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to FVariables.Count-1 do
+    FVariables[i].RestoreValue;
+end;
+
+procedure TFPCustomReport.InitializeDefaultExpressions;
+
+Var
+  I : Integer;
+  V : TFPReportVariable;
+
+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('PageCount', 'I', '', @BuiltinGetPageCount);
+  FExpr.Identifiers.AddFunction('PageNoPerDesignerPage', 'I', '', @BuiltInGetPageNoPerDesignerPage);
+  For I:=0 to FVariables.Count-1 do
+    begin
+    V:=FVariables[i];
+    V.SaveValue;
+    FExpr.Identifiers.AddVariable(V.Name,V.DataType,@V.GetRTValue);
+    end;
+end;
+
+procedure TFPCustomReport.InitializeExpressionVariables(const AData: TFPReportData);
+var
+  i: Integer;
+  f: string;
+  r: TResultType;
+  d: string;
+
+  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
+        Result := rtString;
+    end;
+  end;
+
+
+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
+    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;
+end;
+
+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;
+
+    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;
+
+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;
+
+function TFPCustomReport.CreateImages: TFPReportImages;
+
+begin
+  Result:=TFPReportImages.Create(self, TFPReportImageItem);
+end;
+
+function TFPCustomReport.CreateVariables: TFPReportVariables;
+
+begin
+  Result:=TFPReportVariables.Create(Self,TFPReportVariable);
+end;
+
+function TFPCustomReport.CreateReportData : TFPReportDataCollection;
+
+begin
+  Result:=TFPReportDataCollection.Create(TFPReportDataItem);
+end;
+
+destructor TFPCustomReport.Destroy;
+begin
+  EmptyRTObjects;
+  FreeAndNil(FReportData);
+  FreeAndNil(FRTObjects);
+  FreeAndNil(FPages);
+  FreeAndNil(FExpr);
+  FreeAndNil(FReferenceList);
+  FreeAndNil(FImages);
+  FreeAndNil(FVariables);
+  inherited Destroy;
+end;
+
+procedure TFPCustomReport.SaveDataToNames;
+
+Var
+  I : Integer;
+
+begin
+  For I:=0 to PageCount-1 do
+    Pages[i].SaveDataToNames;
+end;
+
+procedure TFPCustomReport.RestoreDataFromNames;
+Var
+  I : Integer;
+
+begin
+  For I:=0 to PageCount-1 do
+    Pages[i].RestoreDataFromNames;
+end;
+
+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;
+
+procedure TFPCustomReport.RemovePage(APage: TFPReportCustomPage);
+begin
+  if Assigned(FPages) then
+    FPages.Remove(APage);
+end;
+
+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
+        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;
+  finally
+    AWriter.PopElement;
+  end;
+  // TODO: Implement writing OnRenderReport, OnBeginReport, OnEndReport
+end;
+
+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
+    AReader.PushElement(E);
+    try
+      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
+      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 }
+
+      // TODO: Implement reading OnRenderReport, OnBeginReport, OnEndReport
+    finally
+      AReader.PopElement;
+    end;
+  end;
+  FixupReferences;
+end;
+
+procedure TFPCustomReport.StartRender;
+begin
+  inherited StartRender;
+  DoBeforeRenderReport;
+end;
+
+procedure TFPCustomReport.EndRender;
+begin
+  inherited EndRender;
+  DoAfterRenderReport;
+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;
+
+      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;
+
+procedure TFPCustomReport.RunReport;
+begin
+  DoBeginReport;
+
+  StartLayout;
+  FExpr := TFPexpressionParser.Create(nil);
+  try
+    InitializeDefaultExpressions;
+    DoPrepareReport;
+  finally
+    RestoreDefaultVariables;
+    FreeAndNil(FExpr);
+  end;
+  EndLayout;
+
+  DoEndReport;
+end;
+
+procedure TFPCustomReport.RenderReport(const AExporter: TFPReportExporter);
+begin
+  if not Assigned(AExporter) then
+    Exit;
+  StartRender;
+  try
+    AExporter.Report := self;
+    AExporter.Execute;
+  finally
+    EndRender;
+  end;
+end;
+
+{$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}
+
+{ TFPReportMargins }
+
+procedure TFPReportMargins.SetBottom(const AValue: TFPReportUnits);
+begin
+  if FBottom = AValue then
+    Exit;
+  FBottom := AValue;
+  Changed;
+end;
+
+procedure TFPReportMargins.SetLeft(const AValue: TFPReportUnits);
+begin
+  if FLeft = AValue then
+    Exit;
+  FLeft := AValue;
+  Changed;
+end;
+
+procedure TFPReportMargins.SetRight(const AValue: TFPReportUnits);
+begin
+  if FRight = AValue then
+    Exit;
+  FRight := AValue;
+  Changed;
+end;
+
+procedure TFPReportMargins.SetTop(const AValue: TFPReportUnits);
+begin
+  if FTop = AValue then
+    Exit;
+  FTop := AValue;
+  Changed;
+end;
+
+procedure TFPReportMargins.Changed;
+begin
+  if Assigned(FPage) then
+    FPage.MarginsChanged;
+end;
+
+constructor TFPReportMargins.Create(APage: TFPReportCustomPage);
+begin
+  inherited Create;
+  FPage := APage;
+end;
+
+procedure TFPReportMargins.Assign(Source: TPersistent);
+var
+  S: TFPReportMargins;
+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
+  else
+    inherited Assign(Source);
+end;
+
+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;
+
+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
+  Top := AReader.ReadFloat('Top', Top);
+  Left := AReader.ReadFloat('Left', Left);
+  Bottom := AReader.ReadFloat('Bottom', Bottom);
+  Right := AReader.ReadFloat('Right', Right);
+end;
+
+{ TFPReportCustomBand }
+
+function TFPReportCustomBand.GetReportPage: TFPReportCustomPage;
+begin
+  Result := Parent as TFPReportCustomPage;
+end;
+
+function TFPReportCustomBand.GetFont: TFPReportFont;
+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;
+end;
+
+function TFPReportCustomBand.IsStringValueZero(const AValue: string): boolean;
+var
+  lIntVal: integer;
+  lFloatVal: double;
+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;
+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
+  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;
+  end;
+  RTLayout.Height := h;
+end;
+
+procedure TFPReportCustomBand.SetFont(AValue: TFPReportFont);
+begin
+  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
+    FFont := TFPReportFont.Create;
+    if Assigned(Owner) then
+      FFont.Assign(TFPReportCustomPage(Owner).Font);
+  end;
+  Changed;
+end;
+
+procedure TFPReportCustomBand.SetVisibleOnPage(AValue: TFPReportVisibleOnPage);
+begin
+  if FVisibleOnPage = AValue then
+    Exit;
+  FVisibleOnPage := AValue;
+  Changed;
+end;
+
+function TFPReportCustomBand.GetReportBandName: string;
+begin
+  Result := 'FPCustomReportBand';
+end;
+
+function TFPReportCustomBand.GetData: TFPReportData;
+begin
+  result := nil;
+end;
+
+procedure TFPReportCustomBand.SetDataFromName(AName: String);
+begin
+  // Do nothing
+end;
+
+function TFPReportCustomBand.ExpandMacro(const s: String; const AIsExpr: boolean): TFPReportString;
+var
+  pstart: integer;
+  pend: integer;
+  len: integer;
+  m: string;  // macro
+  mv: string; // macro value
+  r: string;
+  lFoundMacroInMacro: boolean;
+
+  function EvaluateExpression(const AExpr: String): String;
+  var
+    lExpr: TFPExpressionParser;
+  begin
+    Result := '';
+    lExpr := Page.Report.FExpr;
+    lExpr.Expression := AExpr;
+    case lExpr.ResultType of
+      rtString  : Result := lExpr.AsString;
+      rtInteger : Result := IntToStr(lExpr.AsInteger);
+      rtFloat   : Result := FloatToStr(lExpr.AsFloat);
+      rtBoolean : Result := BoolToStr(lExpr.AsBoolean, True);
+      rtDateTime : Result := FormatDateTime('yyyy-mm-dd', lExpr.AsDateTime);
+    end;
+  end;
+
+begin
+  r := s;
+  lFoundMacroInMacro := False;
+  pstart := Pos('[', r);
+  while (pstart > 0) or lFoundMacroInMacro do
+  begin
+    if lFoundMacroInMacro then
+    begin
+      pstart := Pos('[', r);
+      lFoundMacroInMacro := False;
+    end;
+    len := Length(r);
+    pend := pstart + 2;
+    while pend < len do
+    begin
+      if r[pend] = '[' then  // a macro inside a macro
+      begin
+        lFoundMacroInMacro := True;
+        pstart := pend;
+      end;
+      if r[pend] = ']' then
+        break
+      else
+        inc(pend);
+    end;
+
+    m := Copy(r, pstart, (pend-pstart)+1);
+    if (m = '[PAGECOUNT]') and Page.Report.TwoPass and Page.Report.IsFirstPass then
+    begin
+      // replace macro with a non-marco marker. We'll replace the marker in the second pass of the report.
+      r := StringReplace(r, m, cPageCountMarker, [rfReplaceAll, rfIgnoreCase]);
+      // look for more macros
+      pstart := Pos('[', r);
+      Continue;
+    end;
+
+    len := Length(m);
+    try
+      if Assigned(GetData) then
+      begin
+        try
+          mv := GetData.FieldValues[Copy(m, 2, len-2)];
+        except
+          on e: EVariantTypeCastError do  // maybe we have an expression not data field
+          begin
+            mv := EvaluateExpression(Copy(m, 2, len-2));
+          end;
+        end;
+      end
+      else
+      begin // No Data assigned, but maybe we have an expression
+        mv := EvaluateExpression(Copy(m, 2, len-2));
+      end;
+    except
+      on e: EVariantTypeCastError do    // ReportData.OnGetValue did not handle all macros, so handle this gracefully
+        mv := SErrUnknownMacro+': '+copy(m,2,len-2);
+      on e: EExprParser do
+        mv := SErrUnknownMacro+': '+copy(m,2,len-2);
+    end;
+    r := StringReplace(r, m, mv, [rfReplaceAll, rfIgnoreCase]);
+    // look for more macros
+    pstart := PosEx('[', r,PStart+Length(mv));
+  end;
+  { This extra check is mostly for ReportGroupHeader expression processing }
+  if (pstart = 0) and Assigned(GetData) and AIsExpr then
+  begin
+    try
+      r := EvaluateExpression(r);
+    except
+      on E: Exception do
+      begin
+        {$ifdef gdebug}
+        writeln('ERROR in expression: ', E.Message);
+        {$endif}
+        // do nothing - move on as we probably handled the expression a bit earlier in this code
+      end;
+    end;
+  end;
+  Result := r;
+end;
+
+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
+  inherited CreateRTLayout;
+  FRTLayout.Left := Page.Layout.Left;
+end;
+
+procedure TFPReportCustomBand.PrepareObjects;
+var
+  lRTPage: TFPReportCustomPage;
+  i: integer;
+  m: TFPReportMemo;
+  cb: TFPReportCheckbox;
+  img: TFPReportCustomImage;
+  s: string;
+  c: integer;
+  n: TFPExprNode;
+  nIdx: integer;
+begin
+  i := Page.Report.FRTCurPageIdx;
+  if Assigned(Page.Report.RTObjects[i]) then
+  begin
+    lRTPage := TFPReportCustomPage(Page.Report.RTObjects[i]);
+    { Thanks to the factory, create the correct [band type] runtime instance }
+    Page.Report.FRTCurBand := gElementFactory.CreateInstance(self.GetReportBandName, lRTPage) as TFPReportCustomBand;
+    Page.Report.FRTCurBand.Assign(self);
+    Page.Report.FRTCurBand.CreateRTLayout;
+  end;
+  inherited PrepareObjects;
+
+  if self is TFPReportGroupHeaderBand then
+  begin
+    s := TFPReportGroupHeaderBand(Page.Report.FRTCurBand).GroupCondition;
+    s := ExpandMacro(s, True);
+    TFPReportCustomGroupHeaderBand(Page.Report.FRTCurBand).GroupCondition := s;
+  end;
+
+  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;
+
+procedure TFPReportCustomBand.RecalcLayout;
+begin
+  inherited RecalcLayout;
+  if StretchMode <> smDontStretch then
+    ApplyStretchMode;
+end;
+
+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
+  Result:=btUnknown;
+end;
+
+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
+  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;
+
+constructor TFPReportCustomBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FVisibleOnPage := vpAll;
+  FUseParentFont := True;
+  FFont := nil
+end;
+
+destructor TFPReportCustomBand.Destroy;
+begin
+  FreeAndNil(FFont);
+  inherited Destroy;
+end;
+
+procedure TFPReportCustomBand.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+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;
+  end;
+end;
+
+procedure TFPReportCustomBand.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+  s: string;
+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
+      begin
+        Font.Name := AReader.ReadString('FontName', Font.Name);
+        Font.Size := AReader.ReadInteger('FontSize', Font.Size);
+        Font.Color := AReader.ReadInteger('FontColor', Font.Color);
+      end;
+
+      // TODO: Read Data information
+      S:=AReader.ReadString('Data','');
+      if (S<>'') then
+        SetDataFromName(S);
+    finally
+      AReader.PopElement;
+    end;
+  end;
+end;
+
+{ TFPReportCustomBandWithData }
+
+procedure TFPReportCustomBandWithData.SetData(const AValue: TFPReportData);
+begin
+  if FData = AValue then
+    Exit;
+  if Assigned(FData) then
+    FData.RemoveFreeNotification(Self);
+  FData := AValue;
+  if Assigned(FData) then
+    FData.FreeNotification(Self);
+end;
+
+procedure TFPReportCustomBandWithData.SaveDataToNames;
+begin
+  inherited SaveDataToNames;
+  if Assigned(FData) then
+    FDataName:=FData.Name
+  else
+    FDataName:='';
+end;
+
+procedure TFPReportCustomBandWithData.ResolveDataName;
+
+begin
+  if (FDataName<>'') then
+    Data:=Report.ReportData.FindReportData(FDataName)
+  else
+    Data:=Nil;
+end;
+procedure TFPReportCustomBandWithData.RestoreDataFromNames;
+
+begin
+  inherited RestoreDataFromNames;
+  ResolveDataName;
+end;
+
+function TFPReportCustomBandWithData.GetData: TFPReportData;
+begin
+  Result := FData;
+end;
+
+procedure TFPReportCustomBandWithData.SetDataFromName(AName: String);
+begin
+  FDataName:=AName;
+  ResolveDataName;
+end;
+
+procedure TFPReportCustomBandWithData.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  if Operation = opRemove then
+  begin
+    if AComponent = FData then
+      FData := nil;
+  end;
+  inherited Notification(AComponent, Operation);
+end;
+
+constructor TFPReportCustomBandWithData.Create(AOwner: TComponent);
+begin
+  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;
+
+function TFPReportCustomGroupFooterBand.GetReportBandName: string;
+begin
+  Result := 'GroupFooterBand';
+end;
+
+procedure TFPReportCustomGroupFooterBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+begin
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  if Assigned(GroupHeader) then
+    AWriter.WriteString('GroupHeader', GroupHeader.Name);
+end;
+
+procedure TFPReportCustomGroupFooterBand.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  if (Operation = opRemove) and (AComponent = FGroupHeader) then
+    FGroupHeader := nil;
+  inherited Notification(AComponent, Operation);
+end;
+
+procedure TFPReportCustomGroupFooterBand.ReadElement(AReader: TFPReportStreamer);
+var
+  s: string;
+//  c: TFPReportElement;
+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);
+end;
+
+class function TFPReportCustomGroupFooterBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btGroupFooter;
+end;
+
+
+{ TFPReportImageItem }
+
+function TFPReportImageItem.GetHeight: Integer;
+begin
+  If Assigned(FImage) then
+    Result:=FImage.Height
+  else
+    Result:=FHeight;
+end;
+
+function TFPReportImageItem.GetStreamed: TBytes;
+begin
+  if Length(FStreamed)=0 then
+    CreateStreamedData;
+  Result:=FStreamed;
+end;
+
+function TFPReportImageItem.GetWidth: Integer;
+begin
+  If Assigned(FImage) then
+    Result:=FImage.Width
+  else
+    Result:=FWidth;
+end;
+
+procedure TFPReportImageItem.SetImage(AValue: TFPCustomImage);
+begin
+  if FImage=AValue then Exit;
+  FImage:=AValue;
+  SetLength(FStreamed,0);
+end;
+
+procedure TFPReportImageItem.SetStreamed(AValue: TBytes);
+begin
+  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;
+
+constructor TFPReportImageItem.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  FOwnsImage := True;
+end;
+
+destructor TFPReportImageItem.Destroy;
+begin
+  if FOwnsImage then
+    FreeAndNil(FImage);
+  inherited Destroy;
+end;
+
+procedure TFPReportImageItem.CreateStreamedData;
+Var
+  X, Y: Integer;
+  C: TFPColor;
+  MS: TMemoryStream;
+  Str: TStream;
+  CWhite: TFPColor; // white color
+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;
+  end;
+end;
+
+function TFPReportImageItem.WriteImageStream(AStream: TStream): UInt64;
+var
+  Img: TBytes;
+begin
+  Img := StreamedData;
+  Result := Length(Img);
+  AStream.WriteBuffer(Img[0],Result);
+end;
+
+function TFPReportImageItem.Equals(AImage: TFPCustomImage): boolean;
+var
+  x, y: Integer;
+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;
+end;
+
+procedure TFPReportImageItem.WriteElement(AWriter: TFPReportStreamer);
+var
+  ms: TMemoryStream;
+  png: TFPWriterPNG;
+begin
+  if Assigned(Image) then
+  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;
+end;
+
+procedure TFPReportImageItem.ReadElement(AReader: TFPReportStreamer);
+var
+  ms: TMemoryStream;
+begin
+  ms := TMemoryStream.Create;
+  try
+    if AReader.ReadStream('ImageData', ms) then
+    begin
+      ms.Position := 0;
+      LoadPNGFromStream(ms);
+    end;
+  finally
+    ms.Free;
+  end;
+end;
+
+{ TFPReportImages }
+
+function TFPReportImages.GetImg(AIndex: Integer): TFPReportImageItem;
+begin
+  Result := Items[AIndex] as TFPReportImageItem;
+end;
+
+function TFPReportImages.GetReportOwner: TFPCustomReport;
+begin
+  Result:=Owner as TFPCustomReport;
+end;
+
+
+constructor TFPReportImages.Create(AOwner: TFPCustomReport; AItemClass: TCollectionItemClass);
+begin
+  inherited Create(aOwner,AItemClass);
+end;
+
+function TFPReportImages.AddImageItem: TFPReportImageItem;
+begin
+  Result := Add as TFPReportImageItem;
+end;
+
+function TFPReportImages.AddFromStream(const AStream: TStream;
+    Handler: TFPCustomImageReaderClass; KeepImage: Boolean): Integer;
+var
+  I: TFPCustomImage;
+  IP: TFPReportImageItem;
+  Reader: TFPCustomImageReader;
+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;
+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;
+  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
+  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;
+
+function TFPReportImages.AddFromData(const AImageData: Pointer; const AImageDataSize: LongWord): integer;
+var
+  s: TMemoryStream;
+begin
+  s := TMemoryStream.Create;
+  try
+    s.Write(AImageData^, AImageDataSize);
+    s.Position := 0;
+    Result := AddFromStream(s, TFPReaderPNG, True);
+  finally
+    s.Free;
+  end;
+end;
+
+function TFPReportImages.GetIndexFromID(const AID: integer): integer;
+var
+  i: integer;
+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;
+  end;
+end;
+
+function TFPReportImages.GetImageFromID(const AID: integer): TFPCustomImage;
+
+Var
+  II : TFPReportImageItem;
+
+begin
+  II:=GetImageItemFromID(AID);
+  if II<>Nil then
+    Result:=II.Image
+  else
+    Result:=Nil;
+end;
+
+function TFPReportImages.GetImageItemFromID(const AID: integer): TFPReportImageItem;
+
+Var
+  I : Integer;
+begin
+  I:=GetIndexFromID(AID);
+  if I<>-1 then
+    Result:=Images[I]
+  else
+    Result:=Nil;
+end;
+
+{ TFPReportPageSize }
+
+procedure TFPReportPageSize.SetHeight(const AValue: TFPReportUnits);
+begin
+  if FHeight = AValue then
+    Exit;
+  FHeight := AValue;
+  Changed;
+end;
+
+procedure TFPReportPageSize.CheckPaperSize;
+var
+  i: integer;
+begin
+  I := PaperManager.IndexOfPaper(FPaperName);
+  if (I <> -1) then
+  begin
+    FWidth := PaperManager.PaperWidth[I];
+    FHeight := PaperManager.PaperHeight[I];
+    Changed;
+  end;
+end;
+
+procedure TFPReportPageSize.SetPaperName(const AValue: string);
+begin
+  if FPaperName = AValue then
+    Exit;
+  FPaperName := AValue;
+  if (FPaperName <> '') then
+    CheckPaperSize;
+end;
+
+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
+  S: TFPReportPageSize;
+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);
+end;
+
+{ TFPReportExporter }
+
+procedure TFPReportExporter.SetFPReport(AValue: TFPCustomReport);
+begin
+  if FPReport = AValue then
+    Exit;
+  if Assigned(FPReport) then
+    FPReport.RemoveFreeNotification(Self);
+  FPReport := AValue;
+  if Assigned(FPReport) then
+    FPReport.FreeNotification(Self);
+end;
+
+procedure TFPReportExporter.SetBaseFileName(AValue: string);
+begin
+  if FBaseFileName=AValue then Exit;
+  FBaseFileName:=AValue;
+end;
+
+procedure TFPReportExporter.Notification(AComponent: TComponent;
+  Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if (Operation=opRemove) and (AComponent=FPReport) then
+    FPReport:=Nil;
+end;
+
+procedure TFPReportExporter.RenderImage(aPos: TFPReportRect; var AImage: TFPCustomImage);
+begin
+  // Do nothing
+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);
+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;
+
+procedure TFPReportExporter.RenderUnknownElement(aBasePos: TFPReportPoint;
+  AElement: TFPReportElement; ADPI: Integer);
+
+Var
+  C : TFPReportElementExporterCallBack;
+  IC : TFPReportImageRenderCallBack;
+  Img : TFPCustomImage;
+  H,W : Integer;
+  R : TFPReportRect;
+
+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;
+end;
+
+
+class function TFPReportExporter.DefaultConfig: TFPReportExporterConfigHandler;
+begin
+  Result:=Nil;
+end;
+
+procedure TFPReportExporter.Execute;
+begin
+  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;
+
+class procedure TFPReportExporter.UnRegisterExporter;
+begin
+  ReportExportManager.UnRegisterExport(Self);
+end;
+
+class function TFPReportExporter.Description: String;
+begin
+  Result:='';
+end;
+
+class function TFPReportExporter.Name: String;
+begin
+  Result:=ClassName;
+end;
+
+class function TFPReportExporter.DefaultExtension: String;
+begin
+  Result:='';
+end;
+
+function TFPReportExporter.ShowConfig: Boolean;
+begin
+  Result:=ReportExportManager.ConfigExporter(Self);
+end;
+
+{ TFPReportPaperSize }
+
+constructor TFPReportPaperSize.Create(const AWidth, AHeight: TFPReportUnits);
+begin
+  FWidth := AWidth;
+  FHeight := AHeight;
+end;
+
+{ TFPReportFont }
+
+procedure TFPReportFont.SetFontName(const avalue: string);
+begin
+  FFontName := AValue;
+end;
+
+procedure TFPReportFont.SetFontSize(const avalue: integer);
+begin
+  FFontSize := AValue;
+end;
+
+procedure TFPReportFont.SetFontColor(const avalue: TFPReportColor);
+begin
+  FFontColor := AValue;
+end;
+
+constructor TFPReportFont.Create;
+begin
+  inherited Create;
+  FFontName := cDefaultFont;
+  FFontColor := clBlack;
+  FFontSize := 10;
+end;
+
+procedure TFPReportFont.Assign(Source: TPersistent);
+var
+  o: TFPReportFont;
+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;
+end;
+
+{ TFPReportPaperManager }
+
+function TFPReportPaperManager.GetPaperHeight(AIndex: integer): TFPReportUnits;
+begin
+  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Height;
+end;
+
+function TFPReportPaperManager.GetPaperHeightByName(AName: string): TFPReportUnits;
+begin
+  Result := GetPaperByName(AName).Height;
+end;
+
+function TFPReportPaperManager.GetPaperCount: integer;
+begin
+  Result := FPaperSizes.Count;
+end;
+
+function TFPReportPaperManager.GetPaperName(AIndex: integer): string;
+begin
+  Result := FPaperSizes[AIndex];
+end;
+
+function TFPReportPaperManager.GetPaperWidth(AIndex: integer): TFPReportUnits;
+begin
+  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Width;
+end;
+
+function TFPReportPaperManager.GetPaperWidthByName(AName: string): TFPReportUnits;
+begin
+  Result := GetPaperByName(AName).Width;
+end;
+
+function TFPReportPaperManager.FindPaper(const AName: string): TFPReportPaperSize;
+var
+  I: integer;
+begin
+  I := IndexOfPaper(AName);
+  if (I = -1) then
+    Result := nil
+  else
+    Result := TFPReportPaperSize(FPaperSizes.Objects[i]);
+end;
+
+function TFPReportPaperManager.GetPaperByname(const AName: string): TFPReportPaperSize;
+begin
+  Result := FindPaper(AName);
+  if Result = nil then
+    ReportError(SErrUnknownPaper, [AName]);
+end;
+
+constructor TFPReportPaperManager.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FPaperSizes := TStringList.Create;
+  FPaperSizes.Sorted := True;
+end;
+
+destructor TFPReportPaperManager.Destroy;
+var
+  I: integer;
+begin
+  if Assigned(FPaperSizes) then
+  begin
+    for I := 0 to FPaperSizes.Count - 1 do
+      FPaperSizes.Objects[i].Free;
+    FreeAndNil(FPaperSizes);
+  end;
+  inherited Destroy;
+end;
+
+procedure TFPReportPaperManager.Clear;
+var
+  i: integer;
+begin
+  for i := 0 to FPaperSizes.Count-1 do
+    if Assigned(FPaperSizes.Objects[i]) then
+      FPaperSizes.Objects[i].Free;
+  FPaperSizes.Clear;
+end;
+
+function TFPReportPaperManager.IndexOfPaper(const AName: string): integer;
+begin
+  if not Assigned(FPaperSizes) then
+    Result := -1
+  else
+    Result := FPaperSizes.IndexOf(AName);
+end;
+
+procedure TFPReportPaperManager.RegisterPaper(const AName: string; const AWidth, AHeight: TFPReportUnits);
+var
+  I: integer;
+  S: TFPReportPaperSize;
+begin
+  I := FPaperSizes.IndexOf(AName);
+  if (I = -1) then
+  begin
+    S := TFPReportPaperSize.Create(AWidth, AHeight);
+    FPaperSizes.AddObject(AName, S);
+  end
+  else
+    ReportError(SErrDuplicatePaperName, [AName]);
+end;
+
+{ Got details from Wikipedia [https://simple.wikipedia.org/wiki/Paper_size] }
+procedure TFPReportPaperManager.RegisterStandardSizes;
+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);
+end;
+
+procedure TFPReportPaperManager.GetRegisteredSizes(var AList: TStringList);
+var
+  i: integer;
+begin
+  if not Assigned(AList) then
+    Exit;
+  AList.Clear;
+  for i := 0 to FPaperSizes.Count - 1 do
+    AList.Add(PaperNames[i]);
+end;
+
+procedure DoneReporting;
+begin
+  if Assigned(uPaperManager) then
+    FreeAndNil(uPaperManager);
+  TFPReportCustomCheckbox.ImgFalse.Free;
+  TFPReportCustomCheckbox.ImgTrue.Free;
+end;
+
+{ TFPTextBlockList }
+
+function TFPTextBlockList.GetItem(AIndex: Integer): TFPTextBlock;
+begin
+  Result := TFPTextBlock(inherited GetItem(AIndex));
+end;
+
+procedure TFPTextBlockList.SetItem(AIndex: Integer; AObject: TFPTextBlock);
+begin
+  inherited SetItem(AIndex, AObject);
+end;
+
+{ TFPReportDataField }
+
+function TFPReportDataField.GetValue: variant;
+begin
+  Result := Null;
+  if Assigned(Collection) then
+    TFPReportDatafields(Collection).ReportData.DoGetValue(FieldName, Result);
+end;
+
+procedure TFPReportDataField.Assign(Source: TPersistent);
+var
+  F: TFPReportDataField;
+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);
+end;
+
+{ TFPReportDataFields }
+
+function TFPReportDataFields.GetF(AIndex: integer): TFPReportDataField;
+begin
+  Result := TFPReportDataField(Items[AIndex]);
+end;
+
+procedure TFPReportDataFields.SetF(AIndex: integer; const AValue: TFPReportDataField);
+begin
+  Items[AIndex] := AValue;
+end;
+
+function TFPReportDataFields.AddField(AFieldName: string; AFieldKind: TFPReportFieldKind): TFPReportDataField;
+begin
+  Result := Add as TFPReportDataField;
+  try
+    Result.FieldName := AFieldName;
+    Result.FieldKind := AFieldKind;
+  except
+    Result.Free;
+    raise;
+  end;
+end;
+
+function TFPReportDataFields.IndexOfField(const AFieldName: string): integer;
+begin
+  Result := Count - 1;
+  while (Result >= 0) and (CompareText(AFieldName, GetF(Result).FieldName) <> 0) do
+    Dec(Result);
+end;
+
+function TFPReportDataFields.FindField(const AFieldName: string): TFPReportDataField;
+var
+  I: integer;
+begin
+  I := IndexOfField(AFieldName);
+  if (I = -1) then
+    Result := nil
+  else
+    Result := GetF(I);
+end;
+
+function TFPReportDataFields.FindField(const AFieldName: string; const AFieldKind: TFPReportFieldKind): TFPReportDataField;
+var
+  lIndex: integer;
+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);
+end;
+
+function TFPReportDataFields.FieldByName(const AFieldName: string): TFPReportDataField;
+begin
+  Result := FindField(AFieldName);
+  if (Result = nil) then
+  begin
+    if Assigned(ReportData) then
+      ReportError(SErrUnknownField, [ReportData.Name, AFieldName])
+    else
+      ReportError(SErrUnknownField, ['', AFieldName]);
+  end;
+end;
+
+{ TFPReportData }
+
+procedure TFPReportData.SetDataFields(const AValue: TFPReportDataFields);
+begin
+  if (FDataFields = AValue) then
+    Exit;
+  FDataFields.Assign(AValue);
+end;
+
+function TFPReportData.GetFieldCount: integer;
+begin
+  Result := FDatafields.Count;
+end;
+
+function TFPReportData.GetFieldName(Index: integer): string;
+begin
+  Result := FDatafields[Index].FieldName;
+end;
+
+function TFPReportData.GetFieldType(AFieldName: string): TFPReportFieldKind;
+begin
+  Result := FDatafields.FieldByName(AFieldName).FieldKind;
+end;
+
+function TFPReportData.GetFieldValue(AFieldName: string): variant;
+begin
+  Result := varNull;
+  DoGetValue(AFieldName, Result);
+end;
+
+function TFPReportData.GetFieldWidth(AFieldName: string): integer;
+begin
+  Result := FDataFields.FieldByName(AFieldName).DisplayWidth;
+end;
+
+function TFPReportData.CreateDataFields: TFPReportDataFields;
+begin
+  Result := TFPReportDataFields.Create(TFPReportDataField);
+end;
+
+procedure TFPReportData.DoGetValue(const AFieldName: string; var AValue: variant);
+begin
+  AValue := Null;
+end;
+
+procedure TFPReportData.DoInitDataFields;
+begin
+  // Do nothing.
+end;
+
+procedure TFPReportData.DoOpen;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportData.DoFirst;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportData.DoNext;
+begin
+  // Do nothing
+end;
+
+procedure TFPReportData.DoClose;
+begin
+  // Do nothing
+end;
+
+function TFPReportData.DoEOF: boolean;
+begin
+  Result := False;
+end;
+
+constructor TFPReportData.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDatafields := CreateDataFields;
+  FDatafields.FReportData := Self;
+end;
+
+destructor TFPReportData.Destroy;
+begin
+  FreeAndNil(FDatafields);
+  inherited Destroy;
+end;
+
+procedure TFPReportData.InitFieldDefs;
+begin
+  if FIsOpened then
+    ReportError(SErrInitFieldsNotAllowedAfterOpen);
+  DoInitDataFields;
+end;
+
+procedure TFPReportData.Open;
+begin
+  if Assigned(FOnOpen) then
+    FOnOpen(Self);
+  DoOpen;
+  InitFieldDefs;
+  FIsOpened := True;
+  FRecNo := 1;
+end;
+
+procedure TFPReportData.First;
+begin
+  if Assigned(FOnFirst) then
+    FOnFirst(Self);
+  DoFirst;
+  FRecNo := 1;
+end;
+
+procedure TFPReportData.Next;
+begin
+  Inc(FRecNo);
+  if Assigned(FOnNext) then
+    FOnNext(Self);
+  DoNext;
+end;
+
+procedure TFPReportData.Close;
+begin
+  if Assigned(FOnClose) then
+    FOnClose(Self);
+  DoClose;
+  FIsOpened := False;
+  FRecNo := -1;
+end;
+
+function TFPReportData.EOF: boolean;
+begin
+  Result := False;
+  if Assigned(FOnGetEOF) then
+    FOnGetEOF(Self, Result);
+  if not Result then
+    Result := DoEOF;
+end;
+
+procedure TFPReportData.GetFieldList(List: TStrings);
+var
+  I: integer;
+begin
+  List.BeginUpdate;
+  try
+    List.Clear;
+    for I := 0 to FDataFields.Count - 1 do
+      List.add(FDataFields[I].FieldName);
+  finally
+    List.EndUpdate;
+  end;
+end;
+
+function TFPReportData.IndexOfField(const AFieldName: string): Integer;
+begin
+  Result:=  FDataFields.IndexOfField(AFieldName);
+end;
+
+function TFPReportData.HasField(const AFieldName: string): boolean;
+begin
+  Result := FDataFields.IndexOfField(AFieldName) <> -1;
+end;
+
+
+{ TFPReportClassMapping }
+
+function TFPReportClassMapping.IndexOfExportRenderer(
+  AClass: TFPReportExporterClass): Integer;
+begin
+  Result:=Length(FRenderers)-1;
+  While (Result>=0) and (FRenderers[Result].aClass<>AClass) do
+    Dec(Result);
+end;
+
+constructor TFPReportClassMapping.Create(const AMappingName: string; AElementClass: TFPReportElementClass);
+begin
+  FMappingName :=  AMappingName;
+  FReportElementClass := AElementClass;
+end;
+
+function TFPReportClassMapping.AddRenderer(aExporterClass: TFPReportExporterClass; aCallback: TFPReportElementExporterCallBack ): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
+
+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;
+end;
+
+function TFPReportClassMapping.FindRenderer(aClass: TFPReportExporterClass): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfExportRenderer(aClass);
+  if I<>-1 then
+    Result:=FRenderers[I].aCallback
+  else
+    Result:=Nil;
+end;
+
+{ TFPReportElementFactory }
+
+function TFPReportElementFactory.GetM(Aindex : integer): TFPReportClassMapping;
+begin
+  Result:=TFPReportClassMapping(FList[AIndex]);
+end;
+
+function TFPReportElementFactory.IndexOfElementName(const AElementName: string): Integer;
+
+begin
+  Result:=Flist.Count-1;
+  While (Result>=0) and not SameText(Mappings[Result].MappingName, AElementName) do
+    Dec(Result);
+end;
+
+function TFPReportElementFactory.IndexOfElementClass(const AElementClass: TFPReportElementClass): Integer;
+
+begin
+  Result:=Flist.Count-1;
+  While (Result>=0) and (Mappings[Result].ReportElementClass<>AElementClass) do
+    Dec(Result);
+end;
+
+constructor TFPReportElementFactory.Create;
+begin
+  FList := TFPObjectList.Create;
+end;
+
+destructor TFPReportElementFactory.Destroy;
+begin
+  FList.Free;
+  inherited Destroy;
+end;
+
+function TFPReportElementFactory.FindRenderer(aClass: TFPReportExporterClass;
+  AElement: TFPReportElementClass): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
+
+begin
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    Result:=Mappings[i].FindRenderer(aClass);
+end;
+
+function TFPReportElementFactory.FindImageRenderer(
+  AElement: TFPReportElementClass): TFPReportImageRenderCallBack;
+Var
+  I : Integer;
+
+begin
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    Result:=Mappings[i].ImageRenderCallback;
+end;
+
+function TFPReportElementFactory.RegisterImageRenderer(AElement: TFPReportElementClass; ARenderer: TFPReportImageRenderCallBack
+  ): TFPReportImageRenderCallBack;
+Var
+  I : Integer;
+begin
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    begin
+    Result:=Mappings[i].ImageRenderCallback;
+    Mappings[i].ImageRenderCallback:=ARenderer;
+    end;
+end;
+
+function TFPReportElementFactory.RegisterElementRenderer(AElement: TFPReportElementClass; ARenderClass: TFPReportExporterClass;
+  ARenderer: TFPReportElementExporterCallBack): TFPReportElementExporterCallBack;
+Var
+  I : Integer;
+begin
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if (I<>-1) then
+    Result:=Mappings[i].AddRenderer(aRenderClass,ARenderer);
+end;
+
+procedure TFPReportElementFactory.RegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
+begin
+  I:=IndexOfElementName(aElementName);
+  if I<>-1 then
+    Mappings[i].EditorClass:=AEditorClass
+  else
+    Raise EReportError.CreateFmt(SErrUnknownElementName,[AElementName]);
+end;
+
+procedure TFPReportElementFactory.RegisterEditorClass(AReportElementClass: TFPReportElementClass;
+  AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
+begin
+  I:=IndexOfElementClass(aReportElementClass);
+  if I<>-1 then
+    Mappings[i].EditorClass:=AEditorClass
+  else
+    if AReportElementClass<>Nil then
+      Raise EReportError.CreateFmt(SErrUnknownElementClass,[AReportElementClass.ClassName])
+    else
+      Raise EReportError.CreateFmt(SErrUnknownElementClass,['Nil']);
+end;
+
+procedure TFPReportElementFactory.UnRegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
+begin
+  I:=IndexOfElementName(aElementName);
+  if I<>-1 then
+    if Mappings[i].EditorClass=AEditorClass then
+      Mappings[i].EditorClass:=nil;
+end;
+
+procedure TFPReportElementFactory.UnRegisterEditorClass(AReportElementClass: TFPReportElementClass;
+  AEditorClass: TFPReportElementEditorClass);
+Var
+  I : integer;
+
+begin
+  I:=IndexOfElementClass(aReportElementClass);
+  if I<>-1 then
+    if Mappings[i].EditorClass=AEditorClass then
+      Mappings[i].EditorClass:=nil;
+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;
+
+function TFPReportElementFactory.CreateInstance(const AElementName: string; AOwner: TComponent): TFPReportElement;
+var
+  i: integer;
+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]);
+end;
+
+function TFPReportElementFactory.FindEditorClassForInstance(AInstance: TFPReportElement): TFPReportElementEditorClass;
+begin
+  if AInstance<>Nil then
+    Result:=FindEditorClassForInstance(TFPReportElementClass(Ainstance.ClassType))
+  else
+    Result:=Nil;
+end;
+
+function TFPReportElementFactory.FindEditorClassForInstance(AClass: TFPReportElementClass): TFPReportElementEditorClass;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfElementClass(AClass);
+  if I<>-1 then
+    Result:=Mappings[I].EditorClass
+  else
+    Result:=nil;
+end;
+
+procedure TFPReportElementFactory.AssignReportElementTypes(AStrings: TStrings);
+var
+  i: integer;
+begin
+  AStrings.Clear;
+  for i := 0 to FList.Count - 1 do
+    AStrings.Add(Mappings[I].MappingName);
+end;
+
+{ TFPReportCustomDataHeaderBand }
+
+function TFPReportCustomDataHeaderBand.GetReportBandName: string;
+begin
+  Result := 'DataHeaderBand';
+end;
+
+class function TFPReportCustomDataHeaderBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btDataHeader;
+end;
+
+{ TFPReportCustomDataFooterBand }
+
+function TFPReportCustomDataFooterBand.GetReportBandName: string;
+begin
+  Result := 'DataFooterBand';
+end;
+
+class function TFPReportCustomDataFooterBand.ReportBandType: TFPReportBandType;
+begin
+  Result:=btDataFooter;
+end;
+
+{ A function borrowed from fpGUI Toolkit. }
+function fpgDarker(const AColor: TFPReportColor; APercent: Byte): TFPReportColor;
+
+  function GetRed(const c: TFPReportColor): Byte; inline;
+  begin
+    Result := (c shr 16) and $FF;
+  end;
+
+  function GetGreen(const c: TFPReportColor): Byte; inline;
+  begin
+    Result := (c shr 8) and $FF;
+  end;
+
+  function GetBlue(const c: TFPReportColor): Byte; inline;
+  begin
+    Result := c and $FF;
+  end;
+
+var
+  r, g, b: Byte;
+begin
+  r := GetRed(AColor);
+  g := GetGreen(AColor);
+  b := GetBlue(AColor);
+
+  r := Round(r*APercent/100);
+  g := Round(g*APercent/100);
+  b := Round(b*APercent/100);
+
+  Result := b or (g shl 8) or (r shl 16);
+end;
+
+
+{ Defines colors that can be used by a report designer or demos. }
+procedure SetupBandRectColors;
+var
+  i: TFPReportBandType;
+begin
+  for i := Low(TFPReportBandType) to High(TFPReportBandType) do
+    DefaultBandRectangleColors[i] := fpgDarker(DefaultBandColors[i], 70);
+end;
+
+
+initialization
+  uElementFactory := nil;
+  gElementFactory.RegisterClass('ReportTitleBand', TFPReportTitleBand);
+  gElementFactory.RegisterClass('ReportSummaryBand', TFPReportSummaryBand);
+  gElementFactory.RegisterClass('GroupHeaderBand', TFPReportGroupHeaderBand);
+  gElementFactory.RegisterClass('GroupFooterBand', TFPReportGroupFooterBand);
+  gElementFactory.RegisterClass('DataBand', TFPReportDataBand);
+  gElementFactory.RegisterClass('ChildBand', TFPReportChildBand);
+  gElementFactory.RegisterClass('PageHeaderBand', TFPReportPageHeaderBand);
+  gElementFactory.RegisterClass('PageFooterBand', TFPReportPageFooterBand);
+  gElementFactory.RegisterClass('DataHeaderBand', TFPReportDataHeaderBand);
+  gElementFactory.RegisterClass('DataFooterBand', TFPReportDataFooterBand);
+  gElementFactory.RegisterClass('ColumnHeaderBand', TFPReportColumnHeaderBand);
+  gElementFactory.RegisterClass('ColumnFooterBand', TFPReportColumnFooterBand);
+  gElementFactory.RegisterClass('Memo', TFPReportMemo);
+  gElementFactory.RegisterClass('Image', TFPReportImage);
+  gElementFactory.RegisterClass('Checkbox', TFPReportCheckbox);
+  gElementFactory.RegisterClass('Shape', TFPReportShape);
+  SetupBandRectColors;
+
+finalization
+  DoneReporting;
+  uElementFactory.Free;
+  EM.Free;
+
+end.

+ 360 - 0
packages/fcl-report/src/fpreportcanvashelper.pp

@@ -0,0 +1,360 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team
+
+    Canvas helper class for fpReport. Used by HTML and FPImage exporters.[B
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportcanvashelper;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpimage, fpreport, fpcanvas;
+
+Type
+
+  { TFPReportCanvasHelper }
+
+  TFPReportCanvasHelper = Class(TObject)
+  private
+    FCanvas: TFPCustomCanvas;
+    FDPI: Integer;
+    procedure RenderShapeCircle(const lpt1: TFPReportPoint; const ALayout: TFPReportLayout);
+    procedure RenderShapeEllipse(const lpt1: TFPReportPoint; const ALayout: TFPReportLayout);
+    procedure RenderShapeLine(lpt1: TFPReportPoint; const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+    procedure RenderShapeRect(const lpt1: TFPReportPoint; const ALayout: TFPReportLayout);
+    procedure RenderShapeTriangle(Alpt: TFPReportPoint; const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+  public
+    constructor Create(aCanvas : TFPCustomCanvas; ADPI : Integer);
+    procedure RenderImage(aRect: TFPReportRect; var AImage: TFPCustomImage); overload;
+    Procedure RenderShape(const lpt1: TFPReportPoint; const AShape: TFPReportCustomShape);
+    procedure RenderImage(const lpt1: TFPReportPoint; const AImage: TFPReportCustomImage);overload;
+    procedure RenderCheckbox(const lPt: TFPReportPoint; const ACheckbox: TFPReportCustomCheckbox);
+    function CoordToPoint(const APos: TFPReportPoint; const AHOffset: TFPReportUnits = 0; const AVOffset: TFPReportUnits = 0): TPoint;
+    function CoordToRect(const APos: TFPReportPoint; const AWidth: TFPReportUnits = 0; const AHeight: TFPReportUnits = 0): TRect;
+    function mmToPixels(const AValue: TFPReportUnits): Integer;
+    class function ColorToRGBTriple(const AColor: UInt32): TFPColor;
+    property Canvas : TFPCustomCanvas Read FCanvas Write FCanvas;
+    property DPI : Integer Read FDPI Write FDPI;
+  end;
+
+const
+  cInchToMM = 25.4;
+
+implementation
+
+uses math;
+
+Type
+  TReportImageFriend = class(TFPReportCustomImage);
+
+
+function GetColorComponent(Var AColor: UInt32): Word;
+begin
+  Result:=AColor and $FF;
+  Result:=Result or (Result shl 8);
+  AColor:=AColor shr 8;
+end;
+
+function TFPReportCanvasHelper.mmToPixels(const AValue: TFPReportUnits): Integer;
+begin
+  Result := Round(AValue * (DPI / cInchToMM));
+end;
+
+function TFPReportCanvasHelper.CoordToRect(const APos: TFPReportPoint;
+  const AWidth: TFPReportUnits = 0; const AHeight: TFPReportUnits = 0): TRect;
+
+begin
+  Result.Left:=mmToPixels(APos.Left);
+  Result.Top:=mmToPixels(APos.Top);
+  Result.Right:=mmToPixels(APos.Left+AWidth);
+  Result.Bottom:=mmToPixels(APos.Top+AHeight);
+end;
+
+function TFPReportCanvasHelper.CoordToPoint(const APos: TFPReportPoint;
+  const AHOffset: TFPReportUnits; const AVOffset: TFPReportUnits): TPoint;
+
+begin
+  Result.X:=mmToPixels(APos.Left+AHOffset);
+  Result.Y:=mmToPixels(APos.Top+AVOffset);
+end;
+
+class function TFPReportCanvasHelper.ColorToRGBTriple(const AColor: UInt32): TFPColor;
+
+Var
+  C : UInt32;
+
+begin
+  C:=AColor;
+  with Result do
+    begin
+    Blue  := GetColorComponent(C);
+    Green := GetColorComponent(C);
+    Red   := GetColorComponent(C);
+    Alpha := GetColorComponent(C);
+    end
+end;
+
+constructor TFPReportCanvasHelper.Create(aCanvas: TFPCustomCanvas; ADPI: Integer
+  );
+begin
+  FCanvas:=ACanvas;
+  FDPI:=ADPI;
+end;
+
+procedure TFPReportCanvasHelper.RenderImage(aRect: TFPReportRect;
+  var AImage: TFPCustomImage);
+Var
+  lpt : TFPReportPoint;
+  pt : TPoint;
+
+begin
+  lPt.Left := aRect.Left;
+  lPt.Top := aRect.Top;
+  PT:=CoordToPoint(Lpt,0,0);
+//  Canvas.StretchDraw(pt.X,pT.Y,mmToPixels(arect.Width), mmToPixels(arect.Height),AImage);
+  Canvas.Draw(pt.X,pT.Y,AImage);
+end;
+
+procedure TFPReportCanvasHelper.RenderShape(const lpt1: TFPReportPoint; const AShape: TFPReportCustomShape);
+
+begin
+  Canvas.Pen.FPColor:=ColorToRGBTriple(TFPReportShape(AShape).Color);
+  Canvas.Pen.Style:=psSolid;
+  Canvas.Pen.Width:=1;
+  case TFPReportShape(AShape).ShapeType of
+    stEllipse: RenderShapeEllipse(lpt1,AShape.RTLayout);
+    stCircle: RenderShapeCircle(lpt1,AShape.RTLayout);
+    stLine: RenderShapeLine(lpt1,TFPReportShape(AShape).Orientation, AShape.RTLayout);
+    stSquare: RenderShapeRect(lpt1,AShape.RTLayout);
+    stTriangle: RenderShapeTriangle(lpt1,TFPReportShape(AShape).Orientation, AShape.RTLayout);
+  end;
+end;
+
+procedure TFPReportCanvasHelper.RenderCheckbox(const lPt: TFPReportPoint; const ACheckbox: TFPReportCustomCheckbox);
+var
+  pt : TPoint;
+  lImage: TFPCustomImage;
+
+begin
+  Pt:=CoordToPoint(lpt,0,0);
+  lImage:=ACheckBox.GetRTImage;
+  Canvas.StretchDraw(pt.X,Pt.Y,mmToPixels(ACheckBox.RTLayout.Width), mmToPixels(ACheckBox.RTLayout.Height),limage);
+end;
+
+procedure TFPReportCanvasHelper.RenderImage(const lpt1: TFPReportPoint;
+  const AImage: TFPReportCustomImage);
+Var
+  PT : TPoint;
+  img: TReportImageFriend;
+
+begin
+  img := TReportImageFriend(AImage);  { for access to Protected methods }
+  if not Assigned(img.Image) then
+    Exit; { nothing further to do }
+  PT:=CoordToPoint(Lpt1,0,0);
+  if img.Stretched then
+    Canvas.StretchDraw(pt.X,pT.Y,mmToPixels(AImage.RTLayout.Width), mmToPixels(AImage.RTLayout.Height),Img.Image)
+  else
+    Canvas.Draw(pt.X,pT.Y,Img.Image);
+end;
+
+procedure TFPReportCanvasHelper.RenderShapeCircle(const lpt1: TFPReportPoint;
+  const ALayout: TFPReportLayout);
+
+var
+  lPt2: TFPReportPoint;  // original Report point
+  R : TRect;
+  LW : TFPReportUnits;
+
+begin
+  // Keep center of circle at center of rectangle
+  lw := Min(ALayout.Width, ALayout.Height);
+  lpt2.Left:=lPt1.Left+(ALayout.Width / 2)-lW/2;
+  lpt2.Top:=lPt1.top+(ALayout.Height / 2)-lW/2;
+  R:=CoordToRect(lpt2,LW,LW);
+  Canvas.ellipse(R);
+end;
+
+procedure TFPReportCanvasHelper.RenderShapeEllipse(const lpt1: TFPReportPoint;
+  const ALayout: TFPReportLayout);
+
+Var
+  R : TRect;
+begin
+  R:=CoordToRect(lpt1,ALayout.Width,ALayout.Height);
+  Canvas.ellipse(R);
+end;
+
+procedure TFPReportCanvasHelper.RenderShapeLine(lpt1: TFPReportPoint;
+  const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+
+var
+  lPt2: TFPReportPoint;  // original Report point
+  R1,R2 : TPoint;
+
+begin
+  case AOrientation of
+  orNorth, orSouth:
+     begin                                         //   |
+     lPt1.Left := lPt1.Left + (ALayout.Width / 2); //   |
+     lPt2.Left := lPt1.Left ;                      //   |
+     lPt2.Top := LPT1.Top + ALayout.Height;        //   |
+     end;
+  orNorthEast, orSouthWest:
+     begin                                         //    /
+     lPt2.Left := lPt1.Left;                       //   /
+     lPt1.Left := lPt1.Left + ALayout.Width;       //  /
+     lPt2.Top := lPt1.Top + ALayout.Height;        // /
+     end;
+  orEast, orWest:
+     begin                                         //
+     lPt2.Left := lPt1.Left + ALayout.Width;       // ----
+     lPt1.Top := lPt1.Top + (ALayout.Height / 2);  //
+     lPt2.Top := lPt1.Top;                         //
+     end;
+  orSouthEast, orNorthWest:
+     begin                                         // \
+     lPt2.Left := lPt1.Left + ALayout.Width;       //  \
+     lPt2.Top := lPt1.Top + ALayout.Height;        //   \
+     end;                                          //    \
+  end;
+  R1:=CoordToPoint(lpt1);
+  R2:=CoordToPoint(lpt2);
+  Canvas.line(R1,R2);
+end;
+
+procedure TFPReportCanvasHelper.RenderShapeRect(const lpt1: TFPReportPoint;
+  const ALayout: TFPReportLayout);
+
+Var
+  ldx, ldy, lw: TFPReportUnits;
+  P : TFPReportPoint;
+begin
+  lw := Min(ALayout.Width, ALayout.Height);
+  if ALayout.Width = ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := 0;
+  end
+  else if ALayout.Width > ALayout.Height then
+  begin
+    ldx := (ALayout.Width - ALayout.Height) / 2;
+    ldy := 0;
+  end
+  else if ALayout.Width < ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := (ALayout.Height - ALayout.Width) / 2;
+  end;
+  P.Left := lPt1.Left + ldx;
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  P.Top := lPt1.Top + ldy;
+  Canvas.rectangle(CoordToRect(P,lw,Lw));
+end;
+
+procedure TFPReportCanvasHelper.RenderShapeTriangle(Alpt: TFPReportPoint;
+  const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+
+
+  Procedure DrawLine(Const A,B : TFPReportPoint);
+
+  begin
+    Canvas.Line(CoordToPoint(A),CoordToPoint(B));
+  end;
+
+var
+  lpt1,lPt2,lpt3: TFPReportPoint;  // original Report points for 3 corners of triangle.
+
+begin
+  case AOrientation of
+  orNorth:
+    begin
+    lPt1.Left := ALPT.Left + (ALayout.Width / 2); //      1
+    lPt1.Top  := ALPT.Top;                        //      /\
+    lPt2.Left := ALPT.Left;                       //     /  \
+    lPt2.Top  := ALPT.Top + ALayout.Height;       //    /____\
+    lPt3.Left := ALPT.Left + ALayout.Width;       //  2       3
+    lPt3.Top  := lPt2.Top;
+    end;
+  orNorthEast:
+    begin
+    lPt1.Left := ALPT.Left + (ALayout.Width );    //   +-------1
+    lPt1.Top  := ALPT.Top;                        //   |       |
+    lPt2.Left := ALPT.Left;                       //   2       |
+    lPt2.Top  := ALPT.Top + ALayout.Height/2;     //   |       |
+    lPt3.Left := ALPT.Left + ALayout.Width/2;     //   +---3---+
+    lPt3.Top  := lPt1.Top + aLayout.height;
+    end;
+  orSouth:
+    begin
+    lPt1.Left := ALPT.Left;                        //  1 ------ 2
+    lPt1.Top  := ALPT.Top;                         //    \    /
+    lPt2.Left := ALPT.Left+ ALayout.Width;         //     \  /
+    lPt2.Top  := ALPT.Top;                         //      \/
+    lPt3.Left := ALPT.Left + (ALayout.Width / 2);  //      3
+    lPt3.Top  := ALPT.Top+ALayout.Height;
+    end;
+  orSouthEast:
+    begin
+    lPt1.Left := ALPT.Left + (ALayout.Width/2);   //   +---1---+
+    lPt1.Top  := ALPT.Top;                        //   |       |
+    lPt2.Left := ALPT.Left;                       //   2       |
+    lPt2.Top  := ALPT.Top + ALayout.Height/2;     //   |       |
+    lPt3.Left := ALPT.Left + ALayout.Width;       //   +-------3
+    lPt3.Top  := lPt1.Top + aLayout.height;
+    end;
+  orEast:
+    begin
+    lPt1.Left := ALPT.Left;                       //   1
+    lPt1.Top  := Alpt.Top ;                       //   |\
+    lPt2.Left := ALPT.Left + ALayout.Width;       //   | \ 2
+    lPt2.Top  := ALPT.Top + (ALayout.Height / 2); //   | /
+    lPt3.Left := ALPT.Left;                       //   |/
+    lPt3.Top  := Alpt.Top + ALayout.Height;       //   3
+    end;
+  orNorthWest:
+    begin
+    lPt1.Left := ALPT.Left;                       //   1-------+
+    lPt1.Top  := ALPT.Top;                        //   |       |
+    lPt2.Left := ALPT.Left+ALayout.width;         //   |       2
+    lPt2.Top  := ALPT.Top + ALayout.Height/2;     //   |       |
+    lPt3.Left := ALPT.Left + ALayout.Width/2;     //   +---3---+
+    lPt3.Top  := lPt1.Top + aLayout.height;
+    end;
+  orWest:
+    begin
+    lPt1.Left := ALPT.Left + ALayout.Width;      //       1
+    lPt1.Top  := ALPT.Top;                       //      /|
+    lPt2.Left := ALPT.Left;                      //   2 / |
+    lPt2.Top  := ALPT.Top + ALayout.Height / 2;  //     \ |
+    lPt3.Left := ALPT.Left + ALayout.Width;      //      \|
+    lPt3.Top  := ALPT.Top+ ALayout.Height;       //       3
+    end;
+  orSouthWest:
+    begin
+    lPt1.Left := ALPT.Left+ ALayout.Height/2;     //   +---1---+
+    lPt1.Top  := ALPT.Top;                        //   |       |
+    lPt2.Left := ALPT.Left+ALayout.width;         //   |       2
+    lPt2.Top  := ALPT.Top + ALayout.Height/2;     //   |       |
+    lPt3.Left := ALPT.Left ;                      //   3-------+
+    lPt3.Top  := lPt1.Top + aLayout.height;
+    end;
+  end;
+  DrawLine(lpt1,lpt2);
+  DrawLine(lpt2,lpt3);
+  DrawLine(lpt3,lpt1);
+end;
+
+end.
+

+ 280 - 0
packages/fcl-report/src/fpreportcheckbox.inc

@@ -0,0 +1,280 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 the Free Pascal development team
+
+    FPReport default Checkbox image data.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+const
+  fpreport_checkbox_false: array[0..337] of byte = (
+     137, 80, 78, 71, 13, 10, 26, 10,  0,  0,  0, 13, 73, 72, 68, 82,  0,
+       0,  0,119,  0,  0,  0,118,  8,  6,  0,  0,  0,242,101, 12, 47,  0,
+       0,  0,  6, 98, 75, 71, 68,  0,112,  0,112,  0,112,  4,101, 66,220,
+       0,  0,  0,  9,112, 72, 89,115,  0,  0, 46, 35,  0,  0, 46, 35,  1,
+     120,165, 63,118,  0,  0,  0,  7,116, 73, 77, 69,  7,224,  2, 12, 12,
+      28, 23,134, 56,124,  4,  0,  0,  0,223, 73, 68, 65, 84,120,218,237,
+     211,177, 17,128, 48, 12,  4, 65,204, 80,240,171, 18,149,108, 90,112,
+     102,195,236,197,138,126, 71, 35,201,188,244,203,110, 19,192, 21, 92,
+     193, 21, 92,193, 21, 92,184,130,171, 47,244,172, 30,118,183,181, 14,
+     168,170,124,174,224,194, 21, 92,193, 21, 92,193, 21, 92,184,130, 43,
+     184,130, 43,184,130, 43,184,112,  5, 87,112,  5, 87,112,  5, 23,174,
+     224, 10,174,224, 10,174,224, 10, 46, 92,193, 21, 92,193, 21, 92,193,
+      21, 92,184,130, 43,184,130, 43,184,130, 11, 87,112,  5, 87,112,  5,
+      87,112,  5, 23,174,224, 10,174,224, 10,174,224,194, 53,  1, 92,193,
+      21, 92,193, 21, 92,193,133, 43,184,130, 43,184,130, 43,184,130, 11,
+      87,112,  5, 87,112,  5, 87,112,225, 10,174,224, 10,174,224, 10,174,
+     224,194, 21, 92,193, 21, 92,193, 21, 92,193,133, 43,184,130, 43,184,
+     130, 43,184,112,  5, 87,112,181,187,145,100,154,193,231, 10,174,224,
+      10,174,224, 10, 46, 92,193, 21, 92,109,235,  5,184,193,  8,139,170,
+      70,170, 29,  0,  0,  0,  0, 73, 69, 78, 68,174, 66, 96,130);
+
+
+const
+  fpreport_checkbox_true: array[0..4051] of byte = (
+     137, 80, 78, 71, 13, 10, 26, 10,  0,  0,  0, 13, 73, 72, 68, 82,  0,
+       0,  0,118,  0,  0,  0,118,  8,  6,  0,  0,  0, 29,167,103, 17,  0,
+       0,  0,  6, 98, 75, 71, 68,  0,112,  0,112,  0,112,  4,101, 66,220,
+       0,  0,  0,  9,112, 72, 89,115,  0,  0, 46, 35,  0,  0, 46, 35,  1,
+     120,165, 63,118,  0,  0,  0,  7,116, 73, 77, 69,  7,224,  2, 12, 12,
+      28, 41, 71, 89, 97,175,  0,  0, 15, 97, 73, 68, 65, 84,120,218,237,
+     157,249, 82, 34,201, 26,197, 15, 72,177, 20,197, 34,171,172,110,236,
+     160,246,188, 89,207,147,204,147,245, 56,184, 34, 40, 42, 80,138, 40,
+      59,197, 82,108,247,143, 59,153, 23,105,208,158, 59,138,160,121, 34,
+     140,176, 91,195,134,250,117,126,203,201, 47, 83,197,247,239,223,199,
+      96,250,116, 82,178, 71,192,192, 50, 49,176, 76, 12, 44, 19,  3,203,
+     196,192, 50,176,236, 17, 48,176, 76, 12, 44,211, 71, 75,245,171,223,
+     248,199, 31,127,176,167,181,  4,250,253,247,223,217,138,101,161,152,
+     137,129,101, 98, 96,153, 24, 88, 38,  6,150,137,129,101, 96,153, 24,
+      88, 38,  6,150,137,129,101, 98, 96,153, 24, 88,  6,150,137,129,101,
+      90, 13,169,216, 35,120,123,141,199,255, 59, 92,161, 80, 40, 24,216,
+      85,135, 57, 28, 14,233,  7,129,186,182,182,  6,165, 82,137,181,181,
+     181,133, 66,102, 96,223, 64,195,225, 16,189, 94, 15,221,110, 23,237,
+     118, 27,178, 44,255, 55,207, 41,149,208,104, 52,208,235,245,208,106,
+     181, 80,169, 84, 80, 42,149, 12,236, 42,168,223,239,163,213,106,161,
+      86,171,225,225,225,  1,157, 78,  7,245,122, 29,107,107,107,224, 56,
+      14, 78,167, 19,130, 32,192,106,181, 66,167,211, 65,163,209, 44,  4,
+      46,  3,251, 47,212,235,245, 80,171,213, 80, 44, 22,145,207,231, 81,
+      40, 20,240,240,240,128, 70,163,  1,149, 74,  5,189, 94, 15,175,215,
+      11,191,223,143,209,104,  4,187,221, 78,129,191,119, 88,102, 96,255,
+      79,117,187, 93, 84, 42, 21,220,222,222, 34,149, 74, 33,157, 78, 35,
+     151,203, 65, 20, 69, 12,  6,  3,140, 70, 35,216,108, 54,228,243,121,
+      72,146, 68, 67, 51,  9,199, 42,149,138,129, 93, 86,168,217,108, 22,
+     135,135,135, 56, 63, 63,199,197,197,  5,242,249, 60,154,205, 38,100,
+      89,134, 66,161,128, 40,138,112,187,221,  0,  0,189, 94, 15,171,213,
+      10, 65, 16,160,213,106, 89, 40, 94, 54,201,178,140, 90,173, 70,161,
+     254,249,231,159, 56, 61, 61, 69,161, 80,160, 80, 73, 85,220,110,183,
+       1,  0,118,187, 29,133, 66,  1,110,183, 27,235,235,235, 24, 14,135,
+      24,143,199,239, 26,142, 25,216,127,160,193, 96,128,122,189,142, 92,
+      46,135,100, 50,137, 31, 63,126,224,232,232,  8,162, 40,162,209,104,
+     160,223,239, 99, 52, 26,209, 62, 86,150,101, 72,146,132,167,167, 39,
+      72,146,244,211,215,217,138, 93,  2,141, 70, 35,180, 90, 45,136,162,
+     136,227,227, 99, 36,147, 73,156,156,156,208,240, 75,160,205,234,111,
+     149, 74, 37,  6,131,  1, 93,201,172,221, 89, 34, 73,146,132,135,135,
+       7,164, 82, 41,252,245,215, 95, 56, 62, 62, 70, 62,159, 71,163,209,
+     192, 96, 48,248,105, 21, 18,115, 66,173, 86, 67,165, 82,193,104, 52,
+     194,100, 50, 65,173, 86, 47,196,172, 96, 94,241, 47,168,211,233,160,
+      92, 46, 35,149, 74, 33,153, 76,226,236,236, 12,185, 92,238, 25,212,
+     105,176, 74,165, 18,106,181, 26,130, 32,192,227,241,192,233,116, 66,
+     167,211,129,231,121,172,173,173,189,251,107,102, 96,127,161, 87, 45,
+     151,203,184,186,186, 66, 50,153, 68, 50,153,196,213,213, 21,106,181,
+      26,250,253,254, 92,168, 28,199, 65, 16,  4,248,124, 62,248,253,126,
+     120,189, 94, 56,157, 78,104,181,218,133,128,101,161,248, 21, 87,169,
+      86,171,225,250,250, 26, 63,126,252,160, 80, 43,149, 10,100, 89,254,
+     169, 16, 82, 40, 20, 52,  4,235,245,122,120, 60, 30,132,195, 97,132,
+      66, 33,248,124, 62,152,205,102,104, 52,154,133,120,198,108,197,206,
+     209,112, 56, 68,189, 94, 71, 62,159,167,133, 82, 58,157,166,182, 33,
+     105, 89,102,229, 85,189, 94,143,141,141, 13,132,195, 97, 68, 34, 17,
+      68, 34, 17,108,108,108, 64, 16,132,133,172, 86,  6,246,133, 10,184,
+     217,108,226,238,238, 14,201,100, 18,135,135,135, 56, 57, 57,193,221,
+     221, 29,218,237,246,204,234,150,172, 86,141, 70,  3,187,221,142, 80,
+      40,132,189,189, 61, 28, 28, 28, 96,115,115, 19,102,179, 25, 28,199,
+      45,236, 61, 48,176, 51,218,147,118,187,141,135,135,  7, 28, 31, 31,
+     227,240,240, 16,103,103,103, 40, 20, 10,144, 36,137, 66,157,149, 87,
+      53, 26, 13, 44, 22, 11,  2,129,  0,246,247,247,241,237,219, 55,  4,
+       2,  1, 88, 44, 22,104, 52,154,133,190, 15,  6,118, 70,177,244,244,
+     244,132, 84, 42,133,163,163, 35,156,156,156,224,250,250,154,246,170,
+     243,138, 37,149, 74,  5,147,201,132,205,205, 77,196,227,113,236,239,
+     239, 35, 20, 10,193, 98,177, 44,196, 66,100,197,211, 11,146,101, 25,
+     213,106, 21,217,108, 22, 71, 71, 71, 72, 38,147,200,102,179,212, 85,
+     154,134, 74,194,175, 74,165,130,193, 96,128,207,231, 67, 34,145,192,
+     193,193,  1,194,225, 48, 28, 14,  7,116, 58,221,135,188, 23,  6,118,
+      34,175, 74,146,132,251,251,123,164, 82, 41,156,159,159,227,230,230,
+       6,213,106,245,153,255, 59,175, 88,114,185, 92,136, 70,163, 56, 56,
+      56, 64, 60, 30,135,203,229,  2,207,243, 31, 54, 26,195, 66,241,223,
+     249,178,223,239,163, 82,169,224,250,250, 26,103,103,103, 72,167,211,
+      40,149, 74,232,118,187, 51,173, 66,133, 66,  1,165, 82,  9,173, 86,
+      11,167,211,137, 72, 36,130,111,223,190, 33,145, 72,192,227,241,192,
+      96, 48, 44,108, 90,130,129,125,  1,108,189, 94,135, 40,138, 72,165,
+      82,184,188,188, 68,177, 88,156,219,214, 76, 58, 75, 54,155, 13,161,
+      80,136, 22, 75,219,219,219, 48,153, 76, 11,173,128, 25,216, 87,220,
+     165,108, 54,139,116, 58,141,219,219, 91,234, 44,205,124,104,127, 23,
+      75,102,179,153, 22, 75,191,253,246, 27,118,118,118, 62,164,  2,102,
+      96,231, 24, 17,213,106, 21,249,124, 30,233,116, 26,217,108,118,174,
+     179, 52,  9,213, 96, 48,192,239,247,211,149, 26, 10,133, 96,179,217,
+      62,164,  2,102, 96,231,244,172,165, 82,  9,153, 76,  6,233,116, 26,
+     247,247,247,104,181, 90, 47, 58, 75, 58,157, 14, 46,151, 11,225,112,
+      24,123,123,123,  8,135,195,212,228, 95, 22,125,105,176,178, 44,163,
+      82,169, 32,147,201,224,226,226,  2,183,183,183,116,181,206,130, 58,
+     153, 87,  3,129,  0, 45,150,124, 62, 31,244,122,253,135, 85,192, 12,
+     236, 84,123, 67,166, 33,200,106, 21, 69, 17,237,118,123,166,185, 15,
+     128,154, 16, 91, 91, 91, 72, 36, 18,136,199,227,240,251,253, 48, 24,
+      12, 11,243,128, 25,216, 87, 66, 48,233, 89, 47, 46, 46,112,122,122,
+     138,219,219, 91,180, 90,173,153,155,230, 36,183,234,116, 58, 56, 28,
+      14,  4,  2,  1, 68,163, 81, 90, 44,125,116,  5,204,192,254,173,110,
+     183,139, 82,169,132,203,203, 75,156,158,158, 34,155,205,162, 92, 46,
+     211,158,117,150,187,164, 86,171, 97, 52, 26,225,247,251, 17,139,197,
+      16, 12,  6,151,170, 88,250,242, 96,201,148, 33, 49, 34,206,207,207,
+      33,138, 34, 45,152,102,137,184, 75,110,183, 27,145, 72,  4,129, 64,
+       0, 27, 27, 27,208,235,245, 75,251, 62,191, 20,216,225,112,  8, 73,
+     146, 80, 40, 20,112,118,118,134,227,227, 99,106, 27,206,243,130,167,
+      11,166, 96, 48,  8,191,223,143,245,245,245,165,203,171, 95, 18,236,
+     120, 60, 70,183,219, 69,177, 88,196,229,229, 37,142,142,142,144,201,
+     100, 80, 42,149,208,235,245,230,182, 55,164, 96,242,249,124,  8,  6,
+     131,216,221,221,133,195,225, 88,216, 36,  4,  3,251, 11,171,181, 86,
+     171, 33,159,207,227,228,228,  4,151,151,151,184,191,191,167,123,172,
+      47, 77, 67,184,221,110,  4,131, 65,196, 98, 49,248,253,126, 24,141,
+     198,119, 63,162,241,111,165,122,207, 21, 50, 30,143,159, 21, 35, 36,
+     180,145,130,100,145,106,181, 90,184,191,191,167,121,245,181,209,209,
+     201,141,243,157,157, 29,196, 98, 49,108,109,109,193,102,179, 45,133,
+     101,248, 33, 96, 71,163, 17,  6,131,  1,100, 89,134, 44,203,244,225,
+     145,124,165,209,104,192,113,220,194,114, 20, 41,152,174,174,174,144,
+     201,100,112,123,123,139,114,185, 60,119,227, 92,161, 80,128,227, 56,
+     152, 76, 38,236,236,236, 32, 30,143, 35, 28, 14,195,227,241, 44,157,
+      17,177, 48,176,195,225, 16,253,126, 31,146, 36,161,217,108,162,209,
+     104, 64,146, 36,140, 70, 35,112, 28,  7,158,231, 97, 50,153, 96, 52,
+      26,161,211,233,160, 86,171,223, 61,  4, 87,171, 85,122, 42,238,226,
+     226,  2,247,247,247,232,116, 58, 51,189,224,233, 41,195,104, 52,138,
+     120, 60, 78,119,109,150, 61,  4,191, 11,216,209,104,132,126,191,143,
+      70,163,129,199,199, 71, 20,139, 69, 20,139, 69, 84, 42, 21,244,122,
+      61,168,213,106,152,205,102, 56, 28, 14,184, 92, 46, 56,157, 78,152,
+      76, 38,104,181,218,119, 89,  5,227,241, 24,173, 86, 11,197, 98, 17,
+     231,231,231, 72,165, 82,244, 72,198, 75,211,251, 58,157, 14, 27, 27,
+      27,136, 68, 34,216,223,223,199,238,238,238, 82,247,172,239, 10,118,
+      60, 30, 99, 48, 24, 64,146, 36, 60, 62, 62,226,250,250, 26,233,116,
+      26, 55, 55, 55, 40,149, 74,104,183,219,224, 56, 14,102,179, 25, 46,
+     151, 11, 91, 91, 91,  8,  4,  2,240,251,253,112, 56, 28,224,121,254,
+     205, 55,166, 59,157, 14,157, 95, 58, 57, 57,193,213,213, 21,158,158,
+     158,208,235,245,230,142,185,144,215,184,181,181,133, 72, 36,130, 96,
+      48,  8,183,219, 13,189, 94,255,161, 27,231, 31, 10, 86,150,101, 52,
+     155, 77, 20,139, 69,164,211,105, 28, 30, 30, 62,123,152, 74,165, 18,
+      60,207,195,110,183, 67, 20, 69,148, 74, 37,212,106, 53,  4,131, 65,
+     184, 92, 46, 24, 12,134, 55,179,231,100, 89, 70,185, 92,166,238,210,
+     197,197,197,179, 73,195, 89,182, 33,  9,193, 94,175, 23,177, 88, 12,
+     137, 68,  2, 91, 91, 91, 48,155,205, 43, 19,130,223, 28,236,104, 52,
+     130, 44,203,104, 52, 26, 40, 22,139,184,185,185,193,213,213, 21,110,
+     110,110,208,104, 52,168,171,195,113, 28, 42,149, 10,202,229, 50,158,
+     158,158, 80,173, 86,209,108, 54, 17,139,197,224,241,120, 96, 54,155,
+     255,117,200,155, 28,246, 62, 61, 61,197,201,201,  9, 29, 74,155, 23,
+     130,201, 76,176,195,225, 64, 48, 24, 68, 40, 20, 66, 48, 24,132,197,
+      98,121,247, 58, 96,101, 66,113,165, 82, 65,169, 84, 66,185, 92, 70,
+     163,209, 64,167,211,161,  7,125,251,253, 62,100, 89, 70,167,211,161,
+     197, 85,163,209, 64,189, 94, 71, 60, 30,199,206,206, 14,236,118, 59,
+     180, 90,237,255, 21,250, 72, 94, 37, 67,105,199,199,199,184,188,188,
+      68,185, 92, 70,175,215,155,235,  5,115, 28,  7,139,197,130,205,205,
+      77,196, 98, 49, 68,163, 81,216,237,118,240, 60,191,146,125,251,155,
+     198, 23,210,183,202,178,140,110,183,251,204,209, 33, 31,  4, 46,169,
+     158,123,189, 30,218,237, 54, 26,141,  6, 90,173, 22, 36, 73, 66, 40,
+      20,130,195,225,128, 32,  8,255, 56,  4,146,107,  4, 50,153, 12, 78,
+      79, 79,145, 74,165,104, 21, 60,237,  5,147,130,109,109,109, 13,130,
+      32,208,205,115,210,218, 24,141,198,149,104,109,222, 21, 44,105,234,
+      57,142,123,214,167, 78,154, 17,147,128,137,197, 55, 28, 14, 33,203,
+      50,133, 90,171,213, 40, 92,175,215, 75,207,148,254,106,  8,110, 54,
+     155,200,229,114, 56, 63, 63,167, 87,  8, 72,146,132,193, 96, 64, 95,
+     195, 51,235,237,111, 35,194,233,116, 82,119,105,123,123, 27, 54,155,
+     109,229,242,234,187,128, 37,230,  3,207,243, 48,155,205,176, 90,173,
+      48, 24, 12,115, 39, 18,200,159,251,253, 62,109, 63,122,189, 30, 36,
+      73,130, 36, 73,104, 52, 26,144,101,153, 26,238,175,229,221,241,120,
+     140, 78,167, 67,189, 96, 50, 23, 92,171,213,232,191, 63,111, 59,206,
+     106,181, 98,123,123, 27,209,104, 20,161, 80,136, 30,119, 92,213,213,
+     250,230, 43, 86,163,209,192,100, 50,193,233,116,194,235,245,194,237,
+     118,211,  7, 59, 43,191, 77, 62,236,118,187,253, 12,110,187,221, 70,
+     175,215,131, 44,203,216,217,217,129,213,106,125, 17, 46,241,130, 11,
+     133,  2, 82,169, 20,174,175,175,241,244,244, 52,115,143,117,210, 93,
+      50, 26,141,240,122,189,136, 70,163,216,219,219,195,230,230,230, 74,
+      25, 17, 11,  1, 75, 30,148,219,237,198,238,238, 46, 42,149, 10, 36,
+      73,162,150,222,188,201, 63,242,119,189, 94, 15,149, 74,133,230,222,
+      94,175,247,172,138,125,201,167,109,181, 90,184,187,187,163,238,210,
+     221,221,221,179, 67, 84,179,140,  8,158,231,225,114,185, 16,137, 68,
+     144, 72, 36,104,  8, 94,  5, 47,120,161,197, 19,113,109, 28, 14,  7,
+      66,161, 16, 58,157, 14, 93,169,215,215,215,168,213,106,115,183,200,
+      38, 11,175,122,189,142,209,104, 68,129, 79,126,191,221,110,255, 41,
+     231, 78,158,185, 73,167,211,200,231,243,168, 86,171,175,230, 85,171,
+     213,138, 96, 48,136,120, 60,142,104, 52, 10,183,219, 13,157, 78,183,
+     210, 33,248,221,188, 98, 50, 66,226,245,122, 49, 26,141,158, 21, 84,
+     228, 52,120,183,219,157,121,119,  3,249,124, 48, 24,160,217,108, 34,
+     159,207,211, 74,154, 20,103, 74,165, 18, 86,171,149,194, 29, 14,135,
+     168, 84, 42,184,185,185,193,233,233, 41, 50,153, 12,138,197,226,220,
+      16, 76,230,130,141, 70, 35,109,109,200,164,225, 34, 15, 38,175, 28,
+      88,  0,208,104, 52, 88, 95, 95,167, 15, 81,171,213, 66, 16,  4,232,
+     116, 58,100,179, 89,148, 74, 37, 90, 48,205,203,187,164, 39, 22, 69,
+     145,246,179,228,161,147,235,236,212,106,245, 51, 47,120,242,118,180,
+     151,182,227,120,158,167, 39,206,137,193,111, 54,155, 87,210,136, 88,
+      40, 88,178,114,215,215,215,193,113, 28,244,122, 61,244,122, 61,  4,
+      65,128,209,104,164,163,158,245,122,157,230,221,233,131, 79,100,235,
+      79,146, 36,228,243,121,186,106,201,127,  4,133, 66,  1,157, 78,135,
+     106,181, 74, 91,155,108, 54,251,170, 23, 76, 54, 34, 54, 55, 55, 17,
+      14,135,177,189,189, 77, 13,145,207,164,119, 45,253, 72,200, 35,171,
+     214,108, 54,195,108, 54,195,100, 50,129,231,121,220,220,220,208,208,
+      76, 96, 78,195, 37, 91,128,185, 92,142,134, 82,242, 53,139,197,  2,
+      81, 20,145,201,100,144, 74,165, 80, 40, 20,102,206,  5, 79,123,193,
+     126,191,159,238,177,122,189, 94,  8,130,240, 41,242,234,194,192, 18,
+      16,130, 32,208, 60,203,243, 60, 12,  6,  3,120,158,135, 32,  8,184,
+     184,184,192,227,227, 35, 58,157,206, 76,184,228,136, 99,171,213,162,
+      57,151, 20, 89, 14,135,  3,143,143,143, 56, 58, 58, 66, 46,151,163,
+     115,193,211, 63,131,172, 86,178, 29, 23,  8,  4,176,187,187,139,157,
+     157, 29,152, 76,166, 79,147, 87, 23, 10,118, 50,239, 90,173, 86,104,
+      52, 26,  8,130, 64, 67,179, 74,165, 66, 38,147,193,253,253, 61,237,
+     101, 39,193, 76, 23, 84,133, 66,129, 14,124, 91, 44, 22, 52,155, 77,
+      90,148,145, 10,124, 22, 84,178, 29, 71,230,130, 35,145,200,171,189,
+      49,  3,251, 15, 67, 51,199,113,116, 68, 70,173, 86, 83,195,159,220,
+     202, 50, 61,178, 50, 13, 87, 20, 69,116,187, 93, 24,141, 70,180,219,
+     109,148,203,229, 87,183,227,  4, 65,128,219,237, 70, 40, 20, 66, 40,
+      20,130,219,237,134,193, 96,248,116, 33,248, 67,192,146,208,172,215,
+     235,225,116, 58,161, 82,169,232, 93,131, 42,149, 10,107,107,107,200,
+     231,243, 20,238, 36,212,105,184,196,204, 24, 14,135,232,116, 58, 47,
+      30,205, 32, 87,244,  4,  2,  1, 90,  5, 91,173,214,149,119,151,150,
+      10, 44, 17,153,152, 32, 45,145, 74,165,162,237,200,100,190,156,134,
+      75, 90, 33,178,121, 64,114,238,172,235,  4, 38,123, 86,175,215,139,
+     112, 56,140, 96, 48,  8,135,195,177,242, 94,240,210,130,  5,  0,173,
+      86, 11,155,205,246,172,199, 36, 15,123,178, 31,157,118,143,136,241,
+      49,185, 74,127,165,103,141,197, 98,240,249,124, 48, 26,141,159,178,
+      96, 90, 26,176,164,168,178,219,237, 20, 42, 25, 83, 29,143,199,244,
+     214,238, 89, 97,118, 52, 26,209,239,159,229,  5, 79, 22, 76, 62,159,
+      15,225,112, 24,126,191,159, 26, 27,159, 93, 75,145,100,200,214,217,
+     100,255, 73, 62, 23, 69,241,153, 75, 53,221, 10,189,228, 91,243, 60,
+      15,143,199,131, 68, 34, 65,247,119, 63,242,138,158, 47,  7,150,192,
+     181,217,108,116,181,145,213,168, 80, 40,144,207,231,231,246,168,179,
+      68,182, 16,157, 78, 39,237, 87, 63,115,207,186,212, 96, 73, 88,182,
+      88, 44, 63,133, 91,146,115, 91,173,214, 79,213,242, 44,168,147,251,
+     172,145, 72,  4,225,112,248, 83,247,172, 75, 15,150, 20, 84,  4, 46,
+      89,177,164, 63,205,229,114,180, 21,154,149, 95,167,247, 89,  3,129,
+       0,  2,129,  0, 29,109,253, 10, 33,120,105,193, 18,184, 86,171,245,
+      25,184,201,130,106,214, 12, 19,129,170,213,106,233,  8,233,222,222,
+     222,167,152, 95,250, 52, 96, 39,171,101,  2,143,180, 56, 42,149, 10,
+     162, 40, 66,146, 36,186,139, 67,138, 37,  2, 53, 20, 10, 33,145, 72,
+     208,147,231,159,101,243,252, 83,128, 37,  5, 21,129, 75, 46,247,224,
+     121, 30,231,231,231,184,187,187,163, 87,205,  2,128, 78,167,163, 19,
+      17,228,  6,210,237,237,109,172,175,175,127,185,213,186,244, 96,167,
+     171,101,242, 43, 59,157, 78, 39,221,123,109,183,219, 80, 40, 20, 48,
+      24, 12,240,120, 60,  8,  4,  2,244,204, 13, 57, 72,245,213, 86,235,
+      74,128, 37, 97,153,140,195,144,251,246, 55, 55, 55,169,249,175, 84,
+      42, 97, 48, 24,224,112, 56,224,243,249,224,241,120, 96,179,217, 86,
+     238, 32,213,151,  3, 75,224,146,  9,  8,147,201,  4,175,215,139, 70,
+     163,129, 94,175, 71,247, 90, 13,  6,  3,204,102, 51, 12,  6,  3,253,
+      69,188, 95, 85, 43,245,206,201,111,153, 34,243,203,100,226,113,242,
+     106,  1,181, 90, 13,142,227,190,236, 74, 93, 73,176,164,250, 37,147,
+     143, 58,157,110, 41,238,183, 96, 96,223, 72,147,227,168,147, 96,153,
+      86, 28,236, 52,100,166,159,197,110, 24,103, 96,153, 24, 88, 38,  6,
+     150,137,129,101, 98, 96,153, 24, 88,  6,150,137,129,101, 98, 96,153,
+     222, 71,138,239,223,191,143,217, 99, 96, 43,150,137,129,101, 98, 96,
+     153, 24, 88, 38,  6,150,129,101, 98, 96,153, 24, 88, 38,  6,150,233,
+     205,245, 31, 88, 10,133,147,255, 36, 69,160,  0,  0,  0,  0, 73, 69,
+      78, 68,174, 66, 96,130);
+

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

@@ -0,0 +1,270 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team
+
+    Report Data loop classes based on object lists in contnrs unit.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportcontnr;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpreport, contnrs;
+
+Type
+  { TFPReportObjectData }
+
+  TFPReportObjectData = class(TFPReportData)
+  private
+    FIndex : Integer;
+  protected
+    Function GetObjectCount : Integer; virtual; Abstract;
+    Function GetObject(Aindex :Integer) : TObject; virtual; Abstract;
+    Function GetObjectClass : TClass; virtual;
+    procedure DoGetValue(const AFieldName: string; var AValue: variant); override;
+    procedure DoInitDataFields; override;
+    procedure DoOpen; override;
+    procedure DoFirst; override;
+    procedure DoNext; override;
+    procedure DoClose; override;
+    function  DoEOF: boolean; override;
+  Public
+    property  DataFields;
+  end;
+
+  { TFPReportCollectionData }
+
+  TFPReportCollectionData = class(TFPReportObjectData)
+  private
+    FCollection: TCollection;
+    FOwnsCollection: Boolean;
+  Protected
+    Function GetObjectCount : Integer; override;
+    Function GetObject(Aindex :Integer) : TObject; override;
+    Function GetObjectClass : TClass; override;
+  Public
+    Destructor Destroy; override;
+    Property Collection : TCollection Read FCollection Write FCollection;
+  Published
+    Property OwnsCollection : Boolean Read FOwnsCollection Write FOwnsCollection;
+  end;
+
+
+  { TFPReportObjectListData }
+
+  TFPReportObjectListData = class(TFPReportObjectData)
+  private
+    FList : TFPObjectList;
+    FOwnsList: Boolean;
+  Protected
+    Function GetObjectCount : Integer; override;
+    Function GetObject(Aindex :Integer) : TObject; override;
+    Function GetObjectClass : TClass; override;
+  Public
+    Destructor Destroy; override;
+    Property List : TFPObjectList Read FList Write FList;
+  Published
+    Property OwnsList : Boolean Read FOwnsList Write FOwnsList;
+  end;
+
+implementation
+
+uses typinfo, variants;
+
+{ TFPReportObjectListData }
+
+function TFPReportObjectListData.GetObjectCount: Integer;
+begin
+  if Assigned(FList) then
+    Result:=FList.Count
+  else
+    Result:=0;
+end;
+
+function TFPReportObjectListData.GetObject(Aindex: Integer): TObject;
+begin
+  if Assigned(FList) then
+    Result:=FList[AIndex]
+  else
+    Result:=Nil;
+end;
+
+function TFPReportObjectListData.GetObjectClass: TClass;
+begin
+  if Assigned(FList) and (FList.Count>0) then
+    Result:=FList[0].ClassType
+  else
+    Result:=Nil;
+end;
+
+destructor TFPReportObjectListData.Destroy;
+begin
+  if FOwnsList then
+    FreeAndNil(Flist);
+  inherited Destroy;
+end;
+
+{ TFPReportCollectionData }
+
+function TFPReportCollectionData.GetObjectCount: Integer;
+begin
+  if Assigned(FCollection) then
+    Result:=FCollection.Count
+  else
+    Result:=0;
+end;
+
+function TFPReportCollectionData.GetObject(Aindex: Integer): TObject;
+begin
+  if Assigned(FCollection) then
+    Result:=FCollection.Items[AIndex]
+  else
+    Result:=Nil;
+end;
+
+function TFPReportCollectionData.GetObjectClass: TClass;
+
+begin
+  if Assigned(FCollection) then
+    if (FCollection.Count>0) then
+      Result:=FCollection.Items[0].ClassType
+    else
+      Result:=FCollection.ItemClass
+  else
+    Result:=Nil;
+end;
+
+destructor TFPReportCollectionData.Destroy;
+begin
+  if FOwnsCollection then
+    FreeAndNil(FCollection);
+  inherited Destroy;
+end;
+
+{ TFPReportObjectData }
+
+function TFPReportObjectData.GetObjectClass: TClass;
+
+Var
+  O : TObject;
+
+begin
+  O:=GetObject(0);
+  if Assigned(O) then
+    Result:=O.ClassType
+  else
+    Result:=nil;
+end;
+
+procedure TFPReportObjectData.DoGetValue(const AFieldName: string;
+  var AValue: variant);
+
+Var
+  O : TObject;
+  PI : PPropInfo;
+
+begin
+  inherited DoGetValue(AFieldName, AValue);
+  O:=GetObject(FIndex);
+  if Assigned(O) then
+    begin
+    PI:=GetPropInfo(O,AFieldName);
+    if Assigned(PI) then
+      aValue:=GetPropValue(O,PI,True);
+    end;
+end;
+
+procedure TFPReportObjectData.DoInitDataFields;
+
+
+Const
+  tkAllowed = tkProperties -
+              [tkArray,tkRecord,tkInterface,tkClass,
+               tkObject,tkDynArray,tkInterfaceRaw,tkProcVar,
+               tkHelper,tkFile,tkClassRef,tkPointer];
+
+Var
+  C : TClass;
+  PL : PPropList;
+  I,Count : Integer;
+  K : TFPReportFieldKind;
+  Tk : TTypeKind;
+
+begin
+  inherited DoInitDataFields;
+  C:=GetObjectClass;
+  if C=Nil then exit;
+  Count:=GetPropList(C,PL);
+  try
+    For I:=0 to Count-1 do
+      begin
+      TK:=PL^[i]^.PropType^.Kind;
+      if (Tk in tkAllowed) then
+        begin
+        Case TK of
+        tkInteger,tkInt64,tkQWord :
+           K:=rfkInteger;
+        tkSet,tkSString,tkLString,tkAString,
+
+        tkChar,tkEnumeration,tkWChar,tkUString,tkUChar,tkWString,tkVariant :
+           k:=rfkString;
+        tkFloat :
+           if PL^[i]^.PropType=TypeInfo(TDateTime) then
+             K:=rfkDateTime
+           else
+             K:=rfkFloat;
+       tkBool:
+           K:=rfkBoolean;
+       end;
+       Datafields.AddField(PL^[i]^.Name,K);
+       end;
+     end;
+  finally
+    FreeMem(PL);
+  end;
+end;
+
+procedure TFPReportObjectData.DoOpen;
+begin
+  inherited DoOpen;
+  FIndex:=0;
+end;
+
+procedure TFPReportObjectData.DoFirst;
+begin
+  inherited DoFirst;
+  FIndex:=0;
+end;
+
+procedure TFPReportObjectData.DoNext;
+begin
+  inherited DoNext;
+  Inc(FIndex);
+end;
+
+procedure TFPReportObjectData.DoClose;
+begin
+  FIndex:=-1;
+  inherited DoClose;
+end;
+
+function TFPReportObjectData.DoEOF: boolean;
+begin
+  Result:=inherited DoEOF;
+  Result:=Result or (FIndex<0) or (FIndex>=GetObjectCount);
+end;
+
+
+
+end.
+

+ 186 - 0
packages/fcl-report/src/fpreportdb.pp

@@ -0,0 +1,186 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team
+
+    Report Data loop classes based on TDataset.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportdb;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpreport, db;
+
+Type
+  TFPReportDatasetData = class(TFPReportData)
+  private
+    FDataSet: TDataSet;
+  protected
+    procedure DoGetValue(const AFieldName: string; var AValue: variant); override;
+    procedure DoInitDataFields; override;
+    procedure DoOpen; override;
+    procedure DoFirst; override;
+    procedure DoNext; override;
+    procedure DoClose; override;
+    function  DoEOF: boolean; override;
+  Public
+    property  DataFields;
+  published
+    property  DataSet: TDataSet read FDataSet write FDataSet;
+  end;
+
+implementation
+
+resourcestring
+  SErrNoDataSetAssigned  = 'No dataset has been assigned.';
+  SErrDatasetNotOpen = 'Dataset has not been opened yet';
+
+
+{ TFPReportDatasetData }
+
+procedure TFPReportDatasetData.DoGetValue(const AFieldName: string; var AValue: variant);
+var
+  ms: TMemoryStream;
+begin
+  inherited DoGetValue(AFieldName, AValue);
+  try
+    if FieldTypes[AFieldName] = rfkStream then
+    begin
+      ms := TMemoryStream.Create;
+      try
+        TBlobField(FDataSet.FieldByName(AFieldName)).SaveToStream(ms);
+        AValue := FPReportStreamToMIMEEncodeString(ms);
+      finally
+        ms.Free;
+      end;
+    end
+    else
+    begin
+      AValue := FDataSet.FieldByName(AFieldName).Value;
+    end;
+  except
+    on E: EDatabaseError do
+    begin
+      // no nothing - it's probably an expression, which will be handled in CustomBand.ExpandMacro()
+    end;
+  end;
+end;
+
+procedure TFPReportDatasetData.DoInitDataFields;
+var
+  i: integer;
+
+  function DatabaseKindToReportKind(const AType: TFieldType): TFPReportFieldKind;
+  begin
+    case AType of
+      ftUnknown:        Result := rfkString;
+      ftString:         Result := rfkString;
+      ftSmallint:       Result := rfkInteger;
+      ftInteger:        Result := rfkInteger;
+      ftWord:           Result := rfkInteger;
+      ftBoolean:        Result := rfkBoolean;
+      ftFloat:          Result := rfkFloat;
+      ftCurrency:       Result := rfkFloat;
+      ftBCD:            Result := rfkFloat;
+      ftDate:           Result := rfkDateTime;
+      ftTime:           Result := rfkDateTime;
+      ftDateTime:       Result := rfkDateTime;
+      ftBytes:          Result := rfkStream;
+      ftVarBytes:       Result := rfkStream;
+      ftAutoInc:        Result := rfkInteger;
+      ftBlob:           Result := rfkStream;
+      ftMemo:           Result := rfkStream;
+      ftGraphic:        Result := rfkStream;
+      ftFmtMemo:        Result := rfkString;
+      //ftParadoxOle:
+      //ftDBaseOle:
+      ftTypedBinary:    Result := rfkStream;
+      //ftCursor:
+      ftFixedChar:      Result := rfkString;
+      ftWideString:     Result := rfkString;
+      ftLargeint:       Result := rfkInteger;
+      //ftADT:
+      //ftArray:
+      //ftReference:
+      //ftDataSet:
+      ftOraBlob:        Result := rfkStream;
+      ftOraClob:        Result := rfkStream;
+      ftVariant:        Result := rfkString;
+      //ftInterface:
+      //ftIDispatch:
+      ftGuid:           Result := rfkString;
+      ftTimeStamp:      Result := rfkDateTime;
+      //ftFMTBcd:
+      ftFixedWideChar:  Result := rfkString;
+      ftWideMemo:       Result := rfkString;
+      else
+        Result := rfkString;
+    end;
+  end;
+
+Var
+  B : Boolean;
+begin
+  inherited DoInitDataFields;
+  B:=FDataset.FieldDefs.Count=0;
+  if B then
+    FDataset.Open;
+  try
+    DataFields.Clear;
+    for i := 0 to FDataSet.FieldDefs.Count-1 do
+    begin
+      DataFields.AddField(FDataset.FieldDefs[i].Name, DatabaseKindToReportKind(FDataset.FieldDefs[i].DataType));
+    end;
+  finally
+    if B then
+      FDataset.Close;
+  end;
+end;
+
+procedure TFPReportDatasetData.DoOpen;
+begin
+  inherited DoOpen;
+  if not Assigned(FDataSet) then
+    ReportError(SErrNoDataSetAssigned);
+  FDataSet.Open;
+end;
+
+procedure TFPReportDatasetData.DoFirst;
+begin
+  if not Assigned(FDataSet) then
+    ReportError(SErrNoDataSetAssigned);
+  if not FDataSet.Active then
+    ReportError(SErrDatasetNotOpen);
+  inherited DoFirst;
+  FDataSet.First;
+end;
+
+procedure TFPReportDatasetData.DoNext;
+begin
+  inherited DoNext;
+  FDataSet.Next;
+end;
+
+procedure TFPReportDatasetData.DoClose;
+begin
+  inherited DoClose;
+  FDataSet.Close;
+end;
+
+function TFPReportDatasetData.DoEOF: boolean;
+begin
+  Result := FDataSet.EOF;
+end;
+
+end.
+

+ 566 - 0
packages/fcl-report/src/fpreportdom.pp

@@ -0,0 +1,566 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2008 Michael Van Canneyt, member of the Free Pascal development team
+
+    Stream report definition to/from XML Stream.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportdom;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, DOM;
+
+Type
+  { TFPReportDOM }
+  TXMLPropertyStorage = (psAttr,psElement);
+
+Const
+  DefPropertyStorage = psAttr;
+
+Type
+  TFPReportDOM = Class(TObject)
+  private
+    FCurrentElement: TDomElement;
+    FDocument: TXMLDocument;
+    FRootNode: TDomElement;
+    FStack : TFPList;
+    function PushCurrentElement: TDomElement;
+    procedure SetCurrentElement(const AValue: TDomElement);
+  Public
+    Constructor Create(ADocument : TXMLDocument; ARootNode : TDomElement = Nil);
+    Destructor Destroy; override;
+    Function StreamToHex(S : TStream) : String;
+    function HexToStringStream(S: String): TStringStream;
+    Function StreamsEqual(S1,S2 : TStream) : Boolean;
+    Function NewDOMElement(Const AName : DOMString) : TDomElement;
+    Function PushElement(Const AName : DOMString) : TDomElement;
+    Function PushElement(AElement : TDomElement) : TDomElement;
+    Function PopElement : TDomElement;
+    Function FindChild(Const AName: DOMString) : TDomElement;
+
+    // Writing properties as attributes of the current element
+    Procedure WriteInteger(AName : DOMString; AValue : Integer; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteString(AName : DOMString; AValue : AnsiString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteWideString(AName : DOMString; AValue : WideString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteFloat(AName : DOMString; AValue : Extended; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteDateTime(AName : DOMString; AValue : TDateTime; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteBoolean(AName : DOMString; AValue : Boolean; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteStream(AName : DOMString; AValue : TStream; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    // Writing properties but only when different from original
+    Procedure WriteIntegerDiff(AName : DOMString; AValue,AOriginal : Integer; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteStringDiff(AName : DOMString; AValue,AOriginal : AnsiString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteWideStringDiff(AName : DOMString; AValue,AOriginal : WideString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteFloatDiff(AName : DOMString; AValue,AOriginal : Extended; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteDateTimeDiff(AName : DOMString; AValue,AOriginal : TDateTime; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteBooleanDiff(AName : DOMString; AValue,AOriginal : Boolean; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+    Procedure WriteStreamDiff(AName : DOMString; AValue,AOriginal : TStream; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+    // Writing properties as attributes of the current element
+    Function ReadInteger(AName : DOMString; ADefault : Integer; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : Integer;
+    Function ReadString(AName : DOMString; ADefault : Ansistring; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : Ansistring;
+    Function ReadWideString(AName : DOMString; ADefault : WideString; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : WideString;
+    Function ReadFloat(AName : DOMString; ADefault : Extended; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : Extended;
+    Function ReadDateTime(AName : DOMString; ADefault : TDateTime; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : TDateTime;
+    Function ReadBoolean(AName : DOMString; ADefault : Boolean; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : Boolean;
+    Function ReadStream(AName : DOMString; AValue : TStream; UseStorage : TXMLPropertyStorage = DefPropertyStorage) : Boolean;
+
+    Property CurrentElement : TDomElement read FCurrentElement write SetCurrentElement;
+    Property Document : TXMLDocument Read FDocument;
+    Property RootNode : TDomElement Read FRootNode;
+  end;
+
+  EReportDOM = Class(Exception);
+
+implementation
+
+Resourcestring
+  SErrStackEmpty = 'Element stack is empty';
+  SErrNoCurrentElement = 'No current element to find node %s below';
+  SErrNodeNotElement = 'Node %s is not an element node';
+
+{ TFPReportDOM }
+
+procedure TFPReportDOM.SetCurrentElement(const AValue: TDomElement);
+begin
+  if FCurrentElement=AValue then exit;
+  FCurrentElement:=AValue;
+end;
+
+constructor TFPReportDOM.Create(ADocument: TXMLDocument; ARootNode: TDomElement
+  );
+begin
+  FDocument:=ADocument;
+  If (ARootNode=Nil) then
+    FRootNode:=FDocument.DocumentElement
+  else
+    FRootNode:=ARootNode;
+  FCurrentElement:=FRootNode;
+end;
+
+destructor TFPReportDOM.Destroy;
+begin
+  FreeAndNil(FStack);
+  inherited Destroy;
+end;
+
+function TFPReportDOM.StreamToHex(S: TStream): String;
+
+Var
+  T : TMemoryStream;
+  P,PD : PChar;
+  I,L : Integer;
+  h : String[2];
+
+begin
+  If(S is TMemoryStream) then
+    T:=S as TMemoryStream
+  else
+    begin
+    T:=TMemoryStream.Create;
+    T.CopyFrom(S,0);
+    end;
+  try
+    L:=T.Size;
+    SetLength(Result,L*2);
+    PD:=PChar(Result);
+    P:=PChar(T.Memory);
+    For I:=1 to L do
+      begin
+      H:=HexStr(Ord(P^),2);
+      PD^:=H[1];
+      Inc(PD);
+      PD^:=H[2];
+      Inc(P);
+      Inc(PD);
+      end;
+  finally
+    S.Position:=0;
+  end;
+end;
+
+function TFPReportDOM.StreamsEqual(S1, S2: TStream): Boolean;
+
+Var
+  S : TStringStream;
+  T : String;
+
+begin
+  Result:=(S1=S2);
+  If not Result then
+    begin
+    Result:=(S1.Size=S2.Size);
+    If Result then
+      begin
+      S:=TStringStream.Create('');
+      try
+        S.CopyFrom(S1,0);
+        T:=S.DataString;
+        S.Size:=0;
+        S.CopyFrom(S2,0);
+        Result:=(T=S.DataString);
+      finally
+        S.Free;
+      end;
+      end;
+    end;
+end;
+
+function TFPReportDOM.NewDOMElement(Const AName: DOMString): TDomElement;
+begin
+  Result:=FDocument.CreateElement(AName);
+  FCurrentElement.AppendChild(Result);
+  FCurrentElement:=Result;
+end;
+
+function TFPReportDOM.PushCurrentElement: TDomElement;
+
+begin
+  If Not Assigned(FStack) then
+    FStack:=TFPList.Create;
+  FStack.Add(FCurrentElement);
+  Result:=FCurrentElement;
+end;
+
+function TFPReportDOM.PushElement(Const AName: DOMString): TDomElement;
+begin
+  Result:=PushCurrentElement;
+  NewDomElement(AName);
+end;
+
+function TFPReportDOM.PushElement(AElement: TDomElement): TDomElement;
+begin
+  Result:=PushCurrentElement;
+  CurrentElement:=AElement;
+end;
+
+function TFPReportDOM.PopElement: TDomElement;
+begin
+  If (FStack=Nil) or (FStack.Count=0) then
+    Raise EReportDOM.Create(SErrStackEmpty);
+  Result:=FCurrentElement;
+  FCurrentElement:=TDomElement(FStack[FStack.Count-1]);
+  FStack.Delete(FStack.Count-1);
+  If (FStack.Count=0) then
+    FreeAndNil(FStack);
+end;
+
+function TFPReportDOM.FindChild(const AName: DOMString): TDomElement;
+
+Var
+  N : TDomNode;
+
+begin
+  If Assigned(FCurrentElement) then
+    N:=FCurrentElement.FindNode(AName)
+  else
+    Raise EReportDom.CreateFmt(SErrNoCurrentElement,[AName]);
+  If N=Nil then
+    Result:=Nil
+  else if N is TDOMElement then
+    Result:=N as TDomElement
+  else
+    Raise EReportDom.CreateFmt(SErrNodeNotElement,[AName]);
+end;
+
+Procedure TFPReportDOM.WriteInteger(AName: DOMString; AValue: Integer; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  WriteString(AName,IntToStr(AValue),UseStorage);
+end;
+
+Procedure TFPReportDOM.WriteString(AName: DOMString; AValue: AnsiString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+Var
+  el : TDomElement;
+
+begin
+  If UseStorage=psAttr then
+    FCurrentElement[AName]:=AValue
+  else
+    begin
+    el:=FDocument.CreateElement(AName);
+    FCurrentElement.AppendChild(el);
+    el.AppendChild(FDocument.CreateTextNode(AValue));
+    end
+end;
+
+Procedure TFPReportDOM.WriteWideString(AName: DOMString; AValue: WideString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+Var
+  el : TDomElement;
+
+begin
+  If UseStorage=psAttr then
+    FCurrentElement[AName]:=AValue
+  else
+    begin
+    el:=FDocument.CreateElement(AName);
+    FCurrentElement.AppendChild(el);
+    el.AppendChild(FDocument.CreateTextNode(AValue));
+    end;
+end;
+
+procedure TFPReportDOM.WriteFloat(AName: DOMString; AValue: Extended; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+Var
+  S : String;
+
+begin
+  Str(AValue:18,S);
+  WriteString(AName,TrimLeft(S),UseStorage);
+end;
+
+procedure TFPReportDOM.WriteDateTime(AName: DOMString; AValue: TDateTime; UseStorage : TXMLPropertyStorage = DefPropertyStorage  );
+
+Var
+  S : String;
+
+begin
+  If Frac(AValue)=0 then  // Only date
+    S:='yyyymmdd'
+  else if Trunc(AValue)=0 then // Only time
+    S:='00000000hhnnsszzz'
+  else
+    S:='yyyymmddhhnnsszzz'; // Full date-time
+  S:=FormatDateTime(S,AValue);
+  WriteString(AName,S,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteBoolean(AName: DOMString; AValue: Boolean; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  WriteString(AName,IntToStr(Ord(Avalue)),UseStorage);
+end;
+
+procedure TFPReportDOM.WriteStream(AName: DOMString; AValue: TStream; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+begin
+  WriteString(AName,StreamToHex(AValue),UseStorage);
+end;
+
+procedure TFPReportDOM.WriteIntegerDiff(AName: DOMString; AValue, AOriginal: Integer; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteInteger(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteStringDiff(AName: DOMString; AValue, AOriginal: AnsiString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteString(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteWideStringDiff(AName: DOMString; AValue, AOriginal: WideString; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteWideString(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteFloatDiff(AName: DOMString; AValue, AOriginal: Extended; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteFloat(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteDateTimeDiff(AName: DOMString; AValue, AOriginal: TDateTime; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteDateTime(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteBooleanDiff(AName: DOMString; AValue, AOriginal: Boolean; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+begin
+  If (AValue<>AOriginal) then
+    WriteBoolean(AName,AValue,UseStorage);
+end;
+
+procedure TFPReportDOM.WriteStreamDiff(AName: DOMString; AValue, AOriginal: TStream; UseStorage : TXMLPropertyStorage = DefPropertyStorage);
+
+begin
+  If StreamsEqual(AValue,AOriginal) then
+    Exit;
+  WriteStream(AName,AValue,UseStorage);
+end;
+
+
+function TFPReportDOM.ReadInteger(AName: DOMString; ADefault: Integer;
+  UseStorage: TXMLPropertyStorage): Integer;
+
+begin
+  Result:=StrToIntDef(ReadString(AName,'',UseStorage),ADefault);
+end;
+
+function TFPReportDOM.ReadString(AName: DOMString; ADefault: Ansistring;
+  UseStorage: TXMLPropertyStorage): Ansistring;
+
+Var
+  N : TDomNode;
+
+begin
+  If (UseStorage=psAttr) then
+    begin
+    N:=FCurrentElement.GetAttributeNode(AName);
+    If Assigned(N) then
+      Result:=TDomAttr(N).NodeValue
+    else
+      Result:=ADefault;
+    end
+  else
+    begin
+    N:=FCurrentElement.FindNode(AName);
+    If Assigned(N) and (N is TDomElement) then
+      Result:=TDomElement(N).FirstChild.NodeValue
+    else
+      Result:=ADefault;
+    end;
+end;
+
+function TFPReportDOM.ReadWideString(AName: DOMString; ADefault: WideString;
+  UseStorage: TXMLPropertyStorage): WideString;
+
+Var
+  N : TDomNode;
+
+begin
+  If (UseStorage=psAttr) then
+    begin
+    N:=FCurrentElement.GetAttributeNode(AName);
+    If Assigned(N) then
+      Result:=TDomAttr(N).NodeValue
+    else
+      Result:=ADefault;
+    end
+  else
+    begin
+    N:=FCurrentElement.FindNode(AName);
+    If Assigned(N) and (N is TDomElement) then
+      Result:=TDomElement(N).FirstChild.NodeValue
+    else
+      Result:=ADefault;
+    end;
+end;
+
+function TFPReportDOM.ReadFloat(AName: DOMString; ADefault: Extended;
+  UseStorage: TXMLPropertyStorage): Extended;
+
+Var
+  S : String;
+
+  C : Integer;
+
+begin
+  S:=ReadString(AName,'',UseStorage);
+  If (S='') then
+    Result:=ADefault
+  else
+    begin
+    Val(S,Result,C);
+    If (C<>0) then
+      Result:=ADefault;
+    end;
+end;
+
+function TFPReportDOM.ReadDateTime(AName: DOMString; ADefault: TDateTime;
+  UseStorage: TXMLPropertyStorage): TDateTime;
+
+  Function NextNumber(Var S : String; Digits : Integer; Out AValue : Word) : Boolean;
+
+  Var
+    I : Integer;
+
+  begin
+    AValue:=0;
+    Result:=TryStrToInt(Copy(S,1,Digits),I);
+    If Result then
+      begin
+      Result:=(I>=0);
+      If Result then
+        AValue:=I;
+      end;
+    system.Delete(S,1,Digits);
+  end;
+
+Var
+  S : String;
+  D : TDateTime;
+  W1,W2,W3,W4 : Word;
+
+begin
+  S:=ReadString(AName,'',UseStorage);
+  If (S='') then
+    Result:=ADefault
+  else If Not (NextNumber(S,4,W1) and NextNumber(S,2,W2) and NextNumber(S,2,W3)) Then
+    Result:=ADefault
+  else If Not TryEncodeDate(W1,W2,W3,Result) then
+    Result:=ADefault
+  else
+    begin
+    // is there a time part ?
+    If (S<>'') then
+      begin
+      if NextNumber(S,2,W1) and NextNumber(S,2,W2) and NextNumber(S,2,W3) then
+        begin
+        // Check milliseconds
+        If (S<>'') then
+          begin
+          if not NextNumber(S,3,W4) then
+            begin
+            Result:=ADefault;
+            Exit;
+            end;
+          end
+        else
+          W4:=0;
+        if Not TryEncodeTime(W1,W2,W3,W4,D) then
+          Result:=ADefault
+        else
+          Result:=Result+D;
+        end
+      else // If time formatted wrong
+        Result:=ADefault;
+      end;
+    end;
+end;
+
+function TFPReportDOM.ReadBoolean(AName: DOMString; ADefault: Boolean;
+  UseStorage: TXMLPropertyStorage): Boolean;
+
+Var
+  S : String;
+
+begin
+  S:=ReadString(AName,'',UseStorage);
+  Result:=ADefault;
+  if (S='1') then
+    Result:=True
+  else If (S='0') then
+    Result:=False;
+end;
+
+function TFPReportDOM.HexToStringStream(S : String) : TStringStream;
+
+Var
+  T : String;
+  I,J : Integer;
+  B : Byte;
+  P : Pchar;
+  H : String[3];
+
+begin
+  Result:=Nil;
+  SetLength(H,3);
+  H[1]:='$';
+  if (S<>'') then
+    begin
+    SetLength(T,Length(S) div 2);
+    P:=PChar(T);
+    I:=1;
+    While I<Length(S) do
+      begin
+      H[2]:=S[i];
+      Inc(I);
+      H[3]:=S[i];
+      Inc(I);
+      Val(H,B,J);
+      If (J=0) then
+        P^:=Char(B)
+      else
+        P^:=#0;
+      Inc(P);
+      end;
+    Result:=TStringStream.Create(T);
+    end;
+end;
+
+function TFPReportDOM.ReadStream(AName: DOMString; AValue: TStream;
+  UseStorage: TXMLPropertyStorage): Boolean;
+
+Var
+  S: String;
+  SS : TStringStream;
+
+begin
+  S:=ReadString(AName,'',UseStorage);
+  Result:=(S<>'');
+  If Result then
+    begin
+    SS:=HexToStringStream(S);
+    try
+      AValue.CopyFrom(SS,0);
+    Finally
+      SS.Free;
+    end;
+    end;
+end;
+
+end.
+

+ 888 - 0
packages/fcl-report/src/fpreportfpimageexport.pp

@@ -0,0 +1,888 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2016 Michael Van Canneyt,
+    member of the Free Pascal development team
+
+    FPReport FPImage export filter.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{
+  FPImage Export filter.
+
+  FPImage is included as standard with FPC. This exporter uses those classes
+  to generate image output. One image per report page, and by default as a
+  PNG image.
+
+}
+unit fpreportfpimageexport;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpReport,
+  fpimage,
+  fpcanvas,
+  fpreportcanvashelper,
+  contnrs,
+  dom,
+  dom_html,
+  htmwrite,
+  FPImgCmn,
+  fpimgcanv,
+  fpreporthtmlutil;
+
+type
+
+  { TFPReportExportfpImage }
+  THTMLOption = (hoEnabled,hoTOCPage,hoFramePage,hoExternalJS);
+  THTMLOptions = set of THTMLOption;
+
+  TFPReportExportfpImage = class(TFPReportExporter)
+  private
+    FBaseFileName: string;
+    FImage : TFPCustomImage;
+    FCanvas : TFPCustomCanvas;
+    FDPI: integer;
+    FImageWidth: integer;
+    FImageHeight: integer;
+    FOldFontDPI: integer;
+    FFonts : TFPObjectHashTable;
+    FSequenceFormat: string;
+    FWriterClass : TFPCustomImageWriterClass;
+    FHelper : TFPReportCanvasHelper;
+    // Html support
+    FCSSDir: String;
+    FFramePage: TFramePageOptions;
+    FHTMLOptions: THTMLOptions;
+    FPageNavigator: TPageNavigatorOptions;
+    FStyleEmbedding: TStyleEmbedding;
+    FTOCPage: TTOCPageOptions;
+    FContext : TGenerateHTMLContext;
+    FDoc : THTMLDocument;
+    FHeadElement : THTMLElement;
+    FBodyElement : THTMLElement;
+    procedure FinishHTMLPage(AFileName: String);
+    procedure SetCanvas(AValue: TFPCustomCanvas);
+    procedure SetDPI(AValue: integer);
+    procedure SetFramePage(AValue: TFramePageOptions);
+    procedure SetPageNavigator(AValue: TPageNavigatorOptions);
+    procedure SetTOCPage(AValue: TTOCPageOptions);
+  protected
+    function CreateFramePageOptions: TFramePageOptions; virtual;
+    function CreatePageNavigatorOptions: TPageNavigatorOptions; virtual;
+    function CreateTOCPageOptions: TTOCPageOptions; virtual;
+    Procedure RenderImage(aRect : TFPReportRect; var AImage: TFPCustomImage) ; override;
+    procedure RenderBand(Aband: TFPReportCustomBand); virtual;
+    procedure RenderElement(ABand: TFPReportCustomBand; Element: TFPReportElement); virtual;
+    procedure ConfigWriter(AWriter: TFPCustomImageWriter); virtual;
+    procedure SetBaseFileName(AValue: string); override;
+    function CoordToPoint(const APos: TFPReportPoint;  const AHOffset: TFPReportUnits=0; const AVoffset: TFPReportUnits=0): TPoint;
+    function CoordToRect(const APos: TFPReportPoint; const AWidth: TFPReportUnits=0; const AHeight: TFPReportUnits=0): TRect;
+    function  FindFontFile(const AFontName: string): string;
+    Function BufferToFile(const APageNo: integer) : String; virtual;
+    procedure SetupPageRender(const APage: TFPReportPage);
+    function  CreateCanvas(AImage: TFPCustomImage): TFPCustomCanvas; virtual;
+    function  CreateImage(const AWidth, AHeight: Integer): TFPCustomImage; virtual;
+    procedure PrepareCanvas; virtual;
+    procedure DoExecute(const ARTObjects: TFPList); override;
+    procedure RenderFrame(const ABand: TFPReportCustomBand; const AFrame: TFPReportFrame; const APos: TFPReportPoint; const AWidth, AHeight: TFPReportUnits); virtual;
+    procedure RenderMemo(const ABand: TFPReportCustomBand; const AMemo: TFPReportCustomMemo); virtual;
+    procedure RenderShape(const ABand: TFPReportCustomBand; const AShape: TFPReportCustomShape); virtual;
+    procedure RenderImage(const ABand: TFPReportCustomBand; const AImage: TFPReportCustomImage); virtual;
+    procedure RenderCheckbox(const ABand: TFPReportCustomBand; const ACheckbox: TFPReportCustomCheckbox); virtual;
+    procedure RenderShape(const lpt1 : TFPReportPoint; const AShape: TFPReportCustomShape); virtual;
+    // HTML support
+    function SetupHTMLPageRender(const APage: TFPReportPage): THTMLElement; virtual;
+    // HTML Wrapper page. Return file name
+    function CreateHTMLFileForImage(const ImageFile: String; APage: TFPReportPage; APageNo: Integer): String; virtual;
+    // This creates the actual element that contains the image
+    function CreateImageElement(const ImageFile: String): THTMLElement; virtual;
+    // Frame page
+    procedure CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String); virtual;
+    // TOC page
+    function CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String; virtual;
+    // Navigator
+    Procedure CreatePageNavigator(aPosition : TNavigatorPosition; AParentDiV : THTMLElement; APage : TFPReportPage; APageNo : integer); virtual;
+    Property Context : TGenerateHTMLContext Read FContext;
+    Property Doc : THTMLDocument Read FDoc;
+    Property HeadElement : THTMLElement Read FHeadElement;
+    Property BodyElement : THTMLElement Read FBodyElement;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Class Function Name : String; override;
+    Class Function Description : String; override;
+    Class Function DefaultExtension: String; override;
+    Procedure GenerateHTML(ImageFiles : TStrings);
+    Procedure SetFileName(const aFileName: String); override;
+    function GetFont(const AFontName: String): TFPCustomFont;
+    function mmToPixels(const AValue: TFPReportUnits): Integer;
+    class function ColorToRGBTriple(const AColor: UInt32): TFPColor;
+    Property Canvas : TFPCustomCanvas Read FCanvas Write SetCanvas;
+    Property Image : TFPCustomImage Read FImage;
+    Property Helper : TFPReportCanvasHelper Read FHelper;
+  Published
+    Property BaseFileName : String Read FBaseFIleName Write SetBaseFileName;
+    property SequenceFormat : string read FSequenceFormat write FSequenceFormat;
+    property DPI: integer read FDPI write SetDPI;
+    property ImageWidth: integer read FImageWidth;
+    property ImageHeight: integer read FImageHeight;
+    Property HTMLOptions : THTMLOptions Read FHTMLOptions Write FHTMLOptions;
+    Property StyleEmbedding : TStyleEmbedding Read FStyleEmbedding Write FStyleEmbedding;
+    Property CSSDir : String Read FCSSDir Write FCSSDir;
+    Property TOCPage : TTOCPageOptions Read FTOCPage Write SetTOCPage;
+    Property FramePage: TFramePageOptions Read FFramePage Write SetFramePage;
+    Property PageNavigator: TPageNavigatorOptions Read FPageNavigator Write SetPageNavigator;
+  end;
+
+implementation
+
+uses
+  ftFont,
+  fpTTF,
+  fpparsettf,
+  fpwritepng;
+
+
+//  RGBA_Width = 4;
+
+type
+
+  { for access to Protected methods }
+  TReportCheckboxFriend = class(TFPReportCustomCheckbox);
+
+  TFPImageFriend = class(TFPCompactImgRGBA8Bit)
+  private
+    procedure ReversePixelColorOrder;
+  end;
+
+{ TFPImageFriend }
+
+procedure TFPImageFriend.ReversePixelColorOrder;
+var
+  x, y: UInt32;
+  v: TFPCompactImgRGBA8BitValue;
+  n: TFPCompactImgRGBA8BitValue;
+begin
+  for x := 0 to Width-1 do
+    for y := 0 to Height-1 do
+    begin
+      v := FData[x+y*Width];
+      n.b := v.r;
+      n.g := v.g;
+      n.r := v.b;
+      n.a := v.a;
+      FData[x+y*Width] := n;
+    end;
+end;
+
+
+
+{ TFPReportExportfpImage }
+
+function TFPReportExportfpImage.FindFontFile(const AFontName: string): string;
+var
+  fnt: TFPFontCacheItem;
+begin
+  fnt := gTTFontCache.Find(AFontName); // we are doing a PostScript Name lookup (it contains Bold, Italic info)
+  if Assigned(fnt) then
+    Result := fnt.FileName
+  else
+    raise EReportExportError.CreateFmt('fpreport: Could not find the font <%s> in the font cache.', [AFontName]);
+end;
+
+function TFPReportExportfpImage.mmToPixels(const AValue: TFPReportUnits): Integer;
+begin
+  Result := Round(AValue * (DPI / cInchToMM));
+end;
+
+class function TFPReportExportfpImage.ColorToRGBTriple(const AColor: UInt32
+  ): TFPColor;
+begin
+  Result:=TFPReportCanvasHelper.ColorToRGBTriple(AColor);
+end;
+
+procedure TFPReportExportfpImage.ConfigWriter(AWriter: TFPCustomImageWriter);
+
+begin
+  {
+    Do nothing.
+    Maybe at a later point set published properties of writer based on a Params Tstrings property
+    With Name=Value pairs.
+  }
+end;
+
+function TFPReportExportfpImage.BufferToFile(const APageNo: integer): String;
+
+var
+  Writer: TFPCustomImageWriter;
+  E,SN,FN : String;
+
+begin
+  SN:=Format(SequenceFormat,[APageNo]);
+  E:=ExtractFileExt(BaseFileName);
+  FN:=ChangeFileExt(BaseFileName,SN+E);
+  Writer:=FWriterClass.Create;
+  try
+    ConfigWriter(Writer);
+    FImage.SaveToFile(FN,writer);
+    Result:=FN;
+  finally
+    Writer.Free;
+  end;
+end;
+
+procedure TFPReportExportfpImage.SetupPageRender(const APage: TFPReportPage);
+begin
+  FreeAndNil(FCanvas);
+  FreeAndNil(FHelper);
+  FreeAndNil(FImage);
+  FImageWidth := mmToPixels(APage.PageSize.Width);
+  FImageHeight := mmToPixels(APage.PageSize.Height);
+  FImage:=CreateImage(FImageWidth,FImageHeight);
+  FCanvas:=CreateCanvas(FImage);
+  FHelper:=TFPReportCanvasHelper.Create(FCanvas,DPI);
+  PrepareCanvas;
+end;
+
+function TFPReportExportfpImage.CreateImage(const AWidth, AHeight: Integer
+  ): TFPCustomImage;
+
+begin
+  Result:=TFPCompactImgRGB8Bit.Create(AWidth,AHeight);
+end;
+
+
+function TFPReportExportfpImage.CreateCanvas(AImage: TFPCustomImage
+  ): TFPCustomCanvas;
+
+begin
+  Result:=TFPImageCanvas.create(FImage);
+end;
+
+procedure TFPReportExportfpImage.PrepareCanvas;
+
+begin
+  Canvas.Pen.Style:=psSolid;
+  Canvas.Brush.Style:=bsSolid;
+  Canvas.Brush.FPColor:=colWhite;
+  Canvas.FillRect(0,0,ImageWidth-1,ImageHeight-1);
+end;
+
+function TFPReportExportfpImage.CoordToPoint(const APos: TFPReportPoint;
+  const AHOffset: TFPReportUnits; const AVoffset: TFPReportUnits): TPoint;
+
+begin
+  Result:=FHelper.CoordToPoint(aPos,aHOffset,aVoffset);
+end;
+
+function TFPReportExportfpImage.CoordToRect(const APos: TFPReportPoint;
+  const AWidth: TFPReportUnits; const AHeight: TFPReportUnits): TRect;
+
+begin
+  Result:=FHelper.CoordToRect(aPos,AWidth,AHeight);
+end;
+
+procedure TFPReportExportfpImage.RenderFrame(const ABand: TFPReportCustomBand; const AFrame: TFPReportFrame;
+  const APos: TFPReportPoint; const AWidth, AHeight: TFPReportUnits);
+
+var
+  bStroke, bFill: boolean;
+  FR : TRect;
+
+begin
+  bStroke := AFrame.Color <> clNone;
+  bFill := AFrame.BackgroundColor <> clNone;
+  if not (bStroke or bFill) then
+    exit;
+  FR:=CoordToRect(APos,AWidth,AHeight);
+  Canvas.Pen.Style:=AFrame.Pen;
+  Canvas.Pen.FPColor:=ColorToRGBTriple(AFrame.Color);
+  Canvas.Pen.Width:=AFrame.Width;
+  if (AFrame.Shape=fsRectangle) and (bStroke or bFill) then
+    begin
+    if bFill then
+      begin
+      Canvas.Brush.Style:=bsSolid;
+      Canvas.Brush.FPColor:=ColorToRGBTriple(AFrame.BackgroundColor);
+      FCanvas.FillRect(FR);
+      end;
+    if bStroke then
+      begin
+      Canvas.Brush.Style:=bsClear;
+      FCanvas.Rectangle(FR);
+      end;
+    end;
+  if (AFrame.Shape=fsNone) and bStroke then
+    begin
+    if (flTop in AFrame.Lines) then
+      FCanvas.line(FR.Left, fr.Top,fr.Right,fr.Top);
+    if (flbottom in AFrame.Lines) then
+      FCanvas.line(FR.Left, fr.Bottom,fr.Right,fr.Bottom);
+    if (flLeft in AFrame.Lines) then
+      FCanvas.line(FR.Left, fr.Top,FR.Left,fr.Bottom);
+    if (flRight in AFrame.Lines) then
+      FCanvas.line(FR.Right, fr.Top,FR.Right,fr.Bottom);
+    end;  { Frame.Shape = fsNone }
+end;
+
+Type
+  THackReportMemo = class(TFPReportCustomMemo)
+  public
+    property  Font;
+  end;
+
+function TFPReportExportfpImage.GetFont(const AFontName: String): TFPCustomFont;
+
+Var
+  FFN : String;
+  lFTFont : TFreeTypeFont;
+
+begin
+  Result:=Nil;
+  Result:=TFPCustomFont(FFonts.Items[AFontName]);
+  If (Result=Nil) then
+    begin
+    FFN:=FindFontFile(AFontName);
+    lFTFont:=TFreeTypeFont.create;
+    lFTFont.Name:=FFN;
+    Result:=lFTFont;
+    FFonts.Add(AFontName,Result);
+    end;
+end;
+
+procedure TFPReportExportfpImage.RenderMemo(const ABand: TFPReportCustomBand; const AMemo: TFPReportCustomMemo);
+var
+  lPt1: TFPReportPoint;  // original Report point
+  lMemo: THackReportMemo;
+  i: integer;
+  lXPos: TFPReportUnits;
+  lYPos: TFPReportUnits;
+  txtblk: TFPTextBlock;
+begin
+  lMemo := THackReportMemo(AMemo);
+
+  { Store the Top-Left coordinate of the Memo. We will be reusing this info. }
+  lPt1.Left := ABand.RTLayout.Left + AMemo.RTLayout.Left;
+  lPt1.Top := ABand.RTLayout.Top + AMemo.RTLayout.Top;
+
+  { Frame must be drawn before the text as it could have a fill color. }
+  RenderFrame(ABand, AMemo.Frame, lPt1, AMemo.RTLayout.Width, AMemo.RTLayout.Height);
+
+  { render the TextBlocks as-is. }
+  for i := 0 to lMemo.TextBlockList.Count-1 do
+  begin
+    txtblk := lMemo.TextBlockList[i];
+    Canvas.Font := GetFont(txtblk.FontName);
+    Canvas.Font.Size := Round(gTTFontCache.PointSizeInPixels(lMemo.Font.Size));
+    lXPos := lPt1.Left + txtblk.Pos.Left;
+    lYPos := lPt1.Top + txtblk.Pos.Top;
+
+    if txtblk.BGColor <> clNone then
+    begin
+      Canvas.Pen.Style := psClear;
+      Canvas.Brush.Style := bsSolid;
+      Canvas.Brush.FPColor := ColorToRGBTriple(txtblk.BGColor);
+      Canvas.Rectangle(
+          mmToPixels(lXPos),
+          mmToPixels(lYPos - txtblk.Descender),
+          mmToPixels(lXPos + txtblk.Width),
+          mmToPixels(lYPos + txtblk.Height + txtblk.Descender)
+      );
+    end;
+    Canvas.Font.FPColor := ColorToRGBTriple(txtblk.FGColor);
+    { FPImage text origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+    lYPos := lYPos + txtblk.Height;
+    Canvas.TextOut(
+        mmToPixels(lXPos),
+        mmToPixels(lYPos),
+        txtblk.Text
+    );
+  end;
+end;
+
+procedure TFPReportExportfpImage.RenderShape(const ABand: TFPReportCustomBand; const AShape: TFPReportCustomShape);
+
+var
+  lPt1: TFPReportPoint;  // original Report point
+
+begin
+  lPt1.Left := ABand.RTLayout.Left + AShape.RTLayout.Left;
+  lPt1.Top := ABand.RTLayout.Top + AShape.RTLayout.Top;
+  { Frame must be drawn before the text as it could have a fill color. }
+  RenderFrame(ABand, AShape.Frame, lPt1, AShape.RTLayout.Width, AShape.RTLayout.Height);
+  RenderShape(lpt1,AShape);
+end;
+
+procedure TFPReportExportfpImage.RenderImage(const ABand: TFPReportCustomBand; const AImage: TFPReportCustomImage);
+
+var
+  lPt: TFPReportPoint;
+
+begin
+  lPt.Left := ABand.RTLayout.Left + AImage.RTLayout.Left;
+  lPt.Top := ABand.RTLayout.Top + AImage.RTLayout.Top;
+  { Frame must be drawn before the Image as it could have a fill color. }
+  RenderFrame(ABand, AImage.Frame, lPt, AImage.RTLayout.Width, AImage.RTLayout.Height);
+  FHelper.RenderImage(lpt,AImage);
+end;
+
+procedure TFPReportExportfpImage.RenderCheckbox(const ABand: TFPReportCustomBand; const ACheckbox: TFPReportCustomCheckbox);
+
+var
+  lPt: TFPReportPoint;
+
+begin
+  lPt.Left := ABand.RTLayout.Left + ACheckbox.RTLayout.Left;
+  lPt.Top := ABand.RTLayout.Top + ACheckbox.RTLayout.Top;
+  FHelper.RenderCheckBox(lpt,aCheckbox);
+end;
+
+procedure TFPReportExportfpImage.RenderShape(const lpt1: TFPReportPoint; const AShape: TFPReportCustomShape);
+begin
+  FHelper.RenderShape(lpt1,AShape);
+end;
+
+procedure TFPReportExportfpImage.CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String);
+Var
+  TFCP : TTOCFramePageCreator;
+
+begin
+  TFCP:=Nil;
+  try
+    TFCP:=TTOCFramePageCreator.Create(FContext,FramePage);
+    TFCP.CreateFramePage(PageNames,ATocPageFileName);
+  finally
+    TFCP.Free;
+  end;
+end;
+
+function TFPReportExportfpImage.CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String;
+Var
+  TPC : TTOCPageCreator;
+
+begin
+  TPC:=TTOCPageCreator.Create(FCOntext,TOCPage);
+  try
+    Result:=TPC.CreateTOCPage(APageNames,ForFrame);
+  finally
+    TPC.Free;
+  end;
+end;
+
+procedure TFPReportExportfpImage.CreatePageNavigator(aPosition: TNavigatorPosition; AParentDiV: THTMLElement; APage : TFPReportPage; APageNo :Integer);
+
+Var
+  PNEC : TPageNavigatorElementCreator;
+
+begin
+  PNEC:=TPageNavigatorElementCreator.Create(FContext,APage,PageNavigator);
+  try
+    FContext.CurrentPageNo:=APageNo;
+    PNec.CreatePageNavigator(aPosition,aParentDiv);
+  finally
+    PNEC.Free;
+  end;
+end;
+
+constructor TFPReportExportfpImage.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  SetBaseFileName(ApplicationName + '-'+DefaultExtension);
+  SequenceFormat := '%.2d';
+  FDPI := 96;
+  FImageWidth := 0;
+  FImageHeight := 0;
+  // store the original DPI, we will restore it later
+  FOldFontDPI := gTTFontCache.DPI;
+  // fonts always seem to be in 72 dpi
+  gTTFontCache.DPI := 72;
+  FFonts:=TFPObjectHashTable.Create(True);
+  FFramePage:=CreateFramePageOptions;
+  FPageNavigator:=CreatePageNavigatorOptions;
+  FTOCPage:=CreateTOCPageOptions;
+end;
+
+destructor TFPReportExportfpImage.Destroy;
+begin
+  FreeAndNil(FFramePage);
+  FreeAndNil(FPageNavigator);
+  FreeAndNil(FTOCPage);
+  FreeAndNil(FFonts);
+  FreeAndNil(FHelper);
+  FreeAndNil(FCanvas);
+  FreeAndNil(FImage);
+  gTTFontCache.DPI := FOldFontDPI;
+  inherited Destroy;
+end;
+
+function TFPReportExportfpImage.CreateTOCPageOptions: TTOCPageOptions;
+begin
+  Result:=TTOCPageOptions.Create;
+end;
+
+function TFPReportExportfpImage.CreateFramePageOptions: TFramePageOptions;
+
+begin
+  Result:=TFramePageOptions.Create;
+  Result.TOCZoneSize:=10;
+end;
+
+
+function TFPReportExportfpImage.CreatePageNavigatorOptions: TPageNavigatorOptions;
+begin
+  Result:=TPageNavigatorOptions.Create;
+  Result.Positions:=[npTop];
+  Result.Options:=[hnoFirstLast,hnoPageNo];
+  Result.FixedWidth:=80;
+  Result.FixedHeight:=32;
+  Result.FixedMargin:=8;
+end;
+
+class function TFPReportExportfpImage.Name: String;
+begin
+  Result:='FPImage';
+end;
+
+class function TFPReportExportfpImage.Description: String;
+begin
+  Result:='Image file export (using FPImage)';
+end;
+
+class function TFPReportExportfpImage.DefaultExtension: String;
+begin
+  Result:='.png';
+end;
+
+function TFPReportExportfpImage.SetupHTMLPageRender(const APage: TFPReportPage): THTMLElement;
+
+Var
+  TitleElement,HTMLEl, El : TDomElement;
+  D : String;
+
+begin
+  FDoc:=THTMLDocument.Create;
+  FDoc.AppendChild(FDoc.Impl.CreateDocumentType(
+    'HTML', '-//W3C//DTD HTML 4.01 Transitional//EN',
+    'http://www.w3.org/TR/html4/loose.dtd'));
+  HTMLEl := FDoc.CreateHtmlElement;
+  FDoc.AppendChild(HTMLEl);
+  FHeadElement:=FDoc.CreateHeadElement;
+  HTMLEl.AppendChild(FHeadElement);
+  El := FDoc.CreateElement('meta');
+  El['http-equiv'] := 'Content-Type';
+  El['content'] := 'text/html; charset=utf-8';
+  FHeadElement.AppendChild(El);
+  FContext.ResetPage(FDoc);
+  Case StyleEmbedding of
+    seInline : ;
+    seCSSFile :
+      begin
+      El:=FDoc.CreateElement('link');
+      El['rel']:='stylesheet';
+      D:=CSSDir;
+      if (D<>'') then
+        D:=D+'/'; // not pathdelim !
+      El['href']:=D+ChangeFileExt(ExtractFileName(BaseFileName),'.css');
+      FHeadElement.AppendChild(El);
+      end
+  end;
+  TitleElement := FDoc.CreateElement('title');
+  FHeadElement.AppendChild(TitleElement);
+  FBodyElement := FDoc.CreateElement('body');
+  HTMLEl.AppendChild(FBodyElement);
+  Result:=Context.CreateDiv(Nil,aPage);
+end;
+
+Procedure TFPReportExportfpImage.FinishHTMLPage(AFileName : String);
+
+Var
+  F,D : String;
+  Sel,El : THTMLElement;
+  Script : TStrings;
+
+begin
+  Case StyleEmbedding of
+     seInline : ;
+      seCSSFile :
+        begin
+        D:=ExtractFilePath(AFilename);
+        if (CSSDir<>'') then
+          D:=D+CSSDir+PathDelim;
+        FContext.StyleContent.SaveToFile(D+ChangeFileExt(ExtractFileName(AFilename),'.css'));
+        end;
+      seStyleTag :
+        begin;
+        El:=FDoc.CreateElement('style');
+        EL.AppendChild(FDOC.CreateTextNode(FContext.StyleContent.Text));
+        FHeadElement.AppendChild(El);
+        end
+    end;
+  if (hnoPageNoEdit in PageNavigator.Options) then
+     begin
+     Script:=TStringList.Create;
+     try
+       TPageNavigatorElementCreator.WriteDefaultScript(FContext,Script);
+      if (Script.Count>0) then
+        begin
+        Sel:=FDoc.CreateElement('script');
+        Sel['type']:='application/javascript';
+        if (hoExternalJS in HTMLOptions) then
+          begin
+          F:=ChangeFileExt(AFileName,'.js');
+          Script.SaveToFile(F);
+          Sel['src']:=ExtractFileName(F);
+          end
+        else
+          Sel.AppendChild(FDoc.CreateCDATASection(Script.Text));
+        HeadElement.AppendChild(Sel);
+        end;
+     finally
+       Script.Free;
+     end;
+     end;
+end;
+
+Function TFPReportExportfpImage.CreateImageElement(Const ImageFile: String) : THTMLElement;
+
+begin
+  Result:=Doc.CreateElement('img');
+  Result['src']:=ExtractFileName(ImageFile);
+end;
+
+Function TFPReportExportfpImage.CreateHTMLFileForImage(Const ImageFile: String; APage : TFPReportPage; APageNo : Integer) : String;
+
+Var
+  PageDiv : THTMLElement;
+  ImgEl : THTMLElement;
+
+begin
+  Result:=ChangeFileExt(ImageFile,'.html');
+  PageDiv:=SetupHTMLPageRender(APage);
+  try
+   if npTop in PageNavigator.Positions then
+     CreatePageNavigator(npTop,BodyElement,aPage,aPageNo);
+   if npLeft in PageNavigator.Positions then
+     CreatePageNavigator(npLeft,BodyElement,aPage,aPageNo);
+   BodyElement.AppendChild(PageDiv);
+   PageDiv.AppendChild(CreateImageElement(ImageFile));
+   if (npRight in PageNavigator.Positions) then
+     CreatePageNavigator(npRight,BodyElement,aPage,aPageNo);
+   if (npBottom in PageNavigator.Positions) then
+     CreatePageNavigator(npBottom,BodyElement,aPage,aPageNo);
+   FinishHTMLPage(Result);
+   WriteHTMLFile(Doc,Result);
+  finally
+    FreeAndNil(FDoc);
+  end;
+end;
+
+procedure TFPReportExportfpImage.GenerateHTML(ImageFiles: TStrings);
+
+Var
+  FN : String;
+  HFN : TStrings;
+  I : integer;
+
+begin
+  HFN:=Nil;
+  FContext:=TGenerateHTMLContext.Create(Report,Nil,DPI);
+  try
+    FContext.SequenceFormat:=Self.SequenceFormat;
+    FContext.SequenceDigits:=2;
+    FContext.TotalPageCount:=ImageFiles.Count;
+    Context.BaseFileName:=ChangeFileExt(Self.BaseFileName,'.html');
+    HFN:=TStringList.Create;
+    For I:=0 to ImageFiles.Count-1 do
+      begin
+      FContext.CurrentPageNo:=I;
+      HFN.Add(CreateHTMLFileForImage(ImageFiles[i],TFPReportPage(ImageFiles.Objects[i]),I+1));
+      end;
+   if (hoTOCPage in HTMLOptions) then
+     FN:=CreateTOCPage(HFN,(hoFramePage in HTMLOptions));
+    If hoFramePage in HTMLOptions then
+      CreateFramePage(HFN,FN);
+  finally
+    FreeAndNil(FContext);
+    FreeAndNil(HFN);
+  end;
+end;
+
+procedure TFPReportExportfpImage.SetFileName(const aFileName: String);
+begin
+  BaseFileName:=aFileName;
+end;
+
+procedure TFPReportExportfpImage.RenderElement(ABand : TFPReportCustomBand; Element : TFPReportElement);
+
+Var
+  C : TFPReportPoint;
+
+begin
+  if Element is TFPReportCustomMemo then
+    RenderMemo(Aband,TFPReportCustomMemo(Element))
+  else if Element is TFPReportCustomShape then
+    RenderShape(ABand,TFPReportCustomShape(Element))
+  else if Element is TFPReportCustomImage then
+    RenderImage(Aband,TFPReportCustomImage(Element))
+  else if Element is TFPReportCustomCheckbox then
+    RenderCheckbox(ABand,TFPReportCustomCheckbox(Element))
+  else
+    begin
+    C.Left := ABand.RTLayout.Left + Element.RTLayout.Left;
+    C.Top := ABand.RTLayout.Top + Element.RTLayout.Top ; // + Element.RTLayout.Height;
+    RenderFrame(ABand, Element.Frame, C, Element.RTLayout.Width, Element.RTLayout.Height);
+    C.Left:=aband.RTLayout.Left;
+    C.Top:=aband.RTLayout.Top;
+    RenderUnknownElement(C,Element,Dpi);
+    end;
+end;
+
+procedure TFPReportExportfpImage.SetBaseFileName(AValue: string);
+
+{$IF NOT (FPC_FULLVERSION >= 30101)}
+ function FindWriterFromExtension(extension: String): TFPCustomImageWriterClass;
+ 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 := ImageWriter[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;
+ {$ELSE}
+ function FindWriterFromExtension(extension: String): TFPCustomImageWriterClass;
+ begin
+   Result:=TFPCustomImage.FindWriterFromExtension(extension);
+ end;
+ {$ENDIF}
+
+Var
+  Ext : String;
+
+begin
+  if FBaseFileName=AValue then Exit;
+  Ext:=ExtractFileExt(AValue);
+  FWriterClass:=FindWriterFromExtension(Ext);
+  if (FWriterClass=Nil) then
+     begin
+     Delete(Ext,1,1);
+     Raise EReportExportError.CreateFmt('Image format "%s" not supported.',[Ext]);
+     end;
+  FBaseFileName:=AValue;
+end;
+
+procedure TFPReportExportfpImage.SetCanvas(AValue: TFPCustomCanvas);
+begin
+  if FCanvas=AValue then Exit;
+  FCanvas:=AValue;
+  if Assigned(FHelper) then
+    FHelper.Canvas:=aValue;
+end;
+
+procedure TFPReportExportfpImage.SetDPI(AValue: integer);
+begin
+  if FDPI=AValue then Exit;
+  FDPI:=AValue;
+  if Assigned(FHelper) then
+    FHelper.DPI:=AValue;
+end;
+
+procedure TFPReportExportfpImage.SetFramePage(AValue: TFramePageOptions);
+begin
+  if FFramePage=AValue then Exit;
+  FFramePage.Assign(AValue);
+end;
+
+procedure TFPReportExportfpImage.SetPageNavigator(AValue: TPageNavigatorOptions);
+begin
+  if FPageNavigator=AValue then Exit;
+  FPageNavigator.Assign(AValue);
+end;
+
+procedure TFPReportExportfpImage.SetTOCPage(AValue: TTOCPageOptions);
+begin
+  if FTOCPage=AValue then Exit;
+  FTOCPage.Assign(AValue);
+end;
+
+procedure TFPReportExportfpImage.RenderImage(aRect: TFPReportRect; var AImage: TFPCustomImage);
+
+begin
+  FHelper.RenderImage(aRect,aImage);
+end;
+
+procedure TFPReportExportfpImage.RenderBand(Aband: TFPReportCustomBand);
+
+Var
+  lPt1: TFPReportPoint;  // original Report point
+  I : integer;
+
+begin
+  lPt1.Left := Aband.RTLayout.Left;
+  lPt1.Top := Aband.RTLayout.Top;
+  RenderFrame(Aband, Aband.Frame, lPt1, Aband.RTLayout.Width, Aband.RTLayout.Height);
+  for I := 0 to Aband.ChildCount-1 do
+    RenderElement(ABAnd,Aband.Child[i]);
+end;
+
+procedure TFPReportExportfpImage.DoExecute(const ARTObjects: TFPList);
+var
+  p, b: integer;
+  rpage: TFPReportPage;
+  ImageFiles : TStringList;
+  D : String;
+begin
+  HTMLOptions:=[hoEnabled,hoTOCPage,hoFramePage,hoExternalJS];
+  BaseFileName:='mydemo/image.png';
+  D:=ExtractFilePath(BaseFileName);
+  if (D<>'') and not DirectoryExists(D) then
+    If not ForceDirectories(D) then
+      Raise EReportError.CreateFmt('Cannot create output directory "%s"',[D]);
+  if SequenceFormat='' then
+    Sequenceformat:='%.2d';
+  ImageFiles:=TStringList.Create;
+  try
+    for p := 0 to (ARTObjects.Count - 1) do { pages }
+      begin
+      rpage := TFPReportPage(ARTObjects[p]);
+      SetupPageRender(rpage);
+      for b := 0 to (rpage.BandCount - 1) do
+        RenderBand(rpage.Bands[b]);
+      ImageFiles.AddObject(BufferToFile(p+1),rPage);
+      end;
+    if hoEnabled in HTMLOptions then
+      GenerateHTML(ImageFiles);
+  finally
+    FreeAndNil(ImageFiles)
+  end;
+end;
+
+initialization
+  TFPReportExportfpImage.RegisterExporter;
+end.
+

+ 1293 - 0
packages/fcl-report/src/fpreporthtmlexport.pp

@@ -0,0 +1,1293 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2016 Michael Van Canneyt,
+    member of the Free Pascal development team
+
+    FPReport FPImage export filter.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+{
+  HTML Export filter.
+
+  FPImage is included as standard with FPC. This exporter uses those classes
+  to generate image output. 
+
+}
+unit fpreporthtmlexport;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpReport,
+  fpimage,
+  fpcanvas,
+  contnrs,
+  FPImgCmn,
+  dom,
+  dom_html,
+  HTMWrite,
+  fpreportcanvashelper,
+  fpreporthtmlutil,
+  fpimgcanv;
+
+type
+  TStyleElement = (sePosition,seSize, seFrame, seBackGroundColor);
+  TStyleElements = Set of TStyleElement;
+
+Const
+  seAll = [sePosition,seSize, seFrame, seBackGroundColor];
+
+Type
+  THTMLExportOption = (heoFixedPositioning,heoInlineImage,heoUseIMGtag,heoTOCPage,heoTOCPageFrame,heoMemoAsIs,heoExternalJS);
+  THTMLExportOptions = set of THTMLExportOption;
+
+
+  { TFixedOffset }
+
+  TFixedOffset  = Class(TPersistent)
+  private
+    FLeft: Integer;
+    FTop: Integer;
+  Public
+    Procedure Assign(Source : TPersistent) ; override;
+  Published
+    Property Left : Integer Read FLeft Write FLeft;
+    Property Top : Integer Read FTop Write FTop;
+  end;
+
+
+
+  THTMLCanvasHelper = Class(TFPReportCanvasHelper)
+  Public
+    Procedure RenderShape(AImage : TFPCustomImage; S : TFPReportCustomShape); overload;
+  end;
+
+  { TFPReportExportHTML }
+
+  TFPReportExportHTML = class(TFPReportExporter)
+  private
+    FBaseFileName: string;
+    FCurrentBand: TFPReportCustomBand;
+    FCurrentBandDIV: THTMLElement;
+    FCurrentDIV: THTMLElement;
+    FCurrentElement: TFPReportElement;
+    FCurrentPage: TFPReportPage;
+    FDoc: THTMLDocument;
+    FFixedOffset: TFixedOffset;
+    FFramePage: TFramePageOptions;
+    FOptions: THTMLExportOptions;
+    FPageNavigator: TPageNavigatorOptions;
+    FPictureDir: String;
+    FCSSDir : String;
+    FHeadElement,
+    FBodyElement,
+    FTitleElement: THTMLElement;
+    FSequenceDigits: Integer;
+    FScript: TStrings;
+    FStyleEmbedding: TStyleEmbedding;
+    FDPI: integer;
+    FSequenceFormat: string;
+    FImageIDCount : Integer;
+    FBasePageFileName : String;
+    FStyleContent : Tstrings;
+    FPageNames : TStrings;
+    FReportImages : TFPStringHashTable;
+    FTOCPage: TTOCPageOptions;
+    FCanvasHelper : THTMLCanvasHelper;
+    FCustomKeyCount: Integer;
+    FContext : TGenerateHTMLContext;
+    class function ColorToRGBString(AColor: TFPReportColor; UseHex: Boolean): String;
+    function GetCurrentPageNo: Integer;
+    function GetRunOffsetX: Integer;
+    function GetRunOffsetY: Integer;
+    function GetTotalPageCount: Integer;
+    procedure SetFixedOffset(AValue: TFixedOffset);
+    procedure SetFramePage(AValue: TFramePageOptions);
+    procedure SetPageNavigator(AValue: TPageNavigatorOptions);
+    procedure SetTocPage(AValue: TTOCPageOptions);
+  protected
+    function CreateContext(AReport: TFPCustomReport; ADoc: THTMLDocument): TGenerateHTMLContext;
+    // General setup and entry point
+    procedure DoExecute(const ARTObjects: TFPList); override;
+    function InitSequence(ARTObjects: TFPList): Boolean; virtual;
+    Procedure CreateDirs; virtual;
+    function CreateTOCPageOptions: TTOCPageOptions; virtual;
+    function CreateFramePageOptions: TFramePageOptions; virtual;
+    function CreatePageNavigatorOptions: TPageNavigatorOptions; virtual;
+    procedure AllocateHelpers; virtual;
+    procedure ReleaseHelpers; virtual;
+    // General HTML generation
+    function FontNameToFontFamilyAndStyle(const AFontName: String; out aFamilyName, AStyle, aWeight: String): Boolean;
+    function AllocateID(aElement: THTMLElement; AReportElement: TFPReportElement): String; virtual;
+    function AllocatePageName(APageNo: Integer): string; virtual;
+    procedure ElementToStyle(aDiv: THTMLElement; AElement: TFPReportElement; StyleElements: TStyleElements=seAll; ABand : TFPReportCustomBand = Nil; AExtra : String = ''); virtual;
+    function CreateDiv(aParent: TDOMElement; AReportElement: TFPReportElement = Nil): THTMLElement;virtual;
+    function CreateDiv(aParent: TDOMElement; AID : String): THTMLElement;virtual;
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: String);virtual;
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: TStrings);virtual;
+    function LayoutToPosition(ALayout,AParentLayout: TFPReportLayout): String; virtual;
+    function LayoutToSize(ALayout: TFPReportLayout): String; virtual;
+    function ColorToStyle(aName: String; AColor: TFPReportColor): String;virtual;
+    function CalcFontSize(aSizeInPoints: Integer): Integer; virtual;
+    function mmToPixels(const AValue: TFPReportUnits): Integer; virtual;
+    procedure WriteDefaultScript; virtual;
+    // Image support
+    function CreateScaledImg(Img: TFPCustomImage; ALayout: TFPReportLayout): TFPCustomImage; virtual;
+    function CreateScaledImg(Img: TFPCustomImage; ARect: TFPReportRect): TFPCustomImage; virtual;
+    function CreateScaledImg(Img: TFPCustomImage; W, H: Integer): TFPCustomImage;
+    function AllocateImageFileName(AIndex: Integer): String;virtual;
+    function CreateImageData(Img: TFPCustomImage; AIndex: integer=-1): String;virtual;
+    procedure RenderImageFromData(aDiv: THTMLElement; aEl: TFPReportElement; const ABand: TFPReportCustomBand; const Data: String); virtual;
+    // Frame page
+    procedure CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String); virtual;
+    // TOC page
+    function CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String; virtual;
+    // Navigator
+    Procedure CreatePageNavigator(aPosition : TNavigatorPosition; AParentDiV : THTMLElement); virtual;
+    // Page generation
+    procedure SetupPageVariables(aPage: TFPReportPage; APageNo: Integer); virtual;
+    procedure CleanUpPageVariables; virtual;
+    Function SetupPageRender(const APage: TFPReportPage) : THTMLElement; virtual;
+    procedure RenderPage(aDiv: THTMLElement; APage: TFPReportPage); virtual;
+    procedure PageToFile; virtual;
+    // Various elements
+    procedure RenderBand(Aband: TFPReportCustomBand); virtual;
+    procedure RenderElement(ABand: TFPReportCustomBand; Element: TFPReportElement); virtual;
+    procedure RenderMemo(const ABand: TFPReportCustomBand; const AMemo: TFPReportCustomMemo); virtual;
+    procedure RenderShape(const ABand: TFPReportCustomBand; const AShape: TFPReportCustomShape); virtual;
+    procedure RenderImage(const ABand: TFPReportCustomBand; const AImage: TFPReportCustomImage); virtual; overload;
+    Procedure RenderImage(aPos : TFPReportRect; var AImage: TFPCustomImage) ; override; overload;
+    procedure RenderCheckbox(const ABand: TFPReportCustomBand; const ACheckbox: TFPReportCustomCheckbox); virtual;
+    // Some properties which remain constant during generation of 1 page.
+    property RunOffsetX : Integer Read GetRunOffsetX;
+    property RunOffsetY : Integer Read GetRunOffsetY;
+    Property PageDoc : THTMLDocument Read FDoc;
+    property PageHeadElement : THTMLElement Read FHeadElement;
+    property PageBodyElement : THTMLElement Read FBodyElement;
+    property PageTitleElement : THTMLElement Read FTitleElement;
+    Property TotalPageCount : Integer Read GetTotalPageCount;
+    Property CurrentPageNo : Integer Read GetCurrentPageNo;
+    Property CurrentPage : TFPReportPage Read FCurrentPage;
+    Property CurrentPageScript : TStrings Read FScript;
+    Property CurrentPageFileName : String Read FBasePageFileName;
+    Property SequenceDigits : Integer Read FSequenceDigits;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Class Function Name : String; override;
+    Class Function Description : String; override;
+    Class Function DefaultExtension: String; override;
+    Procedure SetFileName(const aFileName: String); override;
+    // Helper for color strings
+    // Current element DIV
+    Property CurrentDIV : THTMLElement Read FCurrentDIV;
+    // Current band DIV
+    Property CurrentBandDIV : THTMLElement Read FCurrentBandDIV;
+    // CurrentElement. Set while RenderElement is called
+    Property CurrentElement : TFPReportElement Read FCurrentElement;
+    // CurrentBand. Set while RenderBand is called
+    Property CurrentBand : TFPReportCustomBand Read FCurrentBand;
+  published
+    // Base filename. Can contain path.
+    property BaseFileName : string read FBaseFileName write FBaseFileName;
+    // Frame Page options.
+    property FramePage : TFramePageOptions Read FFramePage Write SetFramePage;
+    // TOC Page options.
+    property TOCPage : TTOCPageOptions Read FTOCPage Write SetTocPage;
+    // Relative to path of base filename.
+    Property PictureDir : String Read FPictureDir Write FPictureDir;
+    // Relative to path of base filename.
+    Property CSSDir : String Read FCSSDir Write FCSSDir;
+    // Format for formatting file numbers.
+    property SequenceFormat : string read FSequenceFormat write FSequenceFormat;
+    // DPI to use when converting to pixels
+    property DPI: integer read FDPI write FDPI;
+    // How to embed CSS
+    property StyleEmbedding : TStyleEmbedding Read FStyleEmbedding Write FStyleEmbedding;
+    // Various miscellaneous options
+    Property Options : THTMLExportOptions Read FOptions Write FOptions;
+    // Offset to be used when using fixed positioning.
+    Property FixedOffset : TFixedOffset Read FFixedOffset Write SetFixedOffset;
+    // Page navigator options
+    Property PageNavigator : TPageNavigatorOptions Read FPageNavigator Write SetPageNavigator;
+  end;
+
+implementation
+
+uses
+  // ftFont,
+  fpTTF,
+  fpparsettf,
+  base64,
+  fpwritepng;
+
+//  RGBA_Width = 4;
+
+type
+
+  { for access to Protected methods }
+  TReportImageFriend = class(TFPReportCustomImage);
+  TReportFriend = class(TFPCustomReport);
+
+
+  { THTMLExportfpImage }
+
+
+
+{ THTMLExportfpImage }
+
+procedure THTMLCanvasHelper.RenderShape(AImage: TFPCustomImage; S: TFPReportCustomShape);
+
+Var
+  L : TFPReportPoint;
+
+begin
+  Canvas:=TFPImageCanvas.Create(AImage);
+  try
+    L.Top:=0;
+    L.Left:=0;
+    Canvas.Brush.Style:=bsSolid;
+    Canvas.Brush.FPColor:=colWhite;// colTransparent;
+    Canvas.Clear;
+    RenderShape(L,S);
+  finally
+    Canvas.Free;
+    Canvas:=Nil;
+  end;
+end;
+
+{ TFixedOffset }
+
+procedure TFixedOffset.Assign(Source: TPersistent);
+Var
+  O : TFixedOffset;
+
+begin
+  if (Source is TFixedOffset) then
+    begin
+    O:=Source as TFixedOffset;
+    Top:=O.Top;
+    Left:=O.Left;
+    end;
+  inherited Assign(Source);
+end;
+
+
+{ TFPReportExportHTML }
+
+{ ---------------------------------------------------------------------
+  General routines
+  ---------------------------------------------------------------------}
+class function TFPReportExportHTML.Name: String;
+begin
+  Result:='HTML';
+end;
+
+class function TFPReportExportHTML.Description: String;
+begin
+  Result:='HTML files export';
+end;
+
+class function TFPReportExportHTML.DefaultExtension: String;
+begin
+  Result:='.html';
+end;
+
+procedure TFPReportExportHTML.SetFileName(const aFileName: String);
+begin
+  BaseFileName:=aFileName;
+end;
+
+constructor TFPReportExportHTML.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  BaseFileName:=ApplicationName + '-.html';
+  SequenceFormat := '%.2d';
+  FDPI := 96;
+  FFramePage:=CreateFramePageOptions;
+  FTOCPage:=CreateTOCPageOptions;
+  FFixedOffset:=TFixedOffset.Create;
+  FPageNavigator:=CreatePageNavigatorOptions;
+end;
+
+destructor TFPReportExportHTML.Destroy;
+
+begin
+  ReleaseHelpers; // To be sure
+  FreeAndNil(FContext);
+  FreeAndNil(FFixedOffset);
+  FreeAndNil(FTOCPage);
+  FreeAndNil(FFramePage);
+  FreeAndNil(FPageNavigator);
+  FreeAndNil(FStyleContent);
+  inherited Destroy;
+end;
+
+procedure TFPReportExportHTML.SetFramePage(AValue: TFramePageOptions);
+begin
+  if FFramePage=AValue then Exit;
+  FFramePage.Assign(AValue);
+end;
+
+procedure TFPReportExportHTML.SetPageNavigator(AValue: TPageNavigatorOptions);
+begin
+  if FPageNavigator=AValue then Exit;
+  FPageNavigator.Assign(AValue);
+end;
+
+procedure TFPReportExportHTML.SetFixedOffset(AValue: TFixedOffset);
+begin
+  if FFixedOffset=AValue then Exit;
+  FFixedOffset.Assign(AValue);
+end;
+
+function TFPReportExportHTML.GetRunOffsetX: Integer;
+begin
+  Result:=FContext.RunOffsetX;
+end;
+
+function TFPReportExportHTML.GetRunOffsetY: Integer;
+begin
+  Result:=FContext.RunOffsetY;
+end;
+
+function TFPReportExportHTML.GetTotalPageCount: Integer;
+begin
+  Result:=FContext.TotalPageCount;
+end;
+
+procedure TFPReportExportHTML.SetTocPage(AValue: TTOCPageOptions);
+begin
+  if FTocPage=AValue then Exit;
+  FTocPage.Assign(AValue);
+end;
+
+function TFPReportExportHTML.CreateContext(AReport : TFPCustomReport; ADoc : THTMLDocument): TGenerateHTMLContext;
+begin
+  Result:=TGenerateHTMLContext.Create(AReport,ADoc,DPI);
+  Result.CSSDir:=CSSDir;
+  Result.BaseFileName:=BaseFileName;
+  Result.SequenceFormat:=SequenceFormat;
+end;
+
+function TFPReportExportHTML.CreateTOCPageOptions: TTOCPageOptions;
+begin
+  Result:=TTOCPageOptions.Create;
+end;
+
+function TFPReportExportHTML.CreateFramePageOptions: TFramePageOptions;
+
+begin
+  Result:=TFramePageOptions.Create;
+  Result.TOCZoneSize:=10;
+end;
+
+function TFPReportExportHTML.CreatePageNavigatorOptions: TPageNavigatorOptions;
+begin
+  Result:=TPageNavigatorOptions.Create;
+  Result.Positions:=[npTop];
+  Result.Options:=[hnoFirstLast,hnoPageNo];
+  Result.FixedWidth:=80;
+  Result.FixedHeight:=32;
+  Result.FixedMargin:=8;
+end;
+
+Function TFPReportExportHTML.InitSequence(ARTObjects : TFPList) : Boolean;
+
+Var
+  P,D,MC : Integer;
+
+begin
+  Result:=(SequenceFormat='');
+  // Determine number of zeroes in filenames
+
+  if Result then
+    begin
+    if TReportFriend(Report).Images.Count>ARTObjects.Count then
+      MC:=TReportFriend(Report).Images.Count
+    else
+      MC:=ARTObjects.Count;
+    D:=1;
+    While MC>10 do
+      begin
+      Inc(D);
+      MC:=MC div 10;
+      end;
+    FSequenceDigits:=D;
+    SequenceFormat:='%.'+IntToStr(D)+'d';
+    end
+  else
+    begin
+    P:=Pos('%.',SequenceFormat);
+    if P=0 then
+      FSequenceDigits:=0
+    else
+      FSequenceDigits:=StrToIntDef(Copy(SequenceFormat,P+2,1),0);
+    end;
+end;
+
+procedure TFPReportExportHTML.DoExecute(const ARTObjects: TFPList);
+var
+  X,Y,P,B : integer;
+  rpage: TFPReportPage;
+  PageDiv : THTMLElement;
+  FN : String;
+  SequenceEmpty,BaseEmpty : Boolean;
+
+begin
+//  Options:=Options+[heoExternalJS,heoTOCPageFrame];
+//  PageNavigator.Options:=PageNavigator.Options+[hnoPageNoEdit,hnoUsePageNOfM];
+//  BaseFileName:='shapedemos/report.html';
+  // Check base name
+  BaseEmpty:=BaseFileName='';
+  if BaseEmpty then
+    BaseFileName:=ApplicationName+'-.html';
+  SequenceEmpty:=InitSequence(ARTObjects);
+  FreeAndNil(FContext);
+  FContext:=CreateContext(Report,FDoc);
+  X:=FixedOffset.Left;
+  if (npLeft in PageNavigator.Positions) then
+    X:=X+PageNavigator.FixedWidth+PageNavigator.FixedMargin;
+  Y:=FixedOffset.Top;
+  if (npTop in PageNavigator.Positions) then
+    Y:=Y+PageNavigator.FixedHeight+PageNavigator.FixedMargin;
+  FContext.InitOffsets(X,Y);
+  FCustomKeyCount:=0;
+  try
+    CreateDirs;
+    AllocateHelpers;
+    FContext.TotalPageCount:=ARTObjects.Count;
+    for p := 0 to (ARTObjects.Count - 1) do { pages }
+      begin
+      rpage := TFPReportPage(ARTObjects[p]);
+      SetUpPageVariables(rPage,P+1);
+      try
+        PageDiv:=SetupPageRender(rpage);
+        if npTop in PageNavigator.Positions then
+          CreatePageNavigator(npTop,PageBodyElement);
+        if npLeft in PageNavigator.Positions then
+          CreatePageNavigator(npLeft,PageBodyElement);
+        PageBodyElement.AppendChild(PageDiv);
+        RenderPage(PageDiv,rPage);
+        for b := 0 to (rpage.BandCount - 1) do
+          begin
+          FCurrentBandDIV:=CreateDiv(PageDiv,rpage.Bands[b]);
+          RenderBand(rpage.Bands[b]);
+          end;
+        if (npRight in PageNavigator.Positions) then
+          CreatePageNavigator(npRight,PageBodyElement);
+        if (npBottom in PageNavigator.Positions) then
+          CreatePageNavigator(npBottom,PageBodyElement);
+        PageToFile;
+      finally
+        CleanUpPageVariables;
+      end;
+      end;
+    if (([heoTOCPage,heoTOCPageFrame] * Options)<>[]) then
+      FN:=CreateTOCPage(FPageNames,(heoTOCPageFrame in Options));
+    if (heoTOCPageFrame in Options) then
+      CreateFramePage(FPageNames,FN);
+  finally
+    ReleaseHelpers;
+    if BaseEmpty then
+      BaseFileName:='';
+    if SequenceEmpty then
+      SequenceFormat:='';
+  end;
+end;
+procedure TFPReportExportHTML.CreateDirs;
+
+Var
+  BD,D : String;
+
+begin
+  BD:=ExtractFilePath(FBaseFileName);
+  if (BD<>'') then
+    If not ForceDirectories(BD) then
+      Raise EReportError.CreateFmt('Cannot create output directory "%s"',[BD]);
+  if (PictureDir<>'') then
+    begin
+    D:=BD+PictureDir;
+    If not ForceDirectories(D) then
+      Raise EReportError.CreateFmt('Cannot create picture output directory "%s"',[D]);
+    end;
+  if (CSSDir<>'') then
+    begin
+    D:=BD+CSSDir;
+    If not ForceDirectories(D) then
+      Raise EReportError.CreateFmt('Cannot create picture output directory "%s"',[BD]);
+    end;
+end;
+
+
+procedure TFPReportExportHTML.ReleaseHelpers;
+
+begin
+  FreeAndNil(FCanvasHelper);
+  FreeAndNil(FReportImages);
+  FreeAndNil(FPageNames);
+end;
+
+procedure TFPReportExportHTML.AllocateHelpers;
+
+begin
+  FPageNames:=TStringList.Create;
+  FReportImages:=TFPStringHashTable.Create;
+  FCanvasHelper:=THTMLCanvasHelper.Create(Nil,96);
+  FImageIDCount:=0;
+end;
+
+
+{ ---------------------------------------------------------------------
+  HTML Generation
+  ---------------------------------------------------------------------}
+
+function TFPReportExportHTML.mmToPixels(const AValue: TFPReportUnits): Integer;
+begin
+  Result := FContext.mmToPixels(aValue); // Round(AValue * (DPI / cInchToMM));
+end;
+
+function TFPReportExportHTML.AllocatePageName(APageNo: Integer): string;
+
+begin
+  Result:=FContext.AllocatePageName(APageNo);
+end;
+
+function TFPReportExportHTML.AllocateID(aElement: THTMLElement;AReportElement: TFPReportElement) : String;
+
+begin
+  Result:=FContext.AllocateID(aElement,aReportElement);
+end;
+
+function TFPReportExportHTML.CreateDiv(aParent: TDOMElement;
+  AReportElement: TFPReportElement): THTMLElement;
+
+begin
+  Result:=FContext.CreateDiv(AParent,AReportElement);
+end;
+
+function TFPReportExportHTML.CreateDiv(aParent: TDOMElement; AID: String): THTMLElement;
+begin
+  Result:=FContext.CreateDiv(AParent,AID);
+end;
+
+function TFPReportExportHTML.LayoutToPosition(ALayout,AParentLayout: TFPReportLayout): String;
+
+
+Var
+  P : String;
+  RL,RT : TFPReportUnits;
+  L,T : Integer;
+
+begin
+  RL:=ALayout.Left;
+  RT:=ALayout.Top;
+  if Assigned(AParentLayout) and (heoFixedPositioning in Options) then
+    begin
+    RL:=RL+AParentLayout.Left;
+    RT:=RT+AParentLayout.Top;
+    end;
+  L:=mmToPixels(RL);
+  T:=mmToPixels(RT);
+  if (heoFixedPositioning in Options) then
+    begin
+    L:=L+RunOffsetX;
+    T:=T+RunOffsetY;
+    end;
+  P:=PosStrings[heoFixedPositioning in Options];
+  Result:=Format('position: %s; left: %dpx; top: %dpx;',[P,L,T]);
+end;
+
+function TFPReportExportHTML.LayoutToSize(ALayout: TFPReportLayout): String;
+
+begin
+  Result:=Format('width: %dpx; height: %dpx;',[mmToPixels(ALayout.Width),mmToPixels(ALayout.Height)]);
+end;
+
+
+function TFPReportExportHTML.ColorToStyle(aName: String; AColor: TFPReportColor
+  ): String;
+
+
+begin
+  Result:=Format('%s: %s;',[aName,ColorToRGBString(aColor,False)]);
+end;
+
+class function TFPReportExportHTML.ColorToRGBString(AColor: TFPReportColor;
+  UseHex: Boolean): String;
+
+begin
+  Result:=TGenerateHTMLContext.ColorToRGBString(aColor,useHex);
+end;
+
+function TFPReportExportHTML.GetCurrentPageNo: Integer;
+begin
+  Result:=FContext.CurrentPageNo;
+end;
+
+function TFPReportExportHTML.CalcFontSize(aSizeInPoints: Integer): Integer;
+
+begin
+//  Result:=aSizeInPoints;
+  Result:=Round(aSizeInPoints/72*DPI);
+end;
+
+procedure TFPReportExportHTML.ElementToStyle(aDiv: THTMLElement;
+  AElement: TFPReportElement; StyleElements: TStyleElements;
+  ABand: TFPReportCustomBand; AExtra: String);
+
+Const
+  FNames : Array[TFPReportFrameLine] of string = ('top', 'bottom', 'left', 'right');
+  FStyles : Array[TFPPenStyle] of string = ('solid', 'dashed', 'dotted', 'dashed', 'dashed', 'inset', 'none','hidden');
+Var
+  Style : TStrings;
+  L : TFPreportLayout;
+  FL : TFPReportFrameLine;
+
+begin
+  Style:=TStringList.Create;
+  try
+    if (seSize in StyleElements) then
+      Style.Add(LayoutToSize(AElement.RTLayout));
+    if (sePosition in StyleElements) then
+      begin
+      if Assigned(ABand) then
+        L:=ABand.RTLayout
+      else
+        L:=Nil;
+      Style.Add(LayoutToPosition(AElement.RTLayout,L));
+      end;
+    if (seBackGroundColor in StyleElements) then
+      Style.Add(ColorToStyle('background-color',AElement.Frame.BackgroundColor));
+    if (seFrame in StyleElements) and (AElement.Frame.Color<>clNone) then
+      begin
+      for FL in TFPReportFrameLine do
+        if (AElement.Frame.Shape=fsRectangle) or (FL in AElement.Frame.Lines) then
+          begin
+          Style.Add(ColorToStyle(Format('border-%s-color',[FNames[FL]]),AElement.Frame.Color));
+          Style.Add('border-%s-width: %dpx;',[FNames[FL],AElement.Frame.Width]);
+          Style.Add('border-%s-style: %s;',[FNames[FL],FStyles[AElement.Frame.Pen]]);
+          end;
+      end;
+    if AExtra<>'' then
+      Style.Add(AExtra);
+    ApplyStyle(aDiv,Style);
+  finally
+    STyle.Free;
+  end;
+end;
+
+procedure TFPReportExportHTML.ApplyStyle(aElement: THTMLElement;
+  const aStyle: TStrings);
+
+begin
+  FContext.ApplyStyle(aElement,aStyle);
+end;
+
+procedure TFPReportExportHTML.ApplyStyle(aElement: THTMLElement;
+  const aStyle: String);
+
+begin
+  FContext.ApplyStyle(aElement,aStyle);
+end;
+
+{ ---------------------------------------------------------------------
+  Page generation
+  ---------------------------------------------------------------------}
+
+procedure TFPReportExportHTML.RenderPage(aDiv : THTMLElement; APage : TFPReportPage);
+
+Var
+  P,S: String;
+
+begin
+  // Div with edge of the page.
+  P:=PosStrings[heoFixedPositioning in Options];
+  S:=Format('position: %s; ',[P]);
+  S:=S+'border-color: #000000; border-style: ridge; border-width: 1px; ';
+  S:=S+Format('top: %dpx; left: %dpx; width: %dpx; height: %dpx; ',[RunOffsetY,RunOffsetX,mmToPixels(APage.PageSize.Width),mmToPixels(APage.PageSize.Height)]);
+  ApplyStyle(aDiv,S);
+end;
+
+
+procedure TFPReportExportHTML.SetupPageVariables(aPage: TFPReportPage;
+  APageNo: Integer);
+begin
+  FCurrentPage:=aPage;
+  FBasePageFileName:=AllocatePageName(aPageNo);
+  FPageNames.Add(FBasePageFileName);
+  FDoc := THTMLDocument.Create;
+  FScript:=TStringList.Create;
+  if (StyleEmbedding<>seInline) then
+    begin
+    FStyleContent:=TStringList.Create;
+    FContext.StyleContent:=FStyleContent
+    end
+  else
+    FContext.StyleContent:=Nil;
+  FContext.CurrentPageNo:=APageNo;
+end;
+
+procedure TFPReportExportHTML.CleanUpPageVariables;
+
+begin
+  FreeAndNil(FDoc);
+  FreeAndNil(FStyleContent);
+  FreeAndNil(FScript);
+  FCurrentPage:=Nil;
+  FContext.CurrentPageNo:=-1;
+  FBasePageFileName:='';
+end;
+
+function TFPReportExportHTML.SetupPageRender(const APage: TFPReportPage): THTMLElement;
+
+Var
+  HTMLEl,El : TDomElement;
+  D : String;
+
+begin
+  FDoc.AppendChild(FDoc.Impl.CreateDocumentType(
+    'HTML', '-//W3C//DTD HTML 4.01 Transitional//EN',
+    'http://www.w3.org/TR/html4/loose.dtd'));
+  HTMLEl := FDoc.CreateHtmlElement;
+  FDoc.AppendChild(HTMLEl);
+  FHeadElement:=FDoc.CreateHeadElement;
+  HTMLEl.AppendChild(FHeadElement);
+  El := FDoc.CreateElement('meta');
+  El['http-equiv'] := 'Content-Type';
+  El['content'] := 'text/html; charset=utf-8';
+  FHeadElement.AppendChild(El);
+  FContext.ResetPage(FDoc);
+  Case StyleEmbedding of
+    seInline : ;
+    seCSSFile :
+      begin
+      El:=FDoc.CreateElement('link');
+      El['rel']:='stylesheet';
+      D:=CSSDir;
+      if (D<>'') then
+        D:=D+'/'; // not pathdelim !
+      El['href']:=D+ChangeFileExt(ExtractFileName(FBasePageFileName),'.css');
+      FHeadElement.AppendChild(El);
+      end
+  end;
+  FTitleElement := FDoc.CreateElement('title');
+  FHeadElement.AppendChild(FTitleElement);
+  FBodyElement := FDoc.CreateElement('body');
+  HTMLEl.AppendChild(FBodyElement);
+  Result:=CreateDiv(Nil,aPage);
+end;
+
+procedure TFPReportExportHTML.WriteDefaultScript;
+
+begin
+  TPageNavigatorElementCreator.WriteDefaultScript(FContext,FScript);
+end;
+
+procedure TFPReportExportHTML.PageToFile;
+
+Var
+  Sel,El : THTMLElement;
+  F,D : String;
+
+begin
+  Case StyleEmbedding of
+    seInline : ;
+    seCSSFile :
+      begin
+      D:=ExtractFilePath(FBasePageFileName);
+      if (CSSDir<>'') then
+        D:=D+CSSDir+PathDelim; 
+      FStyleContent.SaveToFile(D+ChangeFileExt(ExtractFileName(FBasePageFileName),'.css'));
+      end;
+    seStyleTag :
+      begin;
+      El:=FDoc.CreateElement('style');
+      EL.AppendChild(FDOC.CreateTextNode(FStyleContent.Text));
+      FHeadElement.AppendChild(El);
+      end
+  end;
+  if (hnoPageNoEdit in PageNavigator.Options) then
+    WriteDefaultScript;
+  if Assigned(FScript) and (FScript.Count>0) then
+    begin
+    Sel:=FDoc.CreateElement('script');
+    Sel['type']:='application/javascript';
+    if (heoExternalJS in Options) then
+      begin
+      F:=ChangeFileExt(CurrentPageFileName,'.js');
+      FScript.SaveToFile(F);
+      Sel['src']:=ExtractFileName(F);
+      end
+    else
+      Sel.AppendChild(FDoc.CreateCDATASection(FScript.Text));
+    FHeadElement.AppendChild(Sel);
+    end;
+  WriteHTMLFile(FDoc,FBasePageFileName);
+end;
+
+procedure TFPReportExportHTML.CreatePageNavigator(aPosition: TNavigatorPosition; AParentDiV: THTMLElement);
+
+Var
+  PNEC : TPageNavigatorElementCreator;
+
+begin
+  PNEC:=TPageNavigatorElementCreator.Create(FContext,CurrentPage,PageNavigator);
+  try
+    PNec.CreatePageNavigator(aPosition,aParentDiv);
+  finally
+    PNEC.Free;
+  end;
+end;
+
+
+{ ---------------------------------------------------------------------
+  Image support
+  ---------------------------------------------------------------------}
+
+function TFPReportExportHTML.AllocateImageFileName(AIndex : Integer) : String;
+
+
+begin
+  if (AIndex<>-1) then
+    begin
+    Result:=Format(SequenceFormat,[AIndex])+'.png';
+    end
+  else
+    begin
+    Inc(FImageIDCount);
+    Result:='extra-'+Format(SequenceFormat,[FImageIDCount])+'.png';
+    end;
+  Result:=ChangeFileExt(ExtractFileName(FBaseFileName),Result);
+  if (PictureDir<>'') then
+    Result:=ExtractFilePath(Result)+StringReplace(PictureDir,'\','/',[rfReplaceAll])+'/'+ExtractFileName(Result);
+end;
+
+function TFPReportExportHTML.CreateImageData(Img: TFPCustomImage; AIndex : integer = -1): String;
+
+Var
+  P : TFPWriterPNG;
+  S : TStringStream;
+  B : TBase64EncodingStream;
+  F : TFileStream;
+
+begin
+  S:=Nil;
+  F:=Nil;
+  B:=Nil;
+  P:=TFPWriterPNG.Create;
+  try
+  //  P.UseAlpha:=True;
+    if heoInlineImage in options then
+      begin
+      S:=TStringStream.Create('');
+      B:=TBase64EncodingStream.Create(S);
+      P.ImageWrite(B,Img);
+      B.Flush;
+      Result:='data:image/png;base64,'+S.DataString;
+      end
+    else
+      begin
+      Result:=AllocateImageFileName(AIndex);
+      F:=TFileStream.Create(ExtractFilePath(FBaseFileName)+Result,fmCreate);
+      P.ImageWrite(F,Img);
+      end;
+  finally
+    F.Free;
+    P.Free;
+    S.Free;
+    B.free;
+  end
+end;
+
+procedure TFPReportExportHTML.RenderImageFromData(aDiv : THTMLElement; aEl : TFPReportElement; const ABand: TFPReportCustomBand; const Data : String);
+
+Var
+  imgEl: THTMLElement;
+
+begin
+  if (Data='') then
+    begin
+    ElementToStyle(aDiv,AEl,seAll,ABand);
+    Exit; { nothing further to do }
+    end;
+  if heoUseIMGtag in Options then
+    begin
+    imgEl:=FDoc.CreateElement('img');
+    adiv.AppendChild(imgEl);
+    imgEl['width']:=Format('%dpx',[mmToPixels(aEl.RTLayout.Width)]);
+    imgEl['height']:=Format('%dpx',[mmToPixels(aEl.RTLayout.Height)]);
+    imgEl['src']:=Data
+    end
+  else
+    ElementToStyle(aDiv,AEl,seAll,ABand,'background-image: url('+Data+'); background-repeat: no-repeat; background-position: left top; background-size: contain;');
+end;
+
+function TFPReportExportHTML.CreateScaledImg(Img: TFPCustomImage; W,H : Integer): TFPCustomImage;
+
+Var
+  Canv : TFPImageCanvas;
+
+begin
+  if (H=Img.Height) and (W=Img.Width) then
+    Result:=Img
+  else
+    begin
+    Result:=TFPCompactImgRGBA8Bit.Create(W,H);
+    Canv:=TFPImageCanvas.Create(Result);
+    try
+      Canv.StretchDraw(0,0,w,h,Img);
+    finally
+      Canv.Free;
+    end;
+    end;
+end;
+
+function TFPReportExportHTML.CreateScaledImg(Img: TFPCustomImage;
+  ALayout: TFPReportLayout): TFPCustomImage;
+
+Var
+  W,H : Integer;
+
+begin
+  H:=mmToPixels(ALayout.Height);
+  W:=mmToPixels(ALayout.Width);
+  Result:=CreateScaledImg(Img,W,H);
+end;
+
+function TFPReportExportHTML.CreateScaledImg(Img: TFPCustomImage; ARect: TFPReportRect): TFPCustomImage;
+Var
+  W,H : Integer;
+
+begin
+  H:=mmToPixels(ARect.Height);
+  W:=mmToPixels(ARect.Width);
+  Result:=CreateScaledImg(Img,W,H);
+end;
+
+{ ---------------------------------------------------------------------
+  Render elements
+  ---------------------------------------------------------------------}
+procedure TFPReportExportHTML.RenderBand(Aband: TFPReportCustomBand);
+
+Var
+  I : integer;
+
+begin
+  ElementToStyle(CurrentBandDiv,aBand);
+  try
+    for I := 0 to Aband.ChildCount-1 do
+      begin
+      FCurrentElement:=Aband.Child[i];
+      FCurrentDIV:=CreateDiv(CurrentBandDiv,FCurrentElement);
+      CurrentBandDiv.AppendChild(FCurrentDIV);
+      RenderElement(ABand,FCurrentElement);
+      end;
+  finally
+    FCurrentDiv:=Nil;
+    FCurrentElement:=nil;
+  end;
+end;
+
+procedure TFPReportExportHTML.RenderElement(ABand : TFPReportCustomBand; Element : TFPReportElement);
+
+Var
+  C : TFPReportPoint;
+begin
+  if Element is TFPReportCustomMemo then
+    RenderMemo(Aband,TFPReportCustomMemo(Element))
+  else if Element is TFPReportCustomShape then
+    RenderShape(ABand,TFPReportCustomShape(Element))
+  else if Element is TFPReportCustomImage then
+    RenderImage(Aband,TFPReportCustomImage(Element))
+  else if Element is TFPReportCustomCheckbox then
+    RenderCheckbox(ABand,TFPReportCustomCheckbox(Element))
+  else
+    begin
+    // C.Left := ABand.RTLayout.Left + Element.RTLayout.Left;
+    // C.Top := ABand.RTLayout.Top + Element.RTLayout.Top ; // + Element.RTLayout.Height;
+    // (ABand, Element.Frame, C, Element.RTLayout.Width, Element.RTLayout.Height);
+    C.Left:=aband.RTLayout.Left;
+    C.Top:=aband.RTLayout.Top;
+    RenderUnknownElement(C,Element,Dpi);
+    end;
+end;
+
+function TFPReportExportHTML.FontNameToFontFamilyAndStyle(
+  const AFontName: String; out aFamilyName, AStyle, aWeight: String): Boolean;
+
+Var
+  CI : TFPFontCacheItem;
+
+begin
+  CI:=gTTFontCache.Find(AFontName);
+  Result:=Assigned(CI);
+  if Result then
+    begin
+    aFamilyName:=CI.FamilyName;
+    if CI.IsBold then
+      AWeight:='bold';
+    if CI.IsItalic then
+      AStyle:='italic';
+    end
+  else
+    begin
+    aFamilyName:=AFontName;
+    aStyle:='';
+    aWeight:='';
+    end
+end;
+
+procedure TFPReportExportHTML.RenderMemo(const ABand: TFPReportCustomBand; const AMemo: TFPReportCustomMemo);
+
+Const
+  SAligns : Array[TFPReportHorzTextAlignment] of string
+          =  ('left', 'right', 'center', 'justify');
+
+var
+  lPt1: TFPReportPoint;  // original Report point
+  lMemo: TFPReportMemo;
+  i,xOffset,yOffset: integer;
+  txtblk: TFPTextBlock;
+  BS,S,aFamily,aStyle,aWeight : String;
+  bDiv,span : THTMLElement;
+  FixedPos : Boolean;
+
+begin
+  lMemo := TFPReportMemo(AMemo);
+  FixedPos:=heoFixedPositioning in Options;
+  { Store the Top-Left coordinate of the Memo. We will be reusing this info. }
+  if FixedPos then
+    begin
+    lPt1.Left := ABand.RTLayout.Left + AMemo.RTLayout.Left;
+    lPt1.Top := ABand.RTLayout.Top + AMemo.RTLayout.Top;
+    XOffSet:=RunOffsetX;
+    YOffset:=RunOffsetY;
+    end
+  else
+    begin
+    lPt1.Left := 0;
+    lPt1.Top := 0;
+    XOffset:=0;
+    YOffset:=0;
+    end;
+  BS:=Format('position: %s; ',[PosStrings[FixedPos]]);
+  BS:=BS+Format(' font-size: %dpx;',[CalcFontSize(lMemo.Font.Size)]);
+  // BS:=BS+' overflow: hidden;';
+  { Frame must be drawn before the text as it could have a fill color. }
+//  Writeln('Memo : ',lMemo.Text);
+  if (heoMemoAsIs in Options) then
+    begin
+    S:=Format('position: %s; ',[PosStrings[FixedPos]]);
+    S:=S+Format(' font-size: %dpx;',[CalcFontSize(lMemo.Font.Size)]);
+    if FontNameToFontFamilyAndStyle(lMemo.Font.Name,aFamily,aStyle,AWeight) then
+      begin
+      S:=S+Format(' font-family: "%s";',[aFamily]);
+      if (aStyle<>'') then
+        S:=S+Format(' font-style: %s;',[aStyle]);
+      if (aWeight<>'') then
+        S:=S+Format(' font-weight: %s;',[aWeight]);
+      end
+    else // Hope for the best...
+      S:=S+Format(' font-family: "%s";',[lMemo.Font.Name]);
+// TODO: Vertical
+// TODO: LineSpacing;
+    S:=S+Format('text-align: %s;',[SAligns[lMemo.TextAlignment.Horizontal]]);
+    S:=S+Format('padding: %dpx %dpx %dpx %dpx;',[
+                 mmToPixels(lMemo.TextAlignment.TopMargin),
+                 mmToPixels(lMemo.TextAlignment.RightMargin),
+                 mmToPixels(lMemo.TextAlignment.BottomMargin),
+                 mmToPixels(lMemo.TextAlignment.LeftMargin)
+    ]);
+
+    ElementToStyle(CurrentDiv,AMemo,seAll-[seBackGroundColor],ABand,S);
+    span:=FDoc.CreateSpanElement;
+    CurrentDiv.appendChild(FDoc.CreateTextNode(lMemo.Text));
+    end
+  else
+    begin
+    ElementToStyle(CurrentDiv,AMemo,seAll-[seBackGroundColor],ABand);
+    { render the TextBlocks as-is. }
+    for i := 0 to lMemo.TextBlockList.Count-1 do
+      begin
+      txtblk := lMemo.TextBlockList[i];
+      bDiv:=CreateDiv(CurrentDiv,lMemo);
+      S:=BS+Format('top: %.dpx; left: %.dpx;',[YoffSet+mmToPixels(lPt1.Top+txtblk.Pos.Top-txtblk.Descender),XOffset+mmToPixels(lPt1.Left+txtblk.Pos.Left)]);
+      S:=S+Format('width: %.dpx; height: %.dpx;',[mmToPixels(txtblk.Width),mmToPixels(txtblk.Height+txtblk.Descender)]);
+      if FontNameToFontFamilyAndStyle(txtblk.FontName,aFamily,aStyle,AWeight) then
+        begin
+        S:=S+Format(' font-family: "%s";',[aFamily]);
+        if (aStyle<>'') then
+          S:=S+Format(' font-style: %s;',[aStyle]);
+        if (aWeight<>'') then
+          S:=S+Format(' font-weight: %s;',[aWeight]);
+        end
+      else // Hope for the best...
+        S:=S+Format(' font-family: "%s";',[txtblk.FontName]);
+      S:=S+ColorToStyle('color',TxtBlk.FGColor);
+      ApplyStyle(bDiv,S);
+      span:=FDoc.CreateSpanElement;
+      span.appendChild(FDoc.CreateTextNode(txtBlk.Text));
+      bDiv.AppendChild(span);
+      end;
+    end;
+end;
+
+
+procedure TFPReportExportHTML.RenderShape(const ABand: TFPReportCustomBand; const AShape: TFPReportCustomShape);
+
+var
+  key,data : string;
+  H,W : Integer;
+  Img : TFPCustomImage;
+
+begin
+  { Frame must be drawn before the text as it could have a fill color. }
+  Key:=AShape.CreatePropertyHash;
+  Data:=FReportImages.Items[Key];
+  if (Data='') then
+    begin
+    H:=mmToPixels(AShape.RTLayout.Height)+1;
+    W:=mmToPixels(AShape.RTLayout.Width)+1;
+    Img:=TFPMemoryImage.Create(W,H);
+    try
+      FCanvasHelper.RenderShape(Img,AShape);
+      Data:=CreateImageData(Img,-1);
+    finally
+      Img.Free;
+    end;
+    FReportImages.Add(Key,Data);
+    end;
+  RenderImageFromData(CurrentDiv,AShape,ABand,Data)
+end;
+
+procedure TFPReportExportHTML.RenderImage(const ABand: TFPReportCustomBand; const AImage: TFPReportCustomImage);
+var
+  img: TReportImageFriend;
+  I,IID : Integer;
+  Key,Data : string;
+
+begin
+  img := TReportImageFriend(AImage);  { for access to Protected methods }
+  // Determine image data
+  if Assigned(img.Image) then
+    begin
+    IID:=img.ImageID;
+    if (IID>=0) then
+      begin
+      I:=TReportFriend(Report).images.GetIndexFromID(IID);
+      Key:=Format('img%d',[i]);
+      Data:=FReportImages.Items[Key];
+      if (Data='') then
+        begin
+        Data:=CreateImageData(TReportFriend(Report).Images[i].Image,I);
+        FReportImages.Add(Key,Data);
+        end;
+      end;
+    end;
+  RenderImageFromData(CurrentDiv,AImage,ABand,Data)
+end;
+
+procedure TFPReportExportHTML.RenderImage(aPos: TFPReportRect;
+  var AImage: TFPCustomImage);
+
+var
+  Data : String;
+  ScaledImg : TFPCustomImage;
+
+begin
+  ScaledImg:=CreateScaledImg(aImage,aPos);
+  try
+    Data:=CreateImageData(ScaledImg,-1);
+  Finally
+    If (ScaledImg<>AImage) then
+      FreeAndNil(ScaledImg);
+  end;
+  Inc(FCustomKeyCount);
+  FReportImages.Add('CustomImage'+IntToStr(FCustomKeyCount),Data);
+  RenderImageFromData(CurrentDiv,CurrentElement,CurrentBand,Data);
+end;
+
+
+procedure TFPReportExportHTML.RenderCheckbox(const ABand: TFPReportCustomBand; const ACheckbox: TFPReportCustomCheckbox);
+
+var
+  Data,Key : String;
+  ScaledImg,OrigImg : TFPCustomImage;
+
+begin
+  Key:=ACheckBox.CreatePropertyHash;
+  Data:=FReportImages.Items[Key];
+  if (Data='') then
+    begin
+    OrigImg:=ACheckBox.GetRTImage;
+    if Assigned(OrigImg) then
+      begin
+      ScaledImg:=CreateScaledImg(OrigImg,ACheckBox.RTLayout);
+      try
+        Data:=CreateImageData(ScaledImg,-1);
+      Finally
+        If (ScaledImg<>OrigImg) then
+          FreeAndNil(ScaledImg);
+      end;
+      end;
+    FReportImages.Add(Key,Data);
+    end;
+  RenderImageFromData(CurrentDiv,ACheckBox,ABand,Data)
+end;
+
+{ ---------------------------------------------------------------------
+  TOC frame
+  ---------------------------------------------------------------------}
+
+
+function TFPReportExportHTML.CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String;
+
+Var
+  TPC : TTOCPageCreator;
+
+begin
+  TPC:=TTOCPageCreator.Create(FCOntext,TOCPage);
+  try
+    Result:=TPC.CreateTOCPage(APageNames,ForFrame);
+  finally
+    TPC.Free;
+  end;
+end;
+
+{ ---------------------------------------------------------------------
+  Frame page
+  ---------------------------------------------------------------------}
+
+procedure TFPReportExportHTML.CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String);
+
+Var
+  TFCP : TTOCFramePageCreator;
+
+begin
+  TFCP:=Nil;
+  try
+    TFCP:=TTOCFramePageCreator.Create(FContext,FramePage);
+    TFCP.CreateFramePage(PageNames,ATocPageFileName);
+  finally
+    TFCP.Free;
+  end;
+end;
+
+
+
+initialization
+  TFPReportExportHTML.RegisterExporter;
+end.
+

+ 436 - 0
packages/fcl-report/src/fpreporthtmlparser.pp

@@ -0,0 +1,436 @@
+{
+  See the section LICENSE/TERMS below for details about the copyright.
+
+  TODO:
+    - OnDone event when parser has finished
+}
+{
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+                    FastHTMLParser unit to parse HTML
+                  (disect html into its tags and text.)
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ TITLE        : Fast HTML Parser (modified)
+ CLASS        : TjsFastHTMLParser
+ VERSION      : 0.5
+
+ AUTHOR       : James Azarja
+                http://www.jazarsoft.com/
+
+ CONTRIBUTORS : L505
+                http://z505.com
+
+                Graeme Geldenhuys
+                http://geldenhuys.co.uk
+
+                YourName Here...
+
+
+ LEGAL        : Copyright (C) 2004 Jazarsoft, All Rights Reserved.
+                Modified 2005 Lars (L505)
+
+--------------------------------------------------------------------------------
+
+  - Modified for use as a pure command line unit (no dialogs) for freepascal.
+  - Also added UPPERCASE tags so that when you check for <font> it returns all
+    tags like <FONT> and <FoNt> and <font>
+
+ Use it for what reasons:
+    -make your own web browsers,
+    -make your own text copies of web pages for caching purposes
+    -Grab content from websites -without- using regular expressions
+    -Seems to be MUCH MUCH FASTER than regular expressions, as it is after all
+     a true parser
+    -convert website tables into spreadsheets (parse <TD> and <TR>, turn in to
+     CSV or similar)
+    -convert websites into text files (parse all text, and tags <BR> <P> )
+    -convert website tables into CSV/Database (<parse <TD> and <TR>)
+    -find certain info from a web page.. i.e. all the bold text or hyperlinks in
+     a page.
+    -Parse websites remotely from a CGI app using something like Sockets or
+     Synapse and SynWrap to first get the HTML site. This would allow you to
+     dynamically parse info from websites and display data on your site in real
+     time.
+    -HTML editor.. WYSIWYG or a partial WYSIWYG editor. Ambitious, but possible.
+    -HTML property editor. Not completely wysiwyg but ability to edit proprties
+     of tags. Work would need to be done to parse each property in a tag.
+
+
+--------------------------------------------------------------------------------
+ LICENSE/TERMS
+--------------------------------------------------------------------------------
+
+ This code may be used and modified by anyone so long as  this header and
+ copyright  information remains intact.
+
+ The code is provided "AS-IS" and without WARRANTY OF ANY KIND,
+ expressed, implied or otherwise, including and without limitation, any
+ warranty of merchantability or fitness for a  particular purpose. 
+
+ In no event shall the author be liable for any special, incidental,
+ indirect or consequential damages whatsoever (including, without
+ limitation, damages for loss of profits, business interruption, loss
+ of information, or any other loss), whether or not advised of the
+ possibility of damage, and on any theory of liability, arising out of
+ or in connection with the use or inability to use this software.  
+
+
+--------------------------------------------------------------------------------
+ HISTORY:
+--------------------------------------------------------------------------------
+
+ 0.1     -  James:
+             Initial Development
+             mostly based on Peter Irlam works & ideas
+
+ 0.2     -  James:
+             Some minor bug has fixed
+
+ 0.3     -  James:
+             Some jsHTMLUtil function bug has been fixed
+
+ 0.4     -  James:
+             jsHTMLUtil Tag Attributes bug has been fixed
+             thanks to Dmitry [[email protected]]
+
+ 0.4L.1a -  L505:
+             Made unit work with freepascal, added UPCASE (case insensitive)
+             exec function
+
+ 0.4L.1b -  L505:
+             Changed case insensitive version to a new class instead of
+             the old ExecUpcase
+
+ 0.5     -  Graeme Geldenhuys <[email protected]>
+             New functions to extract attibute details from the tags.
+                                                                                                                                                          //
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+}
+
+{$IFDEF FPC}{$MODE DELPHI}{$H+}{$ENDIF}
+
+
+// {$DEFINE DEBUGLN_ON}
+
+unit fpReportHTMLParser;
+
+
+interface
+
+uses
+  SysUtils;
+
+
+{$IFDEF DEBUGLN_ON}
+  // dummy, default debugging
+  procedure debugproc(s: string);
+  // for custom debugging, assign this in your units 
+  var debugln: procedure(s: string) = debugproc;
+{$ENDIF}
+
+type
+
+  // when tag content found in HTML, including names and values
+  // case insensitive analysis available via NoCaseTag
+  TOnFoundTag = procedure(NoCaseTag, ActualTag: string) of object;
+
+  // when text  found in the HTML
+  TOnFoundText = procedure(Text: string) of object;
+
+  // html parser, case insensitive or case sensitive
+  THTMLParser = class(TObject)
+    private
+      FDone: Boolean;
+      function CopyBuffer(StartIndex: PChar; Length: Integer): string;
+    public
+      OnFoundTag: TOnFoundTag;
+      OnFoundText: TOnFoundText;
+      Raw: Pchar;
+      FCurrent : PChar;
+      constructor Create(sRaw: string);overload;
+      constructor Create(pRaw: PChar);overload;
+      procedure Exec;
+      procedure NilOnFoundTag(NoCaseTag, ActualTag: string);
+      procedure NilOnFoundText(Text: string);
+      function CurrentPos : Integer;
+      property Done: Boolean read FDone write FDone;
+    public
+      {****  These are utility function - not used by the HTML parser **** }
+
+      { return value of attrib, NAME case ignored, VALUE case preserved }
+      function GetVal(const tag, attribname_ci: string): string;
+      { Return tag name, case preserved }
+      function GetTagName(const Tag: string): string;
+      { Return name=value pair ignore case of NAME, preserve case of VALUE }
+      function GetNameValPair(const tag, attribname_ci: string): string;
+      { Get value of attribute, e.g WIDTH=36 returns 36, preserves case of value.
+        The value is stripped from wrapped quotes. eg: bgcolor="black" returns 'black'. }
+      function GetValFromNameVal(const namevalpair: string): string;
+  end;
+
+
+implementation
+
+
+// default debugging, do nothing, let user do his own by assigning DebugLn var
+procedure debugproc(s: string);
+begin 
+end;
+
+{ THTMLParser }
+
+function THTMLParser.CopyBuffer(StartIndex: PChar; Length: Integer): string;
+begin
+  SetLength(Result, Length);
+  StrLCopy(@Result[1], StartIndex, Length);
+end;
+
+function THTMLParser.GetVal(const tag, attribname_ci: string): string;
+var
+  nameval: string;
+begin
+  result := '';
+  if tag = '' then exit;
+  if attribname_ci = '' then exit;
+  // returns full name=value pair
+  nameval := GetNameValPair(tag, attribname_ci);
+  // extracts value portion only
+  result := GetValFromNameVal(nameval);
+end;
+
+function THTMLParser.GetTagName(const tag: string): string;
+var
+  P: Pchar;
+  S: Pchar;
+begin
+  P := Pchar(tag);
+  while P^ in ['<',' ',#9] do inc(P);
+  S := P;
+  while Not (P^ in [' ','>',#0]) do inc(P);
+  if P > S then
+    Result := CopyBuffer( S, P-S);
+end;
+
+function THTMLParser.GetNameValPair(const tag, attribname_ci: string): string;
+var
+  P: Pchar;
+  S: Pchar;
+  UpperTag,
+  UpperAttrib: string;
+  Start: integer;
+  L: integer;
+  C: char;
+begin
+  result := '';
+  if tag = '' then
+    exit;
+  if attribname_ci = '' then
+    exit;
+  // must be space before case insensitive NAME, i.e. <a HREF= STYLE=
+  UpperAttrib := ' ' + upcase(attribname_ci);
+  UpperTag := upcase(Tag);
+  P := Pchar(UpperTag);
+  S := StrPos(P, Pchar(UpperAttrib));
+
+  if S <> nil then
+  begin
+    inc(S); // skip space
+    P := S;
+
+    // Skip until hopefully equal sign
+    while not (P^ in ['=', ' ', '>', #0]) do
+      inc(P);
+
+    if (P^ = '=') then inc(P);
+
+    while not (P^ in [' ','>',#0]) do
+    begin
+      if (P^ in ['"','''']) then
+      begin
+        C:= P^;
+        inc(P); { Skip quote }
+      end
+      else
+        C:= ' ';
+
+      while not (P^ in [C, '>', #0]) do
+        Inc(P);
+
+      if (P^ <> '>') then inc(P); { Skip current character, except '>' }
+
+      break;
+    end;
+
+    L := P - S;
+    Start := S - Pchar(UpperTag);
+    P := Pchar(Tag);
+    S := P;
+    inc(S, Start);
+
+    { we use Trim() because non-ending Name/Value pairs have a trailing space }
+    result := Trim(CopyBuffer(S, L));
+  end;
+end;
+
+function THTMLParser.GetValFromNameVal(const namevalpair: string): string;
+var
+  P: Pchar;
+  S: Pchar;
+  C: Char;
+begin
+  result := '';
+  if namevalpair = '' then exit;
+  P := Pchar(namevalpair);
+  S := StrPos(P, '=');
+
+  if S <> nil then
+  begin
+    inc(S); // skip equal
+    P := S;  // set P to a character after =
+
+    if (P^ in ['"','''']) then
+    begin
+      C := P^;
+      Inc(P); { Skip current character }
+    end else
+      C := ' ';
+
+    S := P;
+    while not (P^ in [C, #0]) do
+      inc(P);
+
+    if (P <> S) then
+      Result := CopyBuffer(S, P - S)
+    else
+      Result := '';
+  end;
+end;
+
+constructor THTMLParser.Create(pRaw: Pchar);
+begin
+  if pRaw = '' then exit;
+  if pRaw = nil then exit;
+  Raw:= pRaw;
+end;
+
+constructor THTMLParser.Create(sRaw: string);
+begin
+  if sRaw = '' then exit;
+  Raw:= Pchar(sRaw);
+end;
+
+{ default dummy "do nothing" events if events are unassigned }
+procedure THTMLParser.NilOnFoundTag(NoCaseTag, ActualTag: string);
+begin 
+end;
+
+procedure THTMLParser.NilOnFoundText(Text: string);
+begin 
+end;
+
+function THTMLParser.CurrentPos: Integer;
+begin
+  if Assigned(Raw) and Assigned(FCurrent) then
+    Result:=FCurrent-Raw
+  else
+    Result:=0;
+end;
+
+procedure THTMLParser.Exec;
+var
+  L: Integer;
+  TL: Integer;
+  I: Integer;
+  TagStart,
+  TextStart,
+  P: PChar;   // Pointer to current char.
+  C: Char;
+begin
+  {$IFDEF DEBUGLN_ON}debugln('FastHtmlParser Exec Begin');{$ENDIF}
+  { set nil events once rather than checking for nil each time tag is found }
+  if not assigned(OnFoundText) then
+    OnFoundText:= NilOnFoundText;
+  if not assigned(OnFoundTag) then
+    OnFoundTag:= NilOnFoundTag;
+
+  TL:= StrLen(Raw);
+  I:= 0;
+  P:= Raw;
+  Done:= False;
+  if P <> nil then
+  begin
+    TagStart:= nil;
+    repeat
+      TextStart:= P;
+      { Get next tag position }
+      while Not (P^ in [ '<', #0 ]) do
+      begin
+        Inc(P); Inc(I);
+        if I >= TL then
+        begin
+          Done:= True;
+          Break;
+        end;
+      end;
+
+      { Is there any text before ? }
+      if (TextStart <> nil) and (P > TextStart) then
+      begin
+        L:= P - TextStart;
+        { Yes, copy to buffer }
+        FCurrent:=P;
+        OnFoundText( CopyBuffer(TextStart, L) );
+      end else
+      begin
+        TextStart:= nil;
+      end;
+      { No }
+
+      if Done then Break;
+
+      TagStart:= P;
+      while Not (P^ in [ '>', #0]) do
+      begin
+        // Find string in tag
+        if (P^ = '"') or (P^ = '''') then
+        begin
+          C:= P^;
+          Inc(P); Inc(I); // Skip current char " or '
+
+          // Skip until string end
+          while Not (P^ in [C, #0]) do
+          begin
+            Inc(P);Inc(I);
+          end;
+        end;
+
+        Inc(P);Inc(I);
+        if I >= TL then
+        begin
+          Done:= True;
+          Break;
+        end;
+      end;
+      if Done then Break;
+
+      { Copy this tag to buffer }
+      L:= P - TagStart + 1;
+
+      FCurrent:=P;
+      OnFoundTag( uppercase(CopyBuffer(TagStart, L )), CopyBuffer(TagStart, L ) );
+      Inc(P); Inc(I);
+      if I >= TL then Break;
+    until (Done);
+  end;
+  {$IFDEF DEBUGLN_ON}debugln('FastHtmlParser Exec End');{$ENDIF}
+end;
+
+
+end.
+
+
+
+

+ 897 - 0
packages/fcl-report/src/fpreporthtmlutil.pp

@@ -0,0 +1,897 @@
+unit fpreporthtmlutil;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpimage, fpreport, dom, dom_html;
+
+Type
+
+  TStyleEmbedding = (seInline,seStyleTag,seCSSFile);
+  TTOCPosition = (tpLeft,tpRight,tpTop,tpBottom);
+
+  TNavigatorPosition = (npLeft,npRight,npTop,npBottom);
+  TNavigatorPositions = set of TNavigatorPosition;
+  THTMLNavigatorOption = (hnoFirstLast,hnoAlwaysFirstLast,hnoPageNo,hnoImage,hnoSkipStyling,hnoUsePageNOfM,hnoPageNoEdit);
+  THTMLNavigatorOptions = set of THTMLNavigatorOption;
+  TNavigatorButton = (nbFirst,nbPrevious,nbPage,nbNext,nbLast);
+  TNavigatorButtons = set of TNavigatorButton;
+
+  { TGenerateHTMLContext }
+
+  TGenerateHTMLContext = Class(TObject)
+  private
+    FDPI: Integer;
+    FIDCount : Integer;
+    FBaseFileName: String;
+    FCSSDir: String;
+    FCurrentPageNo: Integer;
+    FReport: TFPCustomReport;
+    FDoc: THTMLDocument;
+    FSequenceDigits: Integer;
+    FSequenceFormat: String;
+    FStyleContent: TStrings;
+    FTotalPageCount: Integer;
+    FRunOffsetX : Integer;
+    FRunOffsetY : Integer;
+  Public
+    Constructor Create(AReport : TFPCustomReport; ADoc : THTMLDocument; ADPI : Integer);
+    Procedure Resetpage(ADoc : THTMLDocument);
+    function AllocateID(aElement: THTMLElement; AReportElement: TFPReportElement): String;
+    function CreateDiv(aParent: TDOMElement; AID: String): THTMLElement;
+    function CreateDiv(aParent: TDOMElement; AReportElement: TFPReportElement): THTMLElement;
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: TStrings);
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: String);
+    Procedure InitOffsets(aX,aY : Integer);
+    function mmToPixels(const AValue: TFPReportUnits): Integer;
+    class function ColorToRGBString(AColor: TFPReportColor; UseHex: Boolean=True): String;
+    function AllocatePageName(APageNo: Integer): string;
+    Property Report : TFPCustomReport Read FReport;
+    Property BaseFileName : String Read FBaseFileName Write FBaseFileName;
+    Property CSSDir : String Read FCSSDir Write FCSSDir;
+    Property CurrentPageNo : Integer Read FCurrentPageNo Write FCurrentPageNo;
+    Property TotalPageCount : Integer Read FTotalPageCount Write FTotalPageCount;
+    Property SequenceFormat : String Read FSequenceFormat Write FSequenceFormat;
+    Property SequenceDigits : Integer Read FSequenceDigits Write FSequenceDigits;
+    Property StyleContent : TStrings Read FStyleContent Write FStyleContent;
+    Property Doc : THTMlDocument Read FDoc;
+    Property DPI : Integer Read FDPI;
+    Property RunOffsetX : Integer Read FRunOffsetX;
+    Property RunOffsetY : Integer Read FRunOffsetY;
+
+  end;
+
+  { TTOCPageOptions }
+
+  TTOCPageOptions  = Class(TPersistent)
+  private
+    FCSSFileName: string;
+    FEvenPageStyle: string;
+    FFileName: string;
+    FOddPageStyle: string;
+    FSkipStyling: Boolean;
+  Public
+    Procedure Assign(Source : TPersistent) ; override;
+  Published
+    // TOC page filename.
+    // If empty, 'index.html' is used, unless used for frame page , then 'toc.html' is used.
+    // TOC CSS page filename.
+    Property FileName : string read FFileName write FFileName;
+    // Frame page CSS filename. If empty, no <link> is added. Relative to CSSDir
+    property CSSFileName: string read FCSSFileName write FCSSFileName;
+    // Odd page style elements.
+    property OddPageStyle : string read FOddPageStyle write FOddPageStyle;
+    // Even page style elements.
+    property EvenPageStyle : string read FEvenPageStyle write FEvenPageStyle;
+    // Skip styling alltogether
+    Property SkipStyling : Boolean Read FSkipStyling Write FSkipStyling;
+  end;
+
+  TFramePageOptions  = Class(TPersistent)
+  private
+    FCSSFileName: string;
+    FFileName: string;
+    FTOCZonePosition: TTOCPosition;
+    FTOCZoneSize: Integer;
+  Public
+    Procedure Assign(Source : TPersistent) ; override;
+  Published
+    // Frame page filename. If not set, it will be 'index.html'
+    Property FileName : string read FFileName write FFileName;
+    // Frame page CSS filename. If empty, no <link> is added. Relative to CSSDir
+    property CSSFileName: string read FCSSFileName write FCSSFileName;
+    // Size, in percentage, of the TOC zone
+    Property TOCZoneSize : Integer Read FTOCZoneSize Write FTOCZoneSize;
+    // Position of TOC zone
+    Property TOCZonePosition : TTOCPosition Read FTOCZonePosition Write FTOCZonePosition;
+  end;
+
+  { TPageNavigatorOptions }
+
+  TPageNavigatorOptions = class(TPersistent)
+  private
+    FActiveBGColor: TFPReportColor;
+    FFixedHeight: Integer;
+    FFixedMargin: Integer;
+    FFixedWidth: Integer;
+    FInActiveBGColor: TFPReportColor;
+    FOptions: THTMLNavigatorOptions;
+    FPositions: TNavigatorPositions;
+  Public
+    Procedure Assign(Source : TPersistent) ; override;
+  Published
+    Property Positions : TNavigatorPositions Read FPositions Write FPositions;
+    Property Options : THTMLNavigatorOptions Read FOptions Write FOptions;
+    Property FixedWidth : Integer Read FFixedWidth Write FFixedWidth;
+    Property FixedHeight : Integer Read FFixedHeight Write FFixedHeight;
+    Property FixedMargin : Integer Read FFixedMargin Write FFixedMargin;
+    Property ActiveBGColor : TFPReportColor Read FActiveBGColor Write FActiveBGColor;
+    Property InActiveBGColor : TFPReportColor Read FInActiveBGColor Write FInActiveBGColor;
+  end;
+
+  { TTOCPageCreator }
+
+  TTOCPageCreator = class
+  private
+    FContext: TGenerateHTMLContext;
+    FTOCPage: TTocPageOptions;
+    FDoc : THTMLDocument;
+    function GetReport: TFPCustomReport;
+  Protected
+    function CreatePageLink(APageName: String; APageNo: Integer; ADoc: THTMLDocument; ForFrame: Boolean): THTMLElement; virtual;
+  Public
+    Constructor Create(AContext : TGenerateHTMLContext; AOptions : TTocPageOptions); virtual;
+    Destructor Destroy; override;
+    function CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String; virtual;
+    Property Context : TGenerateHTMLContext read FContext;
+    Property TOCPage : TTocPageOptions Read FTOCPage;
+    Property Report : TFPCustomReport Read GetReport;
+    Property Doc : THTMLDocument Read FDoc;
+  end;
+
+  { TTOCFramePageCreator }
+
+  TTOCFramePageCreator = class
+  private
+    FFramePage: TFramePageOptions;
+    FContext : TGenerateHTMLContext;
+    FDoc : THTMLDocument;
+    function GetCSSDIR: String;
+    function GetReport: TFPCustomReport;
+  Public
+    Constructor Create(AContext : TGenerateHTMLContext; AOptions : TFramePageOptions); virtual;
+    Destructor Destroy; override;
+    procedure CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String); virtual;
+    procedure ConfigureFrameSet(FramesEl: THTMLElement); virtual;
+    Property Context : TGenerateHTMLContext read FContext;
+    Property FramePage : TFramePageOptions Read FFramePage;
+    Property Report : TFPCustomReport Read GetReport;
+    Property CSSDIR : String Read GetCSSDIR;
+  end;
+
+  { TPageNavigatorElementCreator }
+
+  TPageNavigatorElementCreator = Class
+  Private
+    FContext : TGenerateHTMLContext;
+    FCurrentPage: TFPReportPage;
+    FPageNavigator : TPageNavigatorOptions;
+    function GetDoc: THTMLDocument;
+  Protected
+    function StyleNavigatorButton(aPosition: TNavigatorPosition; AButton: TNavigatorButton; ActiveLink: Boolean): String; virtual;
+    procedure CreatePageNoNavigatorElement(AParentDiV: THTMLElement; aPosition: TNavigatorPosition); virtual;
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: TStrings); inline;
+    procedure ApplyStyle(aElement: THTMLElement; const aStyle: String); inline;
+  Public
+    Constructor Create(AContext : TGenerateHTMLContext; APage : TFPReportPage; AOptions : TPageNavigatorOptions); virtual;
+    Procedure CreatePageNavigator(aPosition : TNavigatorPosition; AParentDiV : THTMLElement); virtual;
+    procedure StyleNavigator(ND: THTMLElement; aPosition: TNavigatorPosition); virtual;
+    class procedure WriteDefaultScript(AContext: TGenerateHTMLContext; AScript: TStrings);
+    Property Context : TGenerateHTMLContext read FContext;
+    Property Doc : THTMLDocument Read GetDoc;
+    Property PageNavigator : TPageNavigatorOptions Read FPageNavigator;
+    Property CurrentPage : TFPReportPage Read FCurrentPage;
+  end;
+
+Function Coalesce(S1,S2 : String) : String;
+Function GetColorComponent(Var AColor: UInt32): Word; inline;
+Function ColorToRGBTriple(const AColor: UInt32): TFPColor;
+
+Const
+  cInchToMM = 25.4;
+  PosStrings : Array[Boolean] of string = ('absolute','fixed');
+  NavigatorPositionNames : Array[TNavigatorPosition] of string =
+          ('Left','Right','Top','Bottom');
+
+resourcestring
+  SFirstPage = 'First';
+  SPreviousPage = 'Previous';
+  SLastPage = 'Last';
+  SNextPage = 'Next';
+  SCurrentPage = 'Page %d';
+  SCurrentPageOf = 'Page %d of %d';
+  STotalPages = 'total: %d';
+  SNoFrameSupport = 'Your browser must support frames to view this';
+
+
+implementation
+
+uses htmwrite;
+
+{ Auxiliary functions }
+
+function GetColorComponent(Var AColor: UInt32): Word; inline;
+
+begin
+  Result:=AColor and $FF;
+  AColor:=AColor shr 8;
+end;
+
+
+function ColorToRGBTriple(const AColor: UInt32): TFPColor;
+
+Var
+  C : UInt32;
+
+begin
+  C:=AColor;
+  with Result do
+    begin
+    Blue  := GetColorComponent(C);
+    Green := GetColorComponent(C);
+    Red   := GetColorComponent(C);
+    Alpha := GetColorComponent(C);
+    end
+end;
+
+Function Coalesce(S1,S2 : String) : String;
+
+begin
+  if S1<>'' then
+    Result:=S1
+  else
+    Result:=S2;
+end;
+
+{ TTOCPageCreator }
+
+function TTOCPageCreator.GetReport: TFPCustomReport;
+begin
+  Result:=FContext.Report;
+end;
+
+constructor TTOCPageCreator.Create(AContext: TGenerateHTMLContext; AOptions: TTocPageOptions);
+begin
+  FContext:=AContext;
+  FTOCPage:=AOptions;
+  FDoc := THTMLDocument.Create;
+  FDoc.AppendChild(FDoc.Impl.CreateDocumentType(
+    'HTML', '-//W3C//DTD HTML 4.01 Frameset//EN"',
+    'http://www.w3.org/TR/html4/loose.dtd'));
+end;
+
+destructor TTOCPageCreator.Destroy;
+begin
+  FreeAndNil(FDoc);
+  inherited Destroy;
+end;
+
+function TTOCPageCreator.CreatePageLink(APageName : String;APageNo : Integer; ADoc : THTMLDocument; ForFrame : Boolean) : THTMLElement;
+
+Const
+  DefaultOddPageStyle = 'width: 72px; height:100px; background-color: rgb(255,241,215); border: solid; margin: 5px;';
+  DefaultEvenPageStyle = 'width: 72px; height:100px; background-color: rgb(155,155,155); border: solid; margin: 5px;';
+
+Var
+  L : THTMLLinkElement;
+  S,D : String;
+
+begin
+  Result:=ADoc.CreateElement('div');
+  Result.ID:=format('page-%d',[APageNo]);
+  if ((APageNo mod 2)=1) then
+    begin
+    S:=TOCPage.OddPageStyle;
+    D:=DefaultOddPageStyle;
+    end
+  else
+    begin
+    S:=TOCPage.EvenPageStyle;
+    D:=DefaultEvenPageStyle;
+    end;
+  if Not TOCPage.SkipStyling then
+    begin
+    S:=Coalesce(S,D);
+    if (S<>'') then
+      Result['style']:=S;
+    end;
+  L:=ADoc.CreateLinkElement;
+  L.AppendChild(ADoc.CreateTextNode(Format('Page %d',[APAgeNo])));
+  L.HRef:=ExtractFileName(APageName);
+  if ForFrame then
+    L.Target:='reportpage';
+  Result.AppendChild(l);
+end;
+
+function TTOCPageCreator.CreateTOCPage(APageNames: TStrings; ForFrame: Boolean): String;
+Var
+
+  AHeadElement,ABodyElement,AHTMLEl,El,PEL : THTMLElement;
+  D,FN : String;
+  I : Integer;
+
+begin
+  AHTMLEl := FDoc.CreateHtmlElement;
+  FDoc.AppendChild(AHTMLEl);
+  AHeadElement:=FDoc.CreateHeadElement;
+  AHTMLEl.AppendChild(AHeadElement);
+  El := Doc.CreateElement('meta');
+  El['http-equiv'] := 'Content-Type';
+  El['content'] := 'text/html; charset=utf-8';
+  AHeadElement.AppendChild(El);
+  El := Doc.CreateElement('title');
+  AHeadElement.AppendChild(el);
+  el.AppendChild(Doc.CreateTextNode(Report.Title));
+  if (TOCPage.CSSFileName<>'') then
+    begin
+    El:=Doc.CreateElement('link');
+    El['rel']:='stylesheet';
+    D:=Context.CSSDir;
+    if (D<>'') then
+      D:=D+'/'; // not pathdelim !
+    El['href']:=D+TOCPage.CSSFileName;
+    AHeadElement.AppendChild(El);
+    end;
+  ABodyElement := Doc.CreateElement('body');
+  AHTMLEl.AppendChild(ABodyElement);
+  PEL:=Doc.CreateElement('div');
+  PEl.ID:='toctable';
+  ABodyElement.AppendChild(pel);
+  For I:=0 to APageNames.Count-1 do
+    begin
+    el:=CreatePageLink(APageNames[i],I+1,Doc,ForFrame);
+    if I=0 then
+      el.ClassName:=EL.ClassName+' '+'firstpage'
+    else if I= (APageNames.Count-1) then
+      EL.ClassName:=EL.ClassName+' '+'lastpage';
+    PEL.AppendChild(el);
+    end;
+  FN:=TOCPage.FileName;
+  if (FN='') then
+    if ForFrame then
+      FN:='toc.html'
+    else
+      FN:='index.html';
+  D:=ExtractFilePath(Context.BaseFileName);
+  FN:=D+FN;
+  WriteHTML(Doc,FN);
+  Result:=FN;
+end;
+
+
+{ TGenerateHTMLContext }
+
+function TGenerateHTMLContext.mmToPixels(const AValue: TFPReportUnits): Integer;
+begin
+  Result := Round(AValue * (DPI / cInchToMM));
+end;
+
+function TGenerateHTMLContext.AllocateID(aElement: THTMLElement; AReportElement: TFPReportElement): String;
+
+Var
+  N : String;
+
+begin
+  if (AElement.ID<>'') then
+    exit(AElement.ID);
+  if AReportElement<>Nil then
+    N:=AReportElement.ClassName
+  else if AElement.TagName='div' then
+    N:='div'
+  else
+    N:='el';
+  Result:=N+'-'+IntToStr(FIDCount);
+  AElement.ID:=Result
+end;
+
+
+function TGenerateHTMLContext.CreateDiv(aParent: TDOMElement; AReportElement: TFPReportElement): THTMLElement;
+
+begin
+  inc(FIDCount);
+  Result:=FDoc.CreateElement('div');
+  AllocateID(Result,AReportElement);
+  if (AParent<>Nil) then
+    AParent.AppendChild(Result);
+end;
+
+function TGenerateHTMLContext.CreateDiv(aParent: TDOMElement; AID: String): THTMLElement;
+
+begin
+  Result:=FDoc.CreateElement('div');
+  Result.ID:=AID;
+  if (AParent<>Nil) then
+    AParent.AppendChild(Result);
+end;
+
+constructor TGenerateHTMLContext.Create(AReport: TFPCustomReport; ADoc: THTMLDocument; ADPI: Integer);
+begin
+  FReport:=AReport;
+  FDoc:=ADoc;
+  FIDCount:=0;
+  FDPI:=ADPI;
+end;
+
+procedure TGenerateHTMLContext.Resetpage(ADoc: THTMLDocument);
+begin
+  FDoc:=ADoc;
+  FIDCount:=0;
+end;
+
+function TGenerateHTMLContext.AllocatePageName(APageNo: Integer): string;
+
+Var
+  E,SN : String;
+begin
+  SN:=Format(SequenceFormat,[APageNo]);
+  E:=ExtractFileExt(BaseFileName);
+  Result:=ChangeFileExt(BaseFileName,SN+E);
+end;
+
+procedure TGenerateHTMLContext.ApplyStyle(aElement: THTMLElement; const aStyle: TStrings);
+
+Var
+  aID : String;
+
+begin
+  if Not Assigned(FStyleContent) then
+    begin
+    aStyle.LineBreak:=' ';
+    AElement['style']:=aStyle.text
+    end
+  else
+    begin
+    AID:=AllocateID(AElement,nil);
+    FStyleContent.Add('#'+aID+' {');
+    FStyleContent.AddStrings(aStyle);
+    FStyleContent.Add('}');
+    end
+end;
+
+procedure TGenerateHTMLContext.ApplyStyle(aElement: THTMLElement;
+  const aStyle: String);
+
+Var
+  aID : String;
+
+begin
+  if Not Assigned(FStyleContent) then
+    AElement['style']:=aStyle
+  else
+    begin
+    AID:=AllocateID(AElement,Nil);
+    FStyleContent.Add('#'+aID+' {');
+    FStyleContent.Add(aStyle);
+    FStyleContent.Add('}');
+    end
+end;
+
+procedure TGenerateHTMLContext.InitOffsets(aX, aY: Integer);
+begin
+  FRunOffsetX:=aX;
+  FRunOffsetY:=aY;
+end;
+
+class function TGenerateHTMLContext.ColorToRGBString(AColor: TFPReportColor;
+  UseHex: Boolean): String;
+
+Var
+  S : TFPColor;
+begin
+  S:=ColorToRGBTriple(aColor);
+  if AColor=clNone then
+    exit;
+  if UseHex then
+    Result:=Format('#%.2x,%.2x,%2.x);',[S.red,S.green,S.Blue])
+  else
+    Result:=Format('rgb(%d,%d,%d);',[S.red,S.green,S.Blue]);
+end;
+
+{ TPageNavigatorElementCreator }
+
+function TPageNavigatorElementCreator.GetDoc: THTMLDocument;
+begin
+  Result:=Context.Doc;
+end;
+
+procedure TPageNavigatorElementCreator.ApplyStyle(aElement: THTMLElement; const aStyle: TStrings);
+begin
+  FContext.ApplyStyle(aElement,AStyle);
+end;
+
+procedure TPageNavigatorElementCreator.ApplyStyle(aElement: THTMLElement; const aStyle: String);
+begin
+  FContext.ApplyStyle(aElement,AStyle);
+end;
+
+constructor TPageNavigatorElementCreator.Create(AContext: TGenerateHTMLContext; APage : TFPReportPage; AOptions: TPageNavigatorOptions);
+begin
+  FContext:=AContext;
+  FPageNavigator:=AOptions;
+  FCurrentPage:=APage;
+end;
+
+procedure TPageNavigatorElementCreator.CreatePageNavigator(aPosition: TNavigatorPosition; AParentDiV: THTMLElement);
+
+Var
+  ND,N : THTMLElement;
+  BN : String;
+  ActiveLink : Boolean;
+
+  Function PageLinkNode(Target : Integer; AText : String; Out IsActive: Boolean) : THTMLElement;
+
+  var
+    L : THTMLLinkElement;
+
+  begin
+    if (Target<1) or (Target=Context.CurrentPageNo) or (Target>Context.TotalPageCount) then
+      begin
+      Result:=Doc.CreateSpanElement;
+      Result.ID:=ND.ID+'-deadlink';
+      IsActive:=False;
+      end
+    else
+      begin
+      L:=Doc.CreateLinkElement;
+      L.HRef:=ExtractFileName(Context.AllocatePageName(Target));
+      L.ID:=ND.ID+'-link';
+      IsActive:=True;
+      Result:=L;
+      end;
+    Result.AppendChild(Doc.CreateTextNode(AText));
+  end;
+
+begin
+  BN:='navigator-'+NavigatorPositionNames[aPosition];
+  ND:=Context.CreateDiv(aParentDIV,BN);
+  ND.ClassName:='navigator '+NavigatorPositionNames[aPosition]+'navigation';
+  if (Context.CurrentPageNo>1) or (hnoAlwaysFirstLast in PageNavigator.Options) then
+    begin
+    if (hnoFirstLast in PageNavigator.Options) then
+      begin
+      N:=Context.CreateDiv(nd,bn+'first');
+      N.AppendChild(PageLinkNode(1,SFirstPage,ActiveLink));
+      ApplyStyle(N,StyleNavigatorButton(aPosition,nbFirst,ActiveLink));
+      end;
+    N:=Context.CreateDiv(nd,bn+'prev');
+    N.AppendChild(PageLinkNode(Context.CurrentPageNo-1,SPreviousPage,ActiveLink));
+    ApplyStyle(N,StyleNavigatorButton(aPosition,nbPrevious,ActiveLink));
+    end;
+  if hnoPageNo in PageNavigator.Options then
+    begin
+    N:=Context.CreateDiv(ND,bn+'pageno');
+    CreatePageNoNavigatorElement(N,aPosition);
+    ApplyStyle(N,StyleNavigatorButton(aPosition,nbPage,True));
+    end;
+  if (Context.CurrentPageNo<Context.TotalPageCount) or (hnoAlwaysFirstLast in PageNavigator.Options) then
+    begin
+    N:=Context.CreateDiv(nd,bn+'next');
+    N.AppendChild(PageLinkNode(Context.CurrentPageNo+1,SNextPage,ActiveLink));
+    ApplyStyle(N,StyleNavigatorButton(aPosition,nbNext,ActiveLink));
+    if (hnoFirstLast in PageNavigator.Options) then
+      begin
+      N:=Context.CreateDiv(nd,bn+'last');
+      N.AppendChild(PageLinkNode(Context.TotalPageCount,SLastPage,ActiveLink));
+      ApplyStyle(N,StyleNavigatorButton(aPosition,nbLast,ActiveLink));
+      end
+    end;
+  If not (hnoSkipStyling in PageNavigator.Options) then
+    StyleNavigator(ND,aPosition);
+end;
+
+procedure TPageNavigatorElementCreator.StyleNavigator(ND : THTMLElement; aPosition : TNavigatorPosition);
+
+Var
+  S : String;
+
+begin
+    begin
+    if (aPosition in [npTop,npBottom]) then
+      begin
+      S:='position: absolute; display: flex; width: 100%; align-items: center; justify-content: center; ';
+      S:=S+Format('height: %dpx;',[PageNavigator.FixedHeight]);
+      if (aPosition = npBottom) then
+        S:=S+Format('top: %dpx;',[Context.RunOffsetY+Context.mmToPixels(CurrentPage.PageSize.Height)]);
+      ApplyStyle(ND,S);
+      end;
+    if (aPosition in [npLeft,npRight]) then
+      begin
+      S:='position: absolute; top: 25%; align-content: center; ';
+      S:=S+Format('width: %dpx;',[PageNavigator.FixedWidth]);
+      if (aPosition = npRight) then
+        S:=S+Format('left: %dpx;',[Context.RunOffsetX+Context.mmToPixels(CurrentPage.PageSize.Width)+PageNavigator.FixedMargin]);
+      ApplyStyle(ND,S);
+      end;
+    end;
+end;
+
+class procedure TPageNavigatorElementCreator.WriteDefaultScript(AContext : TGenerateHTMLContext; AScript: TStrings);
+
+Const
+  SScript = 'function maybesetpage(ev) {'+sLineBreak+
+            '  if (ev.keyCode==13) {' +sLineBreak+
+            '    var pageno= ""+ev.target.value;'+sLineBreak+
+            '    while (pageno.length<SequenceDigits) { pageno = "0"+ pageno; }; '+sLineBreak+
+            '    if ((pageno>0) && (pageno<=TotalPageCount)) {'+sLineBreak+
+            '      var url = PageNameFormat.replace(/%d/,pageno);'+sLineBreak+
+            '      window.location.href=url;'+sLineBreak+
+            '    }'+sLineBreak+
+            '  }'+sLineBreak+
+            '}';
+
+Var
+  S : TStrings;
+  FN : String;
+  P : Integer;
+begin
+  FN:=ChangeFileExt(extractFileName(AContext.BaseFileName),AContext.SequenceFormat+ExtractFileExt(AContext.BaseFileName));
+  P:=pos('%',FN);
+  if FN[P+1]='.' then
+    Delete(FN,P+1,2);
+  AScript.Add(Format('var PageNameFormat = "%s";',[FN]));
+  AScript.Add(Format('var TotalPageCount = %d;',[AContext.TotalPageCount]));
+  AScript.Add(Format('var CurrentPage = %d;',[AContext.CurrentPageNo]));
+  AScript.Add(Format('var SequenceDigits = %d;',[AContext.SequenceDigits]));
+  S:=TStringList.Create;
+  try
+    S.Text:=SScript;
+    AScript.AddStrings(S);
+  finally
+    S.Free;
+  end;
+end;
+
+
+function TPageNavigatorElementCreator.StyleNavigatorButton(aPosition: TNavigatorPosition; AButton: TNavigatorButton; ActiveLink: Boolean): String;
+
+Var
+  C : String;
+
+begin
+  if (hnoSkipStyling in PageNavigator.Options) then
+    exit;
+  if aPosition in [npTop,npBottom] then
+    begin
+    Result:='margin: 0 4px 0 4px;';
+    Result:=Result+' float: left;';
+    end
+  else
+    Result:='margin: 4px 0 4px 0;';
+  if aButton<>nbPage then
+    begin
+    if ActiveLink then
+      C:=Coalesce(Context.ColorToRGBString(PageNavigator.ActiveBGColor,True),'#63CF80')
+    else
+      C:=Coalesce(Context.ColorToRGBString(PageNavigator.InActiveBGColor,True),'#BABABA');
+    end;
+  Result:=Result+' background-color: '+C+';';
+  Result:=Result+' border: solid;';
+  Result:=Result+' border-width: 1px;';
+end;
+
+procedure TPageNavigatorElementCreator.CreatePageNoNavigatorElement(AParentDiV: THTMLElement; aPosition: TNavigatorPosition);
+Var
+  N2 : THTMLElement;
+  IE : THTMLElement;
+  Fmt: String;
+
+begin
+  if (hnoPageNoEdit in PageNavigator.Options) then
+    begin
+    IE:=Doc.CreateElement('input');
+    IE['type']:='number';
+    IE['id']:='pageedit'+NavigatorPositionNames[aPosition];
+    IE['onkeypress']:='maybesetpage(event)';
+    IE['value']:=IntToStr(Context.CurrentPageNo);
+    if not (hnoSkipStyling in PageNavigator.Options) then
+      ApplyStyle(IE,'width: 50px;');
+    AParentDiV.AppendChild(IE);
+    if hnoUsePageNOfM in PageNavigator.Options then
+      begin
+      N2:=Doc.CreateSpanElement;
+      N2.AppendChild(Doc.CreateTextNode(Format(STotalPages,[Context.TotalPageCount])));
+      AParentDiV.AppendChild(N2);
+      end;
+    end
+  else
+    begin
+    N2:=Doc.CreateSpanElement;
+    AParentDiv.AppendChild(N2);
+    if hnoUsePageNOfM in PageNavigator.Options then
+      Fmt:=SCurrentPageOf
+    else
+      Fmt:=SCurrentPage;
+    N2.AppendChild(Doc.CreateTextNode(Format(Fmt,[Context.CurrentPageNo,Context.TotalPageCount])));
+    end;
+end;
+
+
+
+{ TTOCFramePageCreator }
+
+function TTOCFramePageCreator.GetReport: TFPCustomReport;
+begin
+  Result:=Context.Report;
+end;
+
+function TTOCFramePageCreator.GetCSSDIR: String;
+begin
+  Result:=FContext.CSSDir;
+end;
+
+constructor TTOCFramePageCreator.Create(AContext: TGenerateHTMLContext; AOptions: TFramePageOptions);
+begin
+  FContext:=AContext;
+  FFramePage:=AOptions;
+  FDoc := THTMLDocument.Create;
+  FDoc.AppendChild(FDoc.Impl.CreateDocumentType(
+    'HTML', '-//W3C//DTD HTML 4.01 Frameset//EN"',
+    'http://www.w3.org/TR/html4/loose.dtd'));
+end;
+
+destructor TTOCFramePageCreator.Destroy;
+begin
+  FreeAndNil(FDoc);
+  inherited Destroy;
+end;
+
+procedure TTOCFramePageCreator.ConfigureFrameSet(FramesEl : THTMLElement);
+
+Const
+  ColRows : Array[Boolean] of string = ('rows','cols');
+
+Var
+  IsCols, DirInvert : Boolean;
+  P : String;
+
+begin
+  DirInvert:=(FramePage.TOCZonePosition in [tpRight,tpBottom]);
+  IsCols:=FramePage.TOCZonePosition in [tpLeft,tpRight];
+  if Not DirInvert then
+    P:=Format('%d%%,%d%%',[FramePage.TOCZoneSize,100-FramePage.TOCZoneSize])
+  else
+    P:=Format('%d%%,%d%%',[100-FramePage.TOCZoneSize,FramePage.TOCZoneSize]);
+  FramesEl[ColRows[isCols]]:=P;
+end;
+
+procedure TTOCFramePageCreator.CreateFramePage(PageNames: TStrings; const ATOCPageFileName: String);
+
+Var
+  aTocPage,aReportPage,FrameSet,AHeadElement,AHTMLEl,El : THTMLElement;
+  FN,D,P : String;
+  DirInvert : Boolean;
+
+begin
+  AHTMLEl := FDoc.CreateHtmlElement;
+  FDoc.AppendChild(AHTMLEl);
+  AHeadElement:=FDoc.CreateHeadElement;
+  AHTMLEl.AppendChild(AHeadElement);
+  El := FDoc.CreateElement('meta');
+  El['http-equiv'] := 'Content-Type';
+  El['content'] := 'text/html; charset=utf-8';
+  AHeadElement.AppendChild(El);
+  El := FDoc.CreateElement('title');
+  AHeadElement.AppendChild(el);
+  el.AppendChild(FDoc.CreateTextNode(Report.Title));
+  if (FFramePage.CSSFileName<>'') then
+    begin
+    El:=FDoc.CreateElement('link');
+    El['rel']:='stylesheet';
+    D:=CSSDir;
+    if (D<>'') then
+      D:=D+'/'; // not pathdelim !
+    El['href']:=D+FramePage.CSSFIleName;
+    AHeadElement.AppendChild(El);
+    end;
+  FrameSet := FDoc.CreateElement('frameset');
+  // No body element when using framesets
+  AHTMLEl.AppendChild(FrameSet);
+  ConfigureFrameSet(FrameSet) ;
+  el := FDoc.CreateNoframesElement;
+  El.AppendChild(FDoc.CreateTextNode(SNoFrameSupport));
+  FrameSet.AppendChild(el);
+  dirInvert:=FramePage.TOCZonePosition in [tpRight,tpBottom];
+  aTocPage:=FDoc.CreateElement('frame');
+  aTocPage['name']:='reporttoc';
+  aTocPage['src']:=ExtractFileName(ATocPageFileName);
+  aReportPage:=FDoc.CreateElement('frame');
+  aReportPage['name']:='reportpage';
+  if PageNames.Count>0 then
+    P:=ExtractFileName(PageNames[0]);
+  aReportPage['src']:=P;
+  if dirInvert then
+    begin
+    FrameSet.AppendChild(aReportPage);
+    FrameSet.AppendChild(aTOCPage);
+    end
+  else
+    begin
+    FrameSet.AppendChild(aTOCPage);
+    FrameSet.AppendChild(aReportPage);
+    end;
+  FN:=FramePage.FileName;
+  if (FN='') then
+    FN:='index.html';
+  D:=ExtractFilePath(Context.BaseFileName);
+  FN:=D+FN;
+  WriteHTML(FDoc,FN);
+end;
+
+{ TPageNavigatorOptions }
+
+procedure TPageNavigatorOptions.Assign(Source: TPersistent);
+
+Var
+  NO : TPageNavigatorOptions;
+
+begin
+  if (source is TPageNavigatorOptions) then
+    begin
+    NO:=source as TPageNavigatorOptions;
+    Positions:=NO.Positions;
+    Options:=NO.Options;
+    FixedWidth:=NO.FixedWidth;
+    FixedMargin:=NO.FixedMargin;
+    FixedHeight:=NO.FixedHeight;
+    FActiveBGColor:=NO.ActiveBGColor;
+    FInActiveBGColor:=NO.InActiveBGColor;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+{ TTOCPageOptions }
+
+procedure TTOCPageOptions.Assign(Source: TPersistent);
+
+Var
+  O : TTOCPageOptions;
+
+begin
+  if (Source is TTOCPageOptions) then
+    begin
+    O:=Source as TTOCPageOptions;
+    FFileName:=O.FileName;
+    FCSSFileName:=O.CSSFileName;
+    OddPageStyle:=O.OddPageStyle;
+    EvenPageStyle:=O.EvenPageStyle;
+    SkipStyling:=SkipStyling;
+    end
+  else
+  inherited Assign(Source);
+end;
+
+{ TFramePageOptions }
+
+procedure TFramePageOptions.Assign(Source: TPersistent);
+
+Var
+  O : TFramePageOptions;
+
+begin
+  if (Source is TFramePageOptions) then
+    begin
+    O:=Source as TFramePageOptions;
+    FFileName:=O.FileName;
+    FCSSFileName:=O.CSSFileName;
+    FTOCZoneSize:=O.TOCZoneSize;
+    FTOCZonePosition:=O.TOCZonePosition;
+    end
+  else
+    inherited Assign(Source);
+end;
+
+end.
+

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

@@ -0,0 +1,289 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2017 Michael Van Canneyt, member of the Free Pascal development team
+
+    Report Data loop classes based on a JSON data structure.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportjson;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpreport, fpjson;
+
+Type
+
+  { TFPReportJSONData }
+
+  TFPReportJSONData = class(TFPReportData)
+  private
+    FJSON : TJSONData;
+    FOwnsJSON: Boolean;
+    FPath: TJSONStringType;
+    FRoot : TJSONData;
+    FIndex : Integer;
+    function GetJSON: TJSONStringType;
+    procedure SetJSON(AValue: TJSONData);
+    procedure SetJSON(AValue: TJSONStringType);
+    procedure SetPath(AValue: TJSONStringType);
+  protected
+    procedure SetRoot;
+    procedure DoGetValue(const AFieldName: string; var AValue: variant); override;
+    procedure DoInitDataFields; override;
+    procedure DoOpen; override;
+    procedure DoFirst; override;
+    procedure DoNext; override;
+    procedure DoClose; override;
+    function  DoEOF: boolean; override;
+  Public
+    Destructor Destroy; override;
+    Procedure LoadFromStream(S : TStream); virtual;
+    Procedure LoadFromFile(const aFileName : String);
+    property  DataFields;
+    property  JSONData : TJSONData read FJSON write SetJSON;
+  Published
+    Property Path : TJSONStringtype Read FPath Write SetPath;
+    Property OwnsJSON : Boolean Read FOwnsJSON Write FOwnsJSON;
+    Property JSON : TJSONStringType Read GetJSON Write SetJSON;
+  end;
+
+
+implementation
+
+resourcestring
+  SErrInvalidJSON = 'Invalid JSON. Need Array or Object';
+  SErrInvalidJSONAtPath = 'Invalid JSON at Path. Need Array or Object';
+  SErrInvalidPath = 'Path "%s" is not valid';
+
+{ TFPReportJSONData }
+
+function TFPReportJSONData.GetJSON: TJSONStringType;
+begin
+  If Assigned(FJSON) then
+    Result:=FJSON.AsJSON
+  else
+    Result:='';
+end;
+
+procedure TFPReportJSONData.SetRoot;
+
+Var
+  d : TJSONData;
+
+begin
+  D:=Nil;
+  if Assigned(FJSON) then
+    begin
+    if (Path='') then
+      D:=FJSON
+    else
+      begin
+      D:=FJSON.FindPath(Path);
+      if D=Nil then
+        Raise EReportError.CreateFmt(SErrInvalidPath,[Path]);
+      end
+    end;
+  if Assigned(D) and Not (D.JSONType in StructuredJSONTypes) then
+    Raise EReportError.Create(SErrInvalidJSONAtPath);
+  FRoot:=D;
+end;
+
+procedure TFPReportJSONData.SetJSON(AValue: TJSONData);
+begin
+  if FJSON=AValue then Exit;
+  if Assigned(AValue) and Not (AValue.JSONType in StructuredJSONTypes) then
+    Raise EReportError.Create(SErrInvalidJSON);
+  if OwnsJSON then
+    FreeAndNil(FJSON);
+  FJSON:=AValue;
+  SetRoot;
+end;
+
+procedure TFPReportJSONData.SetJSON(AValue: TJSONStringType);
+
+Var
+  aJSON : TJSONData;
+
+begin
+  if (AValue='') then
+    JSON:=Nil
+  else
+    begin
+    aJSON:=fpjson.GetJSON(aValue);
+    try
+      JSON:=aJSON;
+      OwnsJSON:=True;
+    except
+      FreeAndNil(aJSON);
+      Raise;
+    end;
+    end;
+end;
+
+procedure TFPReportJSONData.SetPath(AValue: TJSONStringType);
+
+begin
+  if FPath=AValue then Exit;
+  FPath:=AValue;
+  SetRoot;
+end;
+
+procedure TFPReportJSONData.DoGetValue(const AFieldName: string;
+  var AValue: variant);
+
+Var
+  Rec : TJSONData;
+  D : TJSONData;
+  I : Integer;
+
+begin
+  inherited DoGetValue(AFieldName, AValue);
+  if (Not Assigned(FRoot)) or (Findex>=FRoot.Count) then
+    exit;
+  I:=-1;
+  Rec:=FRoot.Items[FIndex];
+  if (Rec is TJSONObject) then
+    I:=TJSONObject(Rec).IndexOfName(AFieldName,True)
+  else if (Rec is TJSONArray) then
+    begin
+    I:=DataFields.IndexOfField(AFieldName);
+    end;
+  if (I=-1) then
+    begin
+    Writeln(FIndex,' : ',AFieldName,' -> ',I);
+    Exit;
+    end;
+  D:=Rec.Items[i];
+  Case D.JSONType of
+  jtString :
+  AValue:=D.AsString;
+  jtNumber :
+    Case TJSONNUmber(D).NumberType of
+      ntFloat : AValue:=D.AsFloat;
+      ntInteger : AValue:=D.AsInteger;
+      ntInt64 : AValue:=D.AsInt64;
+      ntQWord : AValue:=D.AsQWord;
+    end;
+  jtBoolean:
+    AValue:=D.AsBoolean;
+  jtNull :
+    ;
+  else
+    AValue:=D.AsJSON;
+  end;
+  Writeln(FIndex,' : ',AFieldName,' -> ',AValue);
+end;
+
+procedure TFPReportJSONData.DoInitDataFields;
+
+Var
+  Rec : TJSONData;
+  E : TJSONEnum;
+  N : String;
+  prefix : Boolean;
+  k : TFPReportFieldKind;
+
+begin
+  inherited DoInitDataFields;
+  if Assigned(FRoot) and (FRoot.Count>0) then
+    begin
+    Rec:=FRoot.Items[0];
+    Prefix:=not (Rec is TJSONObject);
+    for E in Rec do
+      begin
+      N:=E.Key;
+      if Prefix then
+        N:='Column'+N;
+      k:=rfkString;
+      Case E.Value.JSONType of
+        jtBoolean :
+          k:=rfkBoolean;
+        jtNumber :
+          if TJSONNumber(E.Value).NumberType=ntFloat then
+            k:=rfkFloat
+          else
+            k:=rfkInteger;
+      end;
+      DataFields.AddField(N,k);
+      end;
+    end;
+end;
+
+procedure TFPReportJSONData.DoOpen;
+begin
+  inherited DoOpen;
+  FIndex:=0;
+end;
+
+procedure TFPReportJSONData.DoFirst;
+begin
+  inherited DoFirst;
+  FIndex:=0;
+end;
+
+procedure TFPReportJSONData.DoNext;
+begin
+  Inherited;
+  Inc(FIndex);
+end;
+
+procedure TFPReportJSONData.DoClose;
+begin
+  inherited DoClose;
+  FIndex:=-1;
+end;
+
+function TFPReportJSONData.DoEOF: boolean;
+begin
+  Result:=Not Assigned(FRoot) or (FIndex>=FRoot.Count) or (FIndex<0);
+end;
+
+destructor TFPReportJSONData.Destroy;
+begin
+  if OwnsJSON then
+    FreeAndNil(FJSON);
+  inherited Destroy;
+end;
+
+procedure TFPReportJSONData.LoadFromStream(S: TStream);
+
+Var
+  aJSON : TJSONData;
+
+begin
+  try
+    aJSON:=fpjson.GetJSON(S);
+    JSON:=aJSON;
+    OwnsJSON:=True;
+  except
+    FreeAndNil(aJSON);
+    Raise;
+  end
+end;
+
+procedure TFPReportJSONData.LoadFromFile(const aFileName: String);
+
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    LoadFromStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+end.
+

+ 771 - 0
packages/fcl-report/src/fpreportpdfexport.pp

@@ -0,0 +1,771 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) WISA b.v.b.a
+
+    FPReport PDF export filter.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit fpreportpdfexport;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpImage,
+  fpreport,
+  fpPDF;
+
+{$IF FPC_FULLVERSION>=30101}
+{$DEFINE PDF_HASISSTANDARDPDFFONT}
+{$DEFINE PDF_HASEXTERNALLINK}
+{$ENDIF}
+
+type
+
+  { TFPReportExportPDF }
+
+  TFPReportExportPDF = class(TFPReportExporter)
+  private
+    FCurrentPage: TPDFPage;
+    FOptions: TPDFOptions;
+    FPageLayout: TPDFPageLayout;
+    FFileName: string;
+    FDocument: TPDFDocument;
+    FAutoSave: boolean;
+  protected
+    procedure RenderElement(pg: TPDFPage; ABand: TFPReportCustomBand; el: TFPReportElement); virtual;
+    Procedure   RenderImage(aRect : TFPReportRect; var AImage: TFPCustomImage) ; override;
+    procedure   DoExecute(const ARTObjects: TFPList); override;
+    procedure   SetupPDFDocument; virtual;
+    procedure   RenderFrame(const APage: TPDFPage; const ABand: TFPReportCustomBand; const AFrame: TFPReportFrame; const APos: TPDFCoord; const AWidth, AHeight: TFPReportUnits); virtual;
+    procedure   RenderMemo(const APage: TPDFPage; const ABand: TFPReportCustomBand; const AMemo: TFPReportCustomMemo); virtual;
+    procedure   RenderShape(const APage: TPDFPage; const ABand: TFPReportCustomBand; const AShape: TFPReportCustomShape); virtual;
+    procedure   RenderImage(const APage: TPDFPage; const ABand: TFPReportCustomBand; const AImage: TFPReportCustomImage); virtual;
+    procedure   RenderCheckbox(const APage: TPDFPage; const ABand: TFPReportCustomBand; const ACheckbox: TFPReportCustomCheckbox); virtual;
+    procedure   RenderShape(const APage: TPDFPage; const AOrigin: TPDFCoord; const AShape: TFPReportCustomShape); virtual;
+    procedure   RenderShapeCircle(const APage: TPDFPage; const lpt1: TPDFCoord; const ALayout: TFPReportLayout);
+    procedure   RenderShapeEllipse(const APage: TPDFPage; const lpt1: TPDFCoord; const ALayout: TFPReportLayout);
+    procedure   RenderShapeLine(const APage: TPDFPage; lpt1: TPDFCoord;  const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+    procedure   RenderShapeRect(const APage: TPDFPage; const lpt1: TPDFCoord; const ALayout: TFPReportLayout);
+    procedure   RenderShapeTriangle(const APage: TPDFPage; Alpt: TPDFCoord; const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+    procedure   RenderShapeRoundedRect(const APage: TPDFPage; const lpt1: TPDFCoord; const ARadius: TFPReportUnits; const ALayout: TFPReportLayout);
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    Class Function Name : String; override;
+    Class Function Description : String; override;
+    Class Function DefaultExtension: String; override;
+    Procedure SetFileName(const aFileName: String); override;
+    function    FindFontIndex(const ADoc: TPDFDocument; const AFontName: string): integer;
+    procedure   SaveToFile;
+    property    Document: TPDFDocument read FDocument;
+    Property    CurrentPage: TPDFPage Read FCurrentPage;
+  published
+    property    AutoSave: boolean read FAutoSave write FAutoSave default True;
+    property    FileName: string read FFileName write FFileName;
+    Property    Options : TPDFOptions Read FOptions Write FOptions;
+    property    PageLayout : TPDFPageLayout read FPageLayout write FPageLayout default lSingle;
+  end;
+
+implementation
+
+uses
+  FPCanvas,
+  fpTTF,
+  fpparsettf;
+
+
+{ TFPReportExportPDF }
+
+function TFPReportExportPDF.FindFontIndex(const ADoc: TPDFDocument; const AFontName: string): integer;
+
+    function isStandardPdfFont: Boolean;
+
+    begin
+{$IFDEF PDF_HASISSTANDARDPDFFONT}
+     Result:=aDoc.IsStandardPDFFont(aFontName)
+{$ELSE}
+      Result:=(AFontName='Courier') or (AFontName='Courier-Bold') or (AFontName='Courier-Oblique') or (AFontName='Courier-BoldOblique')
+              or (AFontName='Helvetica') or (AFontName='Helvetica-Bold') or (AFontName='Helvetica-Oblique') or (AFontName='Helvetica-BoldOblique')
+              or (AFontName='Times-Roman') or (AFontName='Times-Bold') or (AFontName='Times-Italic') or (AFontName='Times-BoldItalic')
+              or (AFontName='Symbol')
+              or (AFontName='ZapfDingbats');
+{$ENDIF}
+    end;
+
+var
+  i: integer;
+  fnt: TFPFontCacheItem;
+begin
+  Result := -1;
+  for i := 0 to Document.Fonts.Count-1 do
+  begin
+    if Document.Fonts.FontDefs[i].Name = AFontName then
+    begin
+      Result := i;
+      break;
+    end;
+  end;  { for i ... }
+
+  if Result = -1 then
+  begin
+    if IsStandardPDFFont then
+    begin
+      Result := Document.AddFont(AFontName);
+    end
+    else
+    begin
+      fnt := gTTFontCache.Find(AFontName); // we are doing a PostScript Name lookup (it contains Bold, Italic info)
+      if Assigned(fnt) then
+        Result := Document.AddFont(fnt.FileName, AFontName)
+      else
+        raise Exception.CreateFmt('fpreport: Could not find the font <%s> in the font cache.', [AFontName]);
+    end;
+  end;
+end;
+
+procedure TFPReportExportPDF.SetupPDFDocument;
+begin
+  if Assigned(FDocument) then
+    FDocument.Free;
+  FDocument := TPDFDocument.Create(Nil);
+  FDocument.Infos.Title := TFPReport(Report).Title;
+  FDocument.Infos.Author := TFPReport(Report).Author;
+  FDocument.Infos.ApplicationName := ApplicationName;
+  FDocument.Infos.CreationDate := Now;
+  FDocument.Options:=Self.Options;
+  FDocument.PageLayout:=Self.PageLayout;
+  FDocument.StartDocument;
+  { we always need at least one section }
+  FDocument.Sections.AddSection;
+end;
+
+procedure TFPReportExportPDF.SaveToFile;
+var
+  F: TFileStream;
+begin
+  if not Assigned(FDocument) then
+    Exit;
+  F := TFileStream.Create(FFileName, fmCreate);
+  try
+    FDocument.SaveToStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TFPReportExportPDF.RenderFrame(const APage: TPDFPage; const ABand: TFPReportCustomBand;
+  const AFrame: TFPReportFrame; const APos: TPDFCoord; const AWidth, AHeight: TFPReportUnits);
+var
+  bStroke, bFill: boolean;
+begin
+  bStroke := AFrame.Color <> clNone;
+  bFill := AFrame.BackgroundColor <> clNone;
+
+  // We only support TPDFPenStyle types. (ppsSolid,ppsDash,ppsDot,ppsDashDot,ppsDashDotDot)
+  case AFrame.Pen of
+    psSolid:      APage.SetPenStyle(ppsSolid);
+    psDash:       APage.SetPenStyle(ppsDash);
+    psDot:        APage.SetPenStyle(ppsDot);
+    psDashDot:    APage.SetPenStyle(ppsDashDot);
+    psDashDotDot: APage.SetPenStyle(ppsDashDotDot);
+    // These FPCanvas pen styles are unsupported
+//    psInsideFrame: ;
+//    psPattern: ;
+//    psClear: ;
+    else
+      // give a sane fallback for now
+      APage.SetPenStyle(ppsSolid);
+  end;
+
+  if (AFrame.Shape = fsRectangle) and (bStroke or bFill) then
+  begin
+    APage.SetColor(AFrame.BackgroundColor, False); // fill color
+    APage.SetColor(AFrame.Color, True); // stroke color
+    APage.DrawRect(APos.X, APos.Y, AWidth, AHeight, AFrame.Width, bFill, bStroke);
+  end;
+
+  if AFrame.Shape = fsNone then
+  begin
+    if AFrame.Lines <> [] then
+    begin
+      APage.SetColor(AFrame.Color, True);
+      APage.SetColor(AFrame.Color, False);
+    end;
+
+    { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left, so adjust them }
+    if flTop in AFrame.Lines then
+      APage.DrawLine(APos.X, APos.Y-AHeight, APos.X+AWidth, APos.Y-AHeight, AFrame.Width);
+
+    if flBottom in AFrame.Lines then
+      APage.DrawLine(APos.X, APos.Y, APos.X+AWidth, APos.Y, AFrame.Width);
+
+    if flLeft in AFrame.Lines then
+        APage.DrawLine(APos.X, APos.Y, APos.X, APos.Y-AHeight, AFrame.Width);
+
+    if flRight in AFrame.Lines then
+      APage.DrawLine(APos.X+AWidth, APos.Y, APos.X+AWidth, APos.Y-AHeight, AFrame.Width);
+  end;  { Frame.Shape = fsNone }
+end;
+
+procedure TFPReportExportPDF.RenderMemo(const APage: TPDFPage; const ABand: TFPReportCustomBand;
+  const AMemo: TFPReportCustomMemo);
+var
+  lPt1: TPDFCoord;  // original Report point
+  lFontIdx: integer;
+  lMemo: TFPReportMemo;
+  i: integer;
+  lYPos: TPDFFloat;
+  txtblk: TFPTextBlock;
+begin
+  lMemo := TFPReportMemo(AMemo);
+
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  lPt1.X := ABand.RTLayout.Left + AMemo.RTLayout.Left;
+  lPt1.Y := ABand.RTLayout.Top + AMemo.RTLayout.Top + AMemo.RTLayout.Height;
+
+  { Frame must be drawn before the text as it could have a fill color. }
+  RenderFrame(APage, ABand, AMemo.Frame, lPt1, AMemo.RTLayout.Width, AMemo.RTLayout.Height);
+
+  { Store the Top-Left coordinate of the Memo. We will be reusing this info. }
+  lPt1.X := ABand.RTLayout.Left + AMemo.RTLayout.Left;
+  lPt1.Y := ABand.RTLayout.Top + AMemo.RTLayout.Top;
+
+  { render the TextBlocks as-is. }
+  for i := 0 to lMemo.TextBlockList.Count-1 do
+  begin
+    txtblk := lMemo.TextBlockList[i];
+    lFontIdx := FindFontIndex(Document, txtblk.FontName);
+    APage.SetFont(lFontIdx, lMemo.Font.Size);
+    { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+    lYPos := lPt1.Y + txtblk.Pos.Top + txtblk.Height;
+
+    if txtblk.BGColor <> clNone then
+    begin
+      { draw highlighting background rectangle }
+      APage.SetColor(txtblk.BGColor, false);
+      APage.DrawRect(lPt1.X + txtblk.Pos.Left, lYPos+txtblk.Descender, txtblk.Width, txtblk.Height+(txtblk.Descender*2), 1.0, True, False);
+    end;
+
+    { Text color is always a fill color, hence the False parameter. }
+    APage.SetColor(txtblk.FGColor, false);
+    APage.WriteText(lPt1.X + txtblk.Pos.Left, lYPos, txtblk.Text);
+
+    // process hyperlink if available
+    if txtblk is TFPHTTPTextBlock then
+    begin
+{$IFDEF PDF_HASEXTERNALLINK}
+      APage.AddExternalLink(lPt1.X + txtblk.Pos.Left, lYPos, txtblk.Width, txtblk.Height, TFPHTTPTextBlock(txtblk).URL);
+{$ENDIF}
+    end;
+  end;
+end;
+
+procedure TFPReportExportPDF.RenderShape(const APage: TPDFPage; const ABand: TFPReportCustomBand;
+    const AShape: TFPReportCustomShape);
+var
+  lPt1: TPDFCoord;  // original Report point
+begin
+  APage.SetColor(clblack, True);
+  APage.SetColor(clblack, False);
+
+  lPt1.X := ABand.RTLayout.Left + AShape.RTLayout.Left;
+  lPt1.Y := ABand.RTLayout.Top + AShape.RTLayout.Top;
+
+  { Frame must be drawn before the text as it could have a fill color. }
+  RenderFrame(APage, ABand, AShape.Frame, lPt1, AShape.RTLayout.Width, AShape.RTLayout.Height);
+  RenderShape(APage, lPt1, AShape);
+end;
+
+type
+  { for access to Protected methods }
+  TReportImageFriend = class(TFPReportCustomImage);
+
+procedure TFPReportExportPDF.RenderImage(const APage: TPDFPage; const ABand: TFPReportCustomBand;
+  const AImage: TFPReportCustomImage);
+var
+  lPt: TPDFCoord;
+  img: TReportImageFriend;
+  idx, i: integer;
+  pdfimg: TPDFImageItem;
+begin
+  img := TReportImageFriend(AImage);  { for access to Protected methods }
+  lPt.X := ABand.RTLayout.Left + AImage.RTLayout.Left;
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  lPt.Y := ABand.RTLayout.Top + AImage.RTLayout.Top + AImage.RTLayout.Height;
+
+  { Frame must be drawn before the Image as it could have a fill color. }
+  RenderFrame(APage, ABand, AImage.Frame, lPt, AImage.RTLayout.Width, AImage.RTLayout.Height);
+
+  if not Assigned(img.Image) then
+    Exit; { nothing further to do }
+
+  idx := -1;
+  for i := 0 to Document.Images.Count-1 do
+  begin
+    if Document.Images.Images[i].Equals(img.Image) then
+    begin
+      idx := i;
+      break;
+    end;
+  end;
+
+  if idx = -1 then
+  begin
+    pdfimg := Document.Images.AddImageItem;
+    pdfimg.Image := img.Image;
+    idx := Document.Images.Count-1;
+  end;
+
+  if img.Stretched then
+  begin
+    case APage.UnitOfMeasure of
+      uomMillimeters:
+        begin
+          APage.DrawImage(lPt, AImage.RTLayout.Width, AImage.RTLayout.Height, idx);
+        end;
+      uomCentimeters:
+        begin
+          APage.DrawImage(lPt, AImage.RTLayout.Width, AImage.RTLayout.Height, idx);
+        end;
+      uomInches:
+        begin
+          APage.DrawImage(lPt, AImage.RTLayout.Width, AImage.RTLayout.Height, idx);
+        end;
+      uomPixels:
+        begin
+          APage.DrawImage(lPt, Integer(round(AImage.RTLayout.Width)), Integer(round(AImage.RTLayout.Height)), idx);
+        end;
+    end;  { case UnitOfMeasure }
+  end
+  else
+    APage.DrawImage(lPt, img.Image.Width, img.Image.Height, idx);
+end;
+
+procedure TFPReportExportPDF.RenderCheckbox(const APage: TPDFPage; const ABand: TFPReportCustomBand;
+  const ACheckbox: TFPReportCustomCheckbox);
+var
+  lPt: TPDFCoord;
+  idx: integer;
+  pdfimg: TPDFImageItem;
+  lImage: TFPCustomImage;
+  i: integer;
+begin
+  lPt.X := ABand.RTLayout.Left + ACheckbox.RTLayout.Left;
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  lPt.Y := ABand.RTLayout.Top + ACheckbox.RTLayout.Top + ACheckbox.RTLayout.Height;
+
+//  { Frame must be drawn before the Image as it could have a fill color. }
+//  RenderFrame(Document, APage, ABand, AImage.Frame, lPt, AImage.RTLayout.Width, AImage.RTLayout.Height);
+
+  lImage:=ACheckBox.GetRTImage;
+  idx := -1;
+  for i := 0 to Document.Images.Count-1 do
+  begin
+    if Document.Images.Images[i].Equals(lImage) then
+    begin
+      idx := i;
+      break;
+    end;
+  end;
+
+  if idx = -1 then
+  begin
+    pdfimg := Document.Images.AddImageItem;
+    pdfimg.Image := lImage;
+    idx := Document.Images.Count-1;
+  end;
+
+  case APage.UnitOfMeasure of
+    uomMillimeters:
+      begin
+        APage.DrawImage(lPt, ACheckBox.RTLayout.Width, ACheckBox.RTLayout.Height, idx);
+      end;
+    uomCentimeters:
+      begin
+        APage.DrawImage(lPt, ACheckBox.RTLayout.Width, ACheckBox.RTLayout.Height, idx);
+      end;
+    uomInches:
+      begin
+        APage.DrawImage(lPt, ACheckBox.RTLayout.Width, ACheckBox.RTLayout.Height, idx);
+      end;
+    uomPixels:
+      begin
+        APage.DrawImage(lPt, Integer(round(ACheckBox.RTLayout.Width)), Integer(round(ACheckBox.RTLayout.Height)), idx);
+      end;
+  end;  { case UnitOfMeasure }
+end;
+
+procedure TFPReportExportPDF.RenderShape(const APage: TPDFPage; const AOrigin: TPDFCoord;
+  const AShape: TFPReportCustomShape);
+begin
+  APage.SetColor(TFPReportShape(AShape).Color, True);
+  APage.SetColor(TFPReportShape(AShape).Color, False);
+
+  case TFPReportShape(AShape).ShapeType of
+    stEllipse:      RenderShapeEllipse(APage, AOrigin, AShape.RTLayout);
+    stCircle:       RenderShapeCircle(APage, AOrigin, AShape.RTLayout);
+    stLine:         RenderShapeLine(APage, AOrigin, TFPReportShape(AShape).Orientation, AShape.RTLayout);
+    stSquare:       RenderShapeRect(APage, AOrigin, AShape.RTLayout);
+    stTriangle:     RenderShapeTriangle(APage, AOrigin, TFPReportShape(AShape).Orientation, AShape.RTLayout);
+    stRoundedRect:  RenderShapeRoundedRect(APage, AOrigin, TFPReportShape(AShape).CornerRadius, AShape.RTLayout);
+  end;
+end;
+
+procedure TFPReportExportPDF.RenderShapeCircle(const APage: TPDFPage; const lpt1: TPDFCoord;
+    const ALayout: TFPReportLayout);
+var
+  lPt2: TPDFCoord;
+  ldx, ldy, lw: TFPReportUnits;
+begin
+  if ALayout.Width = ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := 0;
+    lw := ALayout.Width;
+  end
+  else if ALayout.Width > ALayout.Height then
+  begin
+    ldx := (ALayout.Width - ALayout.Height) / 2;
+    ldy := 0;
+    lw := ALayout.Height;
+  end
+  else if ALayout.Width < ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := (ALayout.Height - ALayout.Width) / 2;
+    lw := ALayout.Height;
+  end;
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  lPt2.X := lPt1.X + ldx;
+  lPt2.Y := lPt1.Y + ldy;
+  APage.DrawEllipse(lPt2, lw, lw, 1, False, True);
+end;
+
+procedure TFPReportExportPDF.RenderShapeEllipse(const APage: TPDFPage; const lpt1: TPDFCoord;
+  const ALayout: TFPReportLayout);
+begin
+  APage.DrawEllipse(lPt1, ALayout.Width, ALayout.Height, 1, False, True);
+end;
+
+procedure TFPReportExportPDF.RenderShapeLine(const APage: TPDFPage; lpt1: TPDFCoord;
+    const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+var
+  lPt2: TPDFCoord;
+begin
+  case AOrientation of
+  orNorth, orSouth:
+   begin                                           //   | (1)
+     lPt1.X := lPt1.X + (ALayout.Width / 2);       //   |
+     lPt2.X := lPt1.X ;                            //   |
+     lPt2.Y := lPt1.Y;                             //   | (2)
+     lPt1.Y := lPt1.Y - ALayout.Height;
+   end;
+  orNorthEast, orSouthWest:
+   begin                                           //    / (1)
+     lPt2.X := lPt1.X;                             //   /
+     lPt1.X := lPt1.X + ALayout.Width;             //  /
+     lPt2.Y := lPt1.Y;                             // / (2)
+     lPt1.Y := lPt1.Y - ALayout.Height;
+   end;
+  orEast, orWest:
+   begin                                           // (1)    (2)
+     lPt2.X := lPt1.X + ALayout.Width;             // ----------
+     lPt1.Y := lPt1.Y - (ALayout.Height / 2);      //
+     lPt2.Y := lPt1.Y;                             //
+   end;
+  orSouthEast, orNorthWest:
+   begin
+     lPt1.Y := lPt1.Y - ALayout.Height;            // \ (1)
+     lPt2.X := lPt1.X + ALayout.Width;             //  \
+     lPt2.Y := lPt1.Y + ALayout.Height;            //   \
+   end;                                            //    \ (2)
+  end;
+  APage.DrawLine(lPt1, lPt2, 1);
+end;
+
+procedure TFPReportExportPDF.RenderShapeRect(const APage: TPDFPage; const lpt1: TPDFCoord;
+    const ALayout: TFPReportLayout);
+var
+  ldx, ldy, lw: TFPReportUnits;
+  P: TPDFCoord;
+begin
+  if ALayout.Width = ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := 0;
+    lw := ALayout.Width;
+  end
+  else if ALayout.Width > ALayout.Height then
+  begin
+    ldx := (ALayout.Width - ALayout.Height) / 2;
+    ldy := 0;
+    lw := ALayout.Height;
+  end
+  else if ALayout.Width < ALayout.Height then
+  begin
+    ldx := 0;
+    ldy := (ALayout.Height - ALayout.Width) / 2;
+    lw := ALayout.Height;
+  end;
+  P.X := lPt1.X + ldx;
+  { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+  P.Y := lPt1.Y + ldy;
+  APage.DrawRect(P, lw, lw, 1, False, True);
+end;
+
+procedure TFPReportExportPDF.RenderShapeTriangle(const APage: TPDFPage; Alpt: TPDFCoord;
+    const AOrientation: TFPReportOrientation; const ALayout: TFPReportLayout);
+var
+  lPt1, lPt2, lPt3: TPDFCoord;  // original Report point
+  lOrigin: TPDFCoord;
+  W, H: TFPReportUnits;
+begin
+  lOrigin.X := AlPt.X;
+  lOrigin.Y := ALPT.Y - ALayout.Height;
+    W:=ALayout.Width;
+    H:=ALayout.Height;
+    case AOrientation of
+    orNorth:
+      begin
+      lPt1.X := lOrigin.X + (W / 2); //      1
+      lPt1.Y := lOrigin.Y;           //      /\
+      lPt2.X := lOrigin.X;           //     /  \
+      lPt2.Y := lOrigin.Y + H;       //    /____\
+      lPt3.X := lOrigin.X + W;       //  2       3
+      lPt3.Y := lPt2.Y;
+      end;
+    orNorthEast:
+      begin
+      lPt1.X := lOrigin.X + (W );    //   +-------1
+      lPt1.Y  := lOrigin.Y;          //   |       |
+      lPt2.X := lOrigin.X;           //   2       |
+      lPt2.Y  := lOrigin.Y + H/2;    //   |       |
+      lPt3.X := lOrigin.X + W/2;     //   +---3---+
+      lPt3.Y  := lPt1.Y + H;
+      end;
+    orSouth:
+      begin
+      lPt1.X := lOrigin.X;            //  1 ------ 2
+      lPt1.Y  := lOrigin.Y;           //    \    /
+      lPt2.X := lOrigin.X+ W;         //     \  /
+      lPt2.Y  := lOrigin.Y;           //      \/
+      lPt3.X := lOrigin.X + (W / 2);  //      3
+      lPt3.Y  := lOrigin.Y+H;
+      end;
+    orSouthEast:
+      begin
+      lPt1.X := lOrigin.X + (W/2);   //   +---1---+
+      lPt1.Y  := lOrigin.Y;          //   |       |
+      lPt2.X := lOrigin.X;           //   2       |
+      lPt2.Y  := lOrigin.Y + H/2;    //   |       |
+      lPt3.X := lOrigin.X + W;       //   +-------3
+      lPt3.Y  := lPt1.Y + H;
+      end;
+    orEast:
+      begin
+      lPt1.X := lOrigin.X;            //   1
+      lPt1.Y  := lOrigin.Y ;          //   |\
+      lPt2.X := lOrigin.X + W;        //   | \ 2
+      lPt2.Y  := lOrigin.Y + (H / 2); //   | /
+      lPt3.X := lOrigin.X;            //   |/
+      lPt3.Y  := lOrigin.Y + H;       //   3
+      end;
+    orNorthWest:
+      begin
+      lPt1.X := lOrigin.X;           //   1-------+
+      lPt1.Y  := lOrigin.Y;          //   |       |
+      lPt2.X := lOrigin.X+W;         //   |       2
+      lPt2.Y  := lOrigin.Y + H/2;    //   |       |
+      lPt3.X := lOrigin.X + W/2;     //   +---3---+
+      lPt3.Y  := lPt1.Y + H;
+      end;
+    orWest:
+      begin
+      lPt1.X := lOrigin.X + W;       //       1
+      lPt1.Y  := lOrigin.Y;          //      /|
+      lPt2.X := lOrigin.X;           //   2 / |
+      lPt2.Y  := lOrigin.Y + H / 2;  //     \ |
+      lPt3.X := lOrigin.X + W;       //      \|
+      lPt3.Y  := lOrigin.Y+ H;       //       3
+      end;
+    orSouthWest:
+      begin
+      lPt1.X := lOrigin.X+ H/2;      //   +---1---+
+      lPt1.Y  := lOrigin.Y;          //   |       |
+      lPt2.X := lOrigin.X+W;         //   |       2
+      lPt2.Y  := lOrigin.Y + H/2;    //   |       |
+      lPt3.X := lOrigin.X ;          //   3-------+
+      lPt3.Y  := lPt1.Y + H;
+      end;
+    end;
+  APage.DrawLine(lPt1, lPt2, 1);
+  APage.DrawLine(lPt2, lPt3, 1);
+  APage.DrawLine(lPt3, lPt1, 1);
+end;
+
+procedure TFPReportExportPDF.RenderShapeRoundedRect(const APage: TPDFPage; const lpt1: TPDFCoord;
+  const ARadius: TFPReportUnits; const ALayout: TFPReportLayout);
+begin
+
+end;
+
+constructor TFPReportExportPDF.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FDocument := nil;
+  FFileName :=  ApplicationName + '.pdf';
+  FAutoSave := True;
+end;
+
+destructor TFPReportExportPDF.Destroy;
+begin
+  FDocument.Free;
+  inherited Destroy;
+end;
+
+class function TFPReportExportPDF.Name: String;
+begin
+  Result:='PDF';
+end;
+
+class function TFPReportExportPDF.Description: String;
+begin
+  Result:='PDF file';
+end;
+
+class function TFPReportExportPDF.DefaultExtension: String;
+begin
+  Result:='.pdf';
+end;
+
+procedure TFPReportExportPDF.SetFileName(const aFileName: String);
+begin
+  Filename:=aFileName;
+end;
+
+
+procedure TFPReportExportPDF.RenderImage(aRect: TFPReportRect; var AImage: TFPCustomImage);
+
+var
+  LPT : TPDFCoord;
+  Idx : Integer;
+  pdfimg: TPDFImageItem;
+
+begin
+  LPT.X:=aRect.Left;
+  LPT.Y:=aRect.Top;
+  idx:=Document.Images.Count-1;
+  While (Idx>=0) and not Document.Images.Images[idx].Equals(AImage) do
+    Dec(Idx);
+  if idx = -1 then
+    begin
+    pdfimg := Document.Images.AddImageItem;
+    pdfimg.Image := AImage;
+    pdfimg.OwnsImage:=True;
+    idx := Document.Images.Count-1;
+    end;
+  CurrentPage.DrawImage(lPt, aRect.Width, ARect.Height, idx);
+  aImage:=Nil; // PDF now owns the image
+end;
+
+procedure TFPReportExportPDF.DoExecute(const ARTObjects: TFPList);
+var
+  pg: TPDFPage;
+  p, b, m: integer;
+  rpage: TFPReportPage;
+  rband: TFPReportCustomBand;
+  lPt1: TPDFCoord;  // original Report point
+  lPDFPaper: TPDFPaper;
+
+begin
+  SetupPDFDocument;
+
+  for p := 0 to (ARTObjects.Count - 1) do
+  begin
+    rpage := TFPReportPage(ARTObjects[p]);
+
+    pg := FDocument.Pages.AddPage;
+    FCurrentPage:=pg;
+    case rpage.PageSize.PaperName of
+      'A4':     pg.PaperType := ptA4;
+      'A5':     pg.PaperType := ptA5;
+      'Letter': pg.PaperType := ptLetter;
+      'Legal':  pg.PaperType := ptLegal;
+      'DL':     pg.PaperType := ptDL;
+      'C5':     pg.PaperType := ptC5;
+      'B5':     pg.PaperType := ptB5
+      else
+      begin
+        lPDFPaper.W := Round(mmToPDF(rpage.PageSize.Width));
+        lPDFPaper.H := Round(mmToPDF(rpage.PageSize.Height));
+        pg.Paper := lPDFPaper;
+        pg.PaperType := ptCustom;
+      end;
+    end;  { case PaperName }
+    pg.UnitOfMeasure := uomMillimeters; { report measurements are always in millimeter units }
+
+    // Convert from the Cartesian coordinate system to the Screen coordinate system
+    pg.Matrix.SetYScalation(-1);
+    pg.Matrix.SetYTranslation(pg.GetPaperHeight);
+
+    if rpage.Orientation = poLandscape then
+      pg.Orientation := ppoLandscape;
+
+    for b := 0 to (rpage.BandCount - 1) do
+      begin
+      rband := rpage.Bands[b];
+      lPt1.X := rband.RTLayout.Left;
+      { PDF origin coordinate is Bottom-Left, and Report Layout is Top-Left }
+      lPt1.Y := rband.RTLayout.Top + rband.RTLayout.Height;
+      RenderFrame(pg, rband, rband.Frame, lPt1, rband.RTLayout.Width, rband.RTLayout.Height);
+      for m := 0 to (rband.ChildCount - 1) do
+        RenderElement(pg, rband, rband.Child[m]);
+      end;
+    Document.Sections[0].AddPage(pg);
+  end;
+  if FAutoSave then
+    SaveToFile;
+end;
+
+procedure TFPReportExportPDF.RenderElement(pg : TPDFPage; ABand : TFPReportCustomBand; el : TFPReportElement);
+
+Var
+  C : TFPReportPoint;
+  lpt : TPDFCoord;
+
+begin
+  if (el is TFPReportCustomMemo) then
+    RenderMemo(pg, aband, TFPReportCustomMemo(el))
+  else if (el is TFPReportCustomShape) then
+    RenderShape(pg, aband, TFPReportCustomShape(el))
+  else if (el is TFPReportCustomImage) then
+    RenderImage(pg, aband, TFPReportCustomImage(el))
+  else if (el is TFPReportCustomCheckbox) then
+    RenderCheckbox(pg, aband, TFPReportCustomCheckbox(el))
+  else
+    begin
+    // PDF coords
+    lPt.X := ABand.RTLayout.Left + el.RTLayout.Left;
+    lPt.Y := ABand.RTLayout.Top + el.RTLayout.Top + el.RTLayout.Height;
+    RenderFrame(pg, ABand, el.Frame, lPt, el.RTLayout.Width, el.RTLayout.Height);
+    C.Left:=aband.RTLayout.Left;
+    C.Top:=aband.RTLayout.Top;
+    RenderUnknownElement(C,El,72);
+    end;
+end;
+
+initialization
+  TFPReportExportPDF.RegisterExporter;
+end.
+

+ 595 - 0
packages/fcl-report/src/fpreportstreamer.pp

@@ -0,0 +1,595 @@
+{
+    This file is part of the Free Component Library.
+    Copyright (c) 2008 Michael Van Canneyt, member of the Free Pascal development team
+    Portions (C) 2016 WISA b.v.b.a.
+
+    Stream report definition to/from JSON Stream.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit fpReportStreamer;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpjson;
+
+type
+
+  { Using an abstract class in case we want to support multiple output writers. eg: JSON, XML etc }
+  TFPReportStreamer = class(TComponent)
+  public
+    function PushCurrentElement: TObject; virtual; abstract;
+    function PushElement(const AName: String): TObject; virtual; abstract;
+    function PushElement(AElement: TObject): TObject; virtual; abstract;
+    function PopElement: TObject; virtual; abstract;
+    function FindChild(const AName: String): TObject; virtual; abstract;
+    function NewElement(const AName: String): TObject; virtual; abstract;
+    function ChildCount: integer; virtual; abstract;
+    function GetChild(AIndex: Integer): TObject; virtual; abstract;
+    function CurrentElementName: string; virtual; abstract;
+
+    // Writing properties of the current element
+    procedure   WriteInteger(AName: String; AValue: Integer); virtual; abstract;
+    procedure   WriteFloat(AName: String; AValue: Extended); virtual; abstract;
+    procedure   WriteString(AName: String; AValue: String); virtual; abstract;
+    procedure   WriteBoolean(AName: String; AValue: Boolean); virtual; abstract;
+    procedure   WriteDateTime(AName: String; AValue: TDateTime); virtual; abstract;
+    procedure   WriteStream(AName: String; AValue: TStream); virtual; abstract;
+    // Writing properties but only when different from original
+    procedure   WriteIntegerDiff(AName: String; AValue, AOriginal: Integer); virtual; abstract;
+    procedure   WriteFloatDiff(AName: String; AValue, AOriginal: Extended); virtual; abstract;
+    procedure   WriteStringDiff(AName: String; AValue, AOriginal: String); virtual; abstract;
+    procedure   WriteBooleanDiff(AName: String; AValue, AOriginal: Boolean); virtual; abstract;
+    procedure   WriteDateTimeDiff(AName: String; AValue, AOriginal: TDateTime); virtual; abstract;
+    procedure   WriteStreamDiff(AName: String; AValue, AOriginal: TStream); virtual; abstract;
+    // Reading properties
+    function    ReadInteger(AName: String; ADefault: Integer): Integer; virtual; abstract;
+    function    ReadFloat(AName: String; ADefault: Extended): Extended; virtual; abstract;
+    function    ReadString(AName: String; ADefault: String): String; virtual; abstract;
+    function    ReadDateTime(AName: String; ADefault: TDateTime): TDateTime; virtual; abstract;
+    function    ReadBoolean(AName: String; ADefault: Boolean): Boolean; virtual; abstract;
+    function    ReadStream(AName: String; AValue: TStream) : Boolean; virtual; abstract;
+  end;
+
+
+  { TFPReportJSONStreamer }
+
+  TFPReportJSONStreamer = class(TFPReportStreamer)
+  private
+    Fjson: TJSONObject;
+    FCurrentElement: TJSONObject;
+    FOwnsJSON: Boolean;
+    FStack: TFPList;
+    procedure   SetCurrentElement(AValue: TJSONObject);
+    function    DateTimeAsIntlDateStor(const ADateTime: TDateTime): string;
+    function    IntlDateStorAsDateTime(const AValue: string): TDateTime;
+    procedure   InitialiseCurrentElement;
+    procedure   SetJSON(AValue: TJSONObject);
+    procedure   SetOwnsJSON(AValue: Boolean);
+  public
+    // FPReportStreamer interface
+    procedure   WriteInteger(AName: String; AValue: Integer); override;
+    procedure   WriteFloat(AName: String; AValue: Extended); override;
+    procedure   WriteString(AName: String; AValue: String); override;
+    procedure   WriteBoolean(AName: String; AValue: Boolean); override;
+    procedure   WriteDateTime(AName: String; AValue: TDateTime); override;
+    procedure   WriteStream(AName: String; AValue: TStream); override;
+    procedure   WriteIntegerDiff(AName: String; AValue, AOriginal: Integer); override;
+    procedure   WriteFloatDiff(AName: String; AValue, AOriginal: Extended); override;
+    procedure   WriteStringDiff(AName: String; AValue, AOriginal: String); override;
+    procedure   WriteBooleanDiff(AName: String; AValue, AOriginal: Boolean); override;
+    procedure   WriteDateTimeDiff(AName: String; AValue, AOriginal: TDateTime); override;
+    procedure   WriteStreamDiff(AName: String; AValue, AOriginal: TStream); override;
+    function    ReadInteger(AName: String; ADefault: Integer): Integer; override;
+    function    ReadFloat(AName: String; ADefault: Extended): Extended; override;
+    function    ReadString(AName: String; ADefault: String): String; override;
+    function    ReadDateTime(AName: String; ADefault: TDateTime): TDateTime; override;
+    function    ReadBoolean(AName: String; ADefault: Boolean): Boolean; override;
+    function    ReadStream(AName: String; AValue: TStream) : Boolean; override;
+    function    PushCurrentElement: TObject; override;
+    function    PushElement(const AName: String): TObject; override;
+    function    PushElement(AElement: TObject): TObject; override;
+    function    PopElement: TObject; override;
+    function    FindChild(const AName: String): TObject; override;
+    function    NewElement(const AName: String): TObject; override;
+    function    ChildCount: integer; override;
+    function    GetChild(AIndex: Integer): TObject; override;
+    function    CurrentElementName: string; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor  Destroy; override;
+    function    StreamToHex(S: TStream): String;
+    function    StreamsEqual(S1, S2: TStream): Boolean;
+    function    HexToStringStream(S: String): TStringStream;
+    property    JSON: TJSONObject read Fjson write SetJSON;
+    Property    OwnsJSON : Boolean Read FOwnsJSON Write SetOwnsJSON;
+    property    CurrentElement: TJSONObject read FCurrentElement write SetCurrentElement;
+  end;
+
+
+  EReportDOM = class(Exception);
+
+
+implementation
+
+resourcestring
+  SErrStackEmpty = 'Element stack is empty';
+  SErrNoCurrentElement = 'No current element to find node %s below';
+  SErrNodeNotElement = 'Node %s is not an element node';
+
+const
+  { Summary of ISO 8601  http://www.cl.cam.ac.uk/~mgk25/iso-time.html }
+  cIntlDateTimeStor = 'yyyymmdd"T"hhnnss';    // for storage
+
+
+{ TFPReportJSONStreamer }
+
+procedure TFPReportJSONStreamer.SetCurrentElement(AValue: TJSONObject);
+begin
+  if FCurrentElement = AValue then Exit;
+  FCurrentElement := AValue;
+end;
+
+{ Borrowed implementation from tiOPF's tiUtils unit. }
+function TFPReportJSONStreamer.DateTimeAsIntlDateStor(const ADateTime: TDateTime): string;
+begin
+  Result := FormatDateTime(cIntlDateTimeStor, ADateTime);
+  if Pos('18991230', Result) = 1 then
+    Result := StringReplace(Result, '18991230', '00000000', [rfReplaceAll]);
+end;
+
+function TFPReportJSONStreamer.IntlDateStorAsDateTime(const AValue: string): TDateTime;
+var
+  lY, lM, lD, lH, lMi, lS: Word;
+begin
+  if Trim(AValue) = '' then
+  begin
+    Result := 0;
+    Exit; //==>
+  end;
+
+    //          1         2
+    // 12345678901234567890123
+    // yyyymmddThhnnss
+  lY := StrToInt(Copy(AValue, 1, 4));
+  lM := StrToInt(Copy(AValue, 5, 2));
+  lD := StrToInt(Copy(AValue, 7, 2));
+  lH := StrToInt(Copy(AValue, 10, 2));
+  lMi := StrToInt(Copy(AValue, 12, 2));
+  lS := StrToInt(Copy(AValue, 14, 2));
+
+  { Cannot EncodeDate if any part equals 0. EncodeTime is okay. }
+  if (lY = 0) or (lM = 0) or (lD = 0) then
+    Result := EncodeTime(lH, lMi, lS, 0)
+  else
+    Result := EncodeDate(lY, lM, lD) + EncodeTime(lH, lMi, lS, 0);
+end;
+
+procedure TFPReportJSONStreamer.InitialiseCurrentElement;
+begin
+  FCurrentElement := Fjson;
+end;
+
+procedure TFPReportJSONStreamer.SetJSON(AValue: TJSONObject);
+begin
+  if Fjson = AValue then
+    Exit;
+  if Assigned(Fjson) and OwnsJSON then
+    FreeAndNil(FJson);
+  Fjson := AValue;
+  InitialiseCurrentElement;
+end;
+
+procedure TFPReportJSONStreamer.SetOwnsJSON(AValue: Boolean);
+begin
+  if FOwnsJSON=AValue then Exit;
+  FOwnsJSON:=AValue;
+  if Not FOwnsJSON then // We no longer own, so free and nil
+    FreeAndNil(FJSON);
+end;
+
+procedure TFPReportJSONStreamer.WriteInteger(AName: String; AValue: Integer);
+begin
+  CurrentElement.Add(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteFloat(AName: String; AValue: Extended);
+begin
+  CurrentElement.Add(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteString(AName: String; AValue: String);
+begin
+  CurrentElement.Add(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteBoolean(AName: String; AValue: Boolean);
+begin
+  CurrentElement.Add(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteDateTime(AName: String; AValue: TDateTime);
+begin
+  CurrentElement.Add(AName, DateTimeAsIntlDateStor(AValue));
+end;
+
+procedure TFPReportJSONStreamer.WriteStream(AName: String; AValue: TStream);
+begin
+  WriteString(AName, StreamToHex(AValue));
+end;
+
+procedure TFPReportJSONStreamer.WriteIntegerDiff(AName: String; AValue, AOriginal: Integer);
+begin
+  if (AValue <> AOriginal) then
+    WriteInteger(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteFloatDiff(AName: String; AValue, AOriginal: Extended);
+begin
+  if (AValue <> AOriginal) then
+    WriteFloat(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteStringDiff(AName: String; AValue, AOriginal: String);
+begin
+  if (AValue <> AOriginal) then
+    WriteString(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteBooleanDiff(AName: String; AValue, AOriginal: Boolean);
+begin
+  if (AValue <> AOriginal) then
+    WriteBoolean(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteDateTimeDiff(AName: String; AValue, AOriginal: TDateTime);
+begin
+  if (AValue <> AOriginal) then
+    WriteDateTime(AName, AValue);
+end;
+
+procedure TFPReportJSONStreamer.WriteStreamDiff(AName: String; AValue, AOriginal: TStream);
+begin
+  if StreamsEqual(AValue, AOriginal) then
+    Exit;
+  WriteStream(AName, AValue);
+end;
+
+function TFPReportJSONStreamer.ReadInteger(AName: String; ADefault: Integer): Integer;
+var
+  d: TJSONData;
+begin
+  d := FindChild(AName) as TJSONData;
+  if d = nil then
+    Result := ADefault
+  else
+  begin
+    if d.JSONType = jtNumber then
+      Result := d.AsInt64
+    else
+      Result := ADefault;
+  end;
+end;
+
+function TFPReportJSONStreamer.ReadFloat(AName: String; ADefault: Extended): Extended;
+var
+  d: TJSONData;
+begin
+  d := FindChild(AName) as TJSONData;
+  if d = nil then
+    Result := ADefault
+  else
+  begin
+    if d.JSONType = jtNumber then
+      Result := d.AsFloat
+    else
+      Result := ADefault;
+  end;
+end;
+
+function TFPReportJSONStreamer.ReadString(AName: String; ADefault: String): String;
+var
+  d: TJSONData;
+begin
+  d := FindChild(AName) as TJSONData;
+  if d = nil then
+    Result := ADefault
+  else
+  begin
+    if d.JSONType = jtString then
+      Result := d.AsString
+    else
+      Result := ADefault;
+  end;
+end;
+
+function TFPReportJSONStreamer.ReadDateTime(AName: String; ADefault: TDateTime): TDateTime;
+var
+  d: TJSONData;
+begin
+  d := FindChild(AName) as TJSONData;
+  if d = nil then
+    Result := ADefault
+  else
+  begin
+    if d.JSONType = jtString then
+    begin
+      try
+        Result := IntlDateStorAsDateTime(d.AsString)
+      except
+        on E: EConvertError do
+          Result := ADefault;
+      end
+    end
+    else
+      Result := ADefault;
+  end;
+end;
+
+function TFPReportJSONStreamer.ReadBoolean(AName: String; ADefault: Boolean): Boolean;
+var
+  d: TJSONData;
+begin
+  d := FindChild(AName) as TJSONData;
+  if d = nil then
+    Result := ADefault
+  else
+  begin
+    if d.JSONType = jtBoolean then
+      Result := d.AsBoolean
+    else
+      Result := ADefault;
+  end;
+end;
+
+function TFPReportJSONStreamer.ReadStream(AName: String; AValue: TStream): Boolean;
+var
+  S: string;
+  SS: TStringStream;
+begin
+  S := ReadString(AName, '');
+  Result := (S <> '');
+  if Result then
+  begin
+    SS := HexToStringStream(S);
+    try
+      AValue.CopyFrom(SS, 0);
+    finally
+      SS.Free;
+    end;
+  end;
+end;
+
+function TFPReportJSONStreamer.PushCurrentElement: TObject;
+begin
+  if not Assigned(FStack) then
+    FStack := TFPList.Create;
+  FStack.Add(FCurrentElement);
+  Result := FCurrentElement;
+end;
+
+function TFPReportJSONStreamer.PushElement(const AName: String): TObject;
+begin
+  PushCurrentElement;
+  Result := NewElement(AName);
+end;
+
+function TFPReportJSONStreamer.PushElement(AElement: TObject): TObject;
+begin
+  PushCurrentElement;
+  CurrentElement := TJSONObject(AElement);
+  Result := CurrentElement;
+end;
+
+function TFPReportJSONStreamer.PopElement: TObject;
+begin
+  if (FStack = nil) or (FStack.Count = 0) then
+    raise EReportDOM.Create(SErrStackEmpty);
+  Result := FCurrentElement;
+  FCurrentElement := TJSONObject(FStack[FStack.Count - 1]);
+  FStack.Delete(FStack.Count - 1);
+  if (FStack.Count = 0) then
+    FreeAndNil(FStack);
+end;
+
+function FindRecursive(AData: TJSONObject; AName: string): TJSONData;
+var
+  i: integer;
+  d: TJSONData;
+  o: TJSONObject;
+begin
+  Result := AData.Find(AName);
+  if Result <> nil then // we found it
+    Exit
+  else
+  begin
+    for i := 0 to AData.Count-1 do
+    begin
+      d := AData.Items[i];
+      if d.JSONType = jtObject then
+      begin
+        o := TJSONObject(d);
+        Result := FindRecursive(o, AName);
+        if Result <> nil then
+          exit;
+      end;
+    end;
+  end;
+end;
+
+function TFPReportJSONStreamer.FindChild(const AName: String): TObject;
+var
+  i: integer;
+  d: TJSONData;
+  o: TJSONObject;
+begin
+  Result := CurrentElement.Find(AName);
+  if Result = nil then
+  begin
+    for i := 0 to CurrentElement.Count-1 do
+    begin
+      d := CurrentElement.Items[i];
+      if d.JSONType = jtObject then
+      begin
+        o := TJSONObject(d);
+        Result := FindRecursive(o, AName);
+        if Result <> nil then
+          exit;
+      end;
+    end;
+  end
+end;
+
+function TFPReportJSONStreamer.NewElement(const AName: String): TObject;
+var
+  obj: TJSONObject;
+begin
+  obj := TJSONObject.Create;
+  FCurrentElement.Add(AName, obj);
+  FCurrentElement := obj;
+  Result := FCurrentElement;
+end;
+
+function TFPReportJSONStreamer.ChildCount: integer;
+begin
+  Result := FCurrentElement.Count;
+end;
+
+function TFPReportJSONStreamer.GetChild(AIndex: Integer): TObject;
+begin
+  if (ChildCount = 0) or (AIndex > ChildCount-1) then
+    result := nil
+  else
+  begin
+    Result := FCurrentElement.Items[AIndex];
+  end;
+end;
+
+function TFPReportJSONStreamer.CurrentElementName: string;
+begin
+  if Assigned(FCurrentElement) then
+    Result := TJSONObject(FCurrentElement).Names[0]
+  else
+    Result := '';
+end;
+
+constructor TFPReportJSONStreamer.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FOwnsJSON:=True;
+  Fjson := TJSONObject.Create;
+  InitialiseCurrentElement;
+end;
+
+destructor TFPReportJSONStreamer.Destroy;
+begin
+  FreeAndNil(FStack);
+  If OwnsJSON then
+    FreeAndNil(Fjson);
+  inherited Destroy;
+end;
+
+function TFPReportJSONStreamer.StreamToHex(S: TStream): String;
+var
+  T: TMemoryStream;
+  P, PD: PChar;
+  I, L: integer;
+  h: string[2];
+begin
+  if (S is TMemoryStream) then
+    T := S as TMemoryStream
+  else
+  begin
+    T := TMemoryStream.Create;
+    T.CopyFrom(S, 0);
+  end;
+  try
+    L := T.Size;
+    SetLength(Result, L * 2);
+    PD := PChar(Result);
+    P := PChar(T.Memory);
+    for I := 1 to L do
+    begin
+      H := HexStr(Ord(P^), 2);
+      PD^ := H[1];
+      Inc(PD);
+      PD^ := H[2];
+      Inc(P);
+      Inc(PD);
+    end;
+  finally
+    S.Position := 0;
+  end;
+end;
+
+function TFPReportJSONStreamer.StreamsEqual(S1, S2: TStream): Boolean;
+var
+  S: TStringStream;
+  T: string;
+begin
+  Result := (S1 = S2);
+  if not Result then
+  begin
+    Result := (S1.Size = S2.Size);
+    if Result then
+    begin
+      S := TStringStream.Create('');
+      try
+        S.CopyFrom(S1, 0);
+        T := S.DataString;
+        S.Size := 0;
+        S.CopyFrom(S2, 0);
+        Result := (T = S.DataString);
+      finally
+        S.Free;
+      end;
+    end;
+  end;
+end;
+
+function TFPReportJSONStreamer.HexToStringStream(S: String): TStringStream;
+var
+  T: string;
+  I, J: integer;
+  B: byte;
+  P: PChar;
+  H: string[3];
+begin
+  Result := nil;
+  SetLength(H, 3);
+  H[1] := '$';
+  if (S <> '') then
+  begin
+    SetLength(T, Length(S) div 2);
+    P := PChar(T);
+    I := 1;
+    while I < Length(S) do
+    begin
+      H[2] := S[i];
+      Inc(I);
+      H[3] := S[i];
+      Inc(I);
+      Val(H, B, J);
+      if (J = 0) then
+        P^ := char(B)
+      else
+        P^ := #0;
+      Inc(P);
+    end;
+    Result := TStringStream.Create(T);
+  end;
+end;
+
+end.

BIN
packages/fcl-report/test/fonts/LiberationSerif-Regular.ttf


BIN
packages/fcl-report/test/fonts/calibri.ttf


BIN
packages/fcl-report/test/fonts/calibrib.ttf


BIN
packages/fcl-report/test/fonts/calibrii.ttf


BIN
packages/fcl-report/test/fonts/calibriz.ttf


+ 100 - 0
packages/fcl-report/test/guitestfpreport.lpi

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <LRSInOutputDirectory Value="False"/>
+      </Flags>
+      <SessionStorage Value="InIDEConfig"/>
+      <MainUnit Value="0"/>
+      <Title Value="guitestfpreport"/>
+    </General>
+    <VersionInfo>
+      <Language Value=""/>
+      <CharSet Value=""/>
+    </VersionInfo>
+    <BuildModes Count="1">
+      <Item1 Name="default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IgnoreBinaries Value="False"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="fclreport"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="FPCUnitTestRunner"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="6">
+      <Unit0>
+        <Filename Value="guitestfpreport.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="tcbasereport.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="tcreportdom.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="regtests.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="tchtmlparser.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit4>
+      <Unit5>
+        <Filename Value="tcreportstreamer.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit5>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="guitestfpreport"/>
+    </Target>
+    <SearchPaths>
+      <UnitOutputDirectory Value="units"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <CStyleOperator Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-dfptestX"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="2">
+      <Item1>
+        <Name Value="ECodetoolError"/>
+      </Item1>
+      <Item2>
+        <Name Value="EFOpenError"/>
+      </Item2>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 26 - 0
packages/fcl-report/test/guitestfpreport.lpr

@@ -0,0 +1,26 @@
+program guitestfpreport;
+
+{$mode objfpc}{$H+}
+
+uses
+  Classes
+  ,Interfaces
+  ,Forms
+  ,GuiTestRunner
+  ,regtests
+  ;
+
+
+
+
+procedure MainProc;
+begin
+  Application.Initialize;
+  Application.CreateForm(TGUITestRunner, TestRunner);
+  Application.Run;
+end;
+
+
+begin
+  MainProc;
+end.

+ 15 - 0
packages/fcl-report/test/regtests.pp

@@ -0,0 +1,15 @@
+unit regtests;
+{
+  Add this unit to the main program uses clause.
+  Add all test units to the uses clause here.
+  Avoids messing with the uses clause of the main program(s).
+}
+interface
+
+uses
+  tcbasereport, tcreportstreamer, tchtmlparser;
+
+implementation
+  
+end.
+

+ 3705 - 0
packages/fcl-report/test/tcbasereport.pp

@@ -0,0 +1,3705 @@
+unit tcbasereport;
+
+{$mode objfpc}{$H+}
+
+{.$define gdebug}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpcunit,
+  testregistry,
+  fpexprpars,
+  fpCanvas,
+  fpReport;
+
+type
+
+  TMyFPReportComponent = class(TFPReportComponent)
+  public
+    procedure StartLayout; override;
+    procedure EndLayout; override;
+    procedure StartRender; override;
+    procedure EndRender; override;
+  end;
+
+
+  TMyFPReportElement = class(TFPReportElement)
+  private
+    FChangedCalled: integer;
+  public
+    procedure CallChange;
+    procedure ResetChanged;
+    procedure DoChanged; override;
+    property ChangedCalled: integer read FChangedCalled;
+  end;
+
+
+  TMyFPReportElementWithChildren = class(TFPReportElementWithChildren)
+  private
+    FChangedCalled: integer;
+  public
+    procedure CallChange;
+    procedure ResetChanged;
+    procedure DoChanged; override;
+    property ChangedCalled: integer read FChangedCalled;
+  end;
+
+
+  TMyFPReportPageSize = class(TFPReportPageSize)
+  private
+    FChangedCalled: integer;
+  public
+    procedure ResetChanged;
+    procedure Changed; override;
+    property ChangedCalled: integer read FChangedCalled;
+  end;
+
+
+  TMyFPReportPage = class(TFPReportPage)
+  private
+    FChangedCalled: integer;
+    FPrepareObjectsCalled: integer;
+    procedure SetupPage;
+  protected
+    procedure PrepareObjects; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure ResetChanged;
+    procedure DoChanged; override;
+    property ChangedCalled: integer read FChangedCalled;
+  end;
+
+
+  TMyReportTitleBand = class(TFPReportCustomTitleBand)
+  private
+    FPrepareObjectsCalled: integer;
+  protected
+    procedure PrepareObjects; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+
+  TMyDataBand = class(TFPReportDataBand)
+  private
+    FPrepareObjectsCalled: integer;
+  protected
+    procedure PrepareObjects; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+
+  TMyCustomReport = class(TFPReport)
+  end;
+
+
+  TMyFPReportData = class(TFPReportData)
+  private
+    FCC: integer;
+    FDFC: integer;
+    FEC: integer;
+    FFC: integer;
+    FNC: integer;
+    FOC: integer;
+    FOE: boolean;
+    FReportEOF: boolean;
+  public
+    procedure ResetCounts;
+    procedure DoInitDataFields; override;
+    procedure DoOpen; override;
+    procedure DoFirst; override;
+    procedure DoNext; override;
+    procedure DoClose; override;
+    function DoEOF: boolean; override;
+    property InitDataFieldsCount: integer read FDFC;
+    property OpenCount: integer read FOC;
+    property FirstCount: integer read FFC;
+    property NextCount: integer read FNC;
+    property CloseCount: integer read FCC;
+    property EOFCount: integer read FEC;
+    property ReportEOF: boolean read FReportEOF write FReportEOF;
+    property OldEOF: boolean read FOE;
+    property Datafields;
+  end;
+
+
+  TTestFPPageSize = class(TTestCase)
+  published
+    procedure TestCreate;
+  end;
+
+
+  TTestFPPapers = class(TTestCase)
+  protected
+    FM: TFPReportPaperManager;
+    procedure Setup; override;
+    procedure TearDown; override;
+    procedure RegisterPapers(ACount: integer; Local: boolean = True);
+  end;
+
+
+  TTestFPPaperManager = class(TTestFPPapers)
+  private
+    FAccess: integer;
+    procedure TestAccess;
+  protected
+    procedure Setup; override;
+  published
+    procedure TestCreate;
+    procedure TestRegister1;
+    procedure TestRegister2;
+    procedure TestRegister3;
+    procedure TestRegisterDuplicate;
+    procedure TestClear;
+    procedure TestFind1;
+    procedure TestFind2;
+    procedure TestFind3;
+    procedure IllegalAccess1;
+    procedure IllegalAccess2;
+    procedure IllegalAccess3;
+    procedure IllegalAccess4;
+    procedure IllegalAccess5;
+    procedure IllegalAccess6;
+    procedure IllegalAccess7;
+    procedure IllegalAccess8;
+    procedure TestWidth;
+    procedure TestHeight;
+  end;
+
+
+  TTestFPReportPageSize = class(TTestFPPapers)
+  private
+    FP: TMyFPReportPageSize;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestCreate;
+    procedure TestCreateWithPage;
+    procedure TestCreateByPage;
+    procedure TestChanged1;
+    procedure TestChanged2;
+    procedure TestChanged3;
+    procedure TestPaperName1;
+    procedure TestPaperName2;
+    procedure TestAssign;
+  end;
+
+
+  TBaseReportComponentTest = class(TTestCase)
+  private
+    FC: TMyFPReportComponent;
+    procedure ExpectState(const aExpected: TFPReportState);
+  protected
+    procedure AssertEquals(Msg: string; const aExpected, AActual: TFPReportState); overload;
+    procedure SetUp; override;
+    procedure TearDown; override;
+  end;
+
+
+  TTestReportComponent = class(TBaseReportComponentTest)
+  published
+    procedure TestCreate;
+    procedure TestLayoutState;
+    procedure TestRenderState;
+  end;
+
+
+  TBaseReportElementTest = class(TTestCase)
+  private
+    FC: TMyFPReportElement;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  end;
+
+
+  TReportElementTest = class(TBaseReportElementTest)
+  published
+    procedure TestCreate;
+    procedure TestDoChange;
+    procedure TestChangeCount;
+    procedure TestChangeCountNested;
+    procedure TestChangeCountNested2;
+    procedure TestVisibleChanges;
+    procedure TestLayoutChanges;
+    procedure TestFrameChanges;
+    procedure TestAssign;
+    procedure TestEquals1;
+    procedure TestEquals2;
+    procedure TestEquals3;
+    procedure TestEquals4;
+    procedure TestEquals5;
+  end;
+
+
+  TTestReportChildren = class(TTestCase)
+  private
+    FC: TMyFPReportElementWithChildren;
+    FChild: TFPReportElement;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    procedure WrongParent;
+  published
+    procedure TestCreate;
+    procedure TestSetParent1;
+    procedure TestSetParent2;
+    procedure TestSetParent3;
+    procedure TestSetParent4;
+    procedure TestSetParent5;
+    procedure TestSetParent6;
+  end;
+
+
+  TTestReportFrame = class(TBaseReportElementTest)
+  published
+    procedure TestCreate;
+    procedure TestWidthChange;
+    procedure TestColorChange;
+    procedure TestPenStyleChange;
+    procedure TestShapeChange;
+    procedure TestLinesChange;
+    procedure TestAssign;
+    procedure TestEquals1;
+    procedure TestEquals2;
+    procedure TestEquals3;
+    procedure TestEquals4;
+    procedure TestEquals5;
+    procedure TestEquals6;
+    procedure TestEquals7;
+  end;
+
+
+  TTestReportLayout = class(TBaseReportElementTest)
+  published
+    procedure TestCreate;
+    procedure TestTopChange;
+    procedure TestLeftChange;
+    procedure TestWidthChange;
+    procedure TestHeightChange;
+    procedure TestAssign;
+    procedure TestEquals1;
+    procedure TestEquals2;
+    procedure TestEquals3;
+    procedure TestEquals4;
+    procedure TestEquals5;
+    procedure TestEquals6;
+  end;
+
+
+  TTestCaseWithData = class(TTestCase)
+  private
+    FData: TFPReportUserData;
+    FSL: TStringList;
+    procedure InitializeData(const ACount: integer);
+    procedure SetReportData(const ADataCount: Byte);
+    procedure DoGetDataValue(Sender: TObject; const AValueName: string; var AValue: variant);
+    procedure DoGetDataEOF(Sender: TObject; var IsEOF: boolean);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  public
+    property Data: TFPReportUserData read FData write FData;
+  end;
+
+
+  TTestCaseWithDataAndReport = class(TTestCaseWithData)
+  private
+    FReport: TMyCustomReport;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  public
+    property Report: TMyCustomReport read FReport write FReport;
+  end;
+
+
+  TTestReportPage = class(TTestCase)
+  private
+    FP: TMyFPReportPage;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestCreate1;
+    procedure TestCreate2;
+    procedure TestCreate3;
+    procedure TestPageSize1;
+    procedure TestPageSize2;
+    procedure TestPageSize3;
+    procedure TestBand1;
+    procedure TestBand2;
+    procedure TestData;
+    procedure TestAssign;
+    procedure TestFindBand;
+  end;
+
+
+  TTestReportData = class(TTestCase)
+  private
+    FD: TMyFPReportData;
+    FHandler: boolean;
+    procedure AssertField(Prefix: string; F: TFPReportDataField; AFieldName: string;
+      AFieldKind: TFPReportFieldKind; ADisplayWidth: integer = 0);
+  protected
+    procedure DoOpen(Sender: TObject);
+    procedure DoNext(Sender: TObject);
+    procedure Setup; override;
+    procedure TearDown; override;
+    procedure CreateFields;
+    procedure DoFieldByName;
+  published
+    procedure TestCreate;
+    procedure TestOpen1;
+    procedure TestNext;
+    procedure TestInitFieldDefs;
+    procedure TestInitFieldDefs_OnlyAllowedOnce;
+    procedure TestEOF1;
+    procedure TestAddDatafield;
+    procedure TestDatafieldAdd;
+    procedure TestCreateFields;
+    procedure TestDatafieldIndexOf1;
+    procedure TestDatafieldIndexOf2;
+    procedure TestFindField1;
+    procedure TestFindField2;
+    procedure TestFindByName1;
+    procedure TestFindByName2;
+    procedure TestFieldAssign;
+    procedure TestGetValue;
+    procedure TestEasyAccessProperties;
+  end;
+
+
+  { Testing UserData by pulling data from a DataField }
+  TTestUserReportData = class(TTestCase)
+  private
+    FD: TFPReportUserData;
+    FExpectName: string;
+    FReturnValue: variant;
+    procedure DoValue(Sender: TObject; const AValueName: string; var AValue: variant);
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestGetValue;
+  end;
+
+
+  { Testing UserData by pulling data from a StringList }
+  TTestUserReportData2 = class(TTestCase)
+  private
+    FData: TFPReportUserData;
+    FSL: TStringList;
+    procedure DoGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+    procedure DoGetEOF(Sender: TObject; var IsEOF: boolean);
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestGetValue;
+    procedure TestOnGetEOF1;
+    procedure TestOnGetEOF2;
+  end;
+
+
+  TTestDataBand = class(TTestCaseWithDataAndReport)
+  private
+    FDataBand: TFPReportDataBand;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestData;
+    procedure TestDataPropertyAutoSet;
+  end;
+
+
+  TTestCustomReport = class(TTestCase)
+  private
+    FRpt: TMyCustomReport;
+    FBeginReportCount: integer;
+    FEndReportCount: integer;
+    FSL: TStringList;
+    FData: TFPReportUserData;
+    procedure HandleOnBeginReport;
+    procedure HandleOnEndReport;
+    procedure InitializeData(const ACount: integer);
+    procedure SetReportData(const ADataCount: Byte);
+    procedure DoGetDataValue(Sender: TObject; const AValueName: string; var AValue: variant);
+    procedure DoGetDataEOF(Sender: TObject; var IsEOF: boolean);
+    procedure DoGetDataFieldNames(Sender: TObject; List: TStrings);
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  public
+    property Data: TFPReportUserData read FData;
+    property Report: TMyCustomReport read FRpt write FRpt;
+  published
+    procedure TestBeginReportEvent;
+    procedure TestEndReportEvent;
+    procedure TestPagePrepareObjects;
+    procedure TestBandPrepareObjects;
+    procedure TestRTObjects1;
+    procedure TestRTObjects2;
+    procedure TestRTObjects3;
+    procedure TestRTObjects4_OneDataItem;
+    procedure TestRTObjects5_TwoDataItems;
+    procedure TestInternalFunction_Page;
+    procedure TestInternalFunction_Page_with_text;
+    procedure TestInternalFunction_RecNo;
+    procedure TestInternalFunction_Today;
+    procedure TestInternalFunction_Today_with_text;
+    procedure TestInternalFunction_Author;
+    procedure TestInternalFunction_Author_with_text;
+    procedure TestInternalFunction_Title;
+    procedure TestInternalFunction_Title_with_text;
+  end;
+
+
+  TTestReportMemo = class(TTestCase)
+  private
+    FMemo: TFPReportMemo;
+    procedure CauseFontNotFoundException;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  published
+    procedure TestCreate;
+    procedure TestPrepareTextBlocks;
+    procedure TestPrepareTextBlocks_multiline_data;
+    procedure TestPrepareTextBlocks_multiline_wraptext;
+    procedure TestRGBToReportColor;
+    procedure TestHTMLColorToReportColor_length7;
+    procedure TestHTMLColorToReportColor_length6;
+    procedure TestHTMLColorToReportColor_length3;
+    procedure TestCreateTestBlock;
+    procedure TestCreateTestBlock_IsURL;
+    procedure TestSubStr;
+    procedure TestTokenCount;
+    procedure TestToken;
+  end;
+
+
+  TTestBandList = class(TTestCase)
+  private
+    FList: TBandList;
+    b1: TFPReportPageHeaderBand;
+    b2: TFPReportTitleBand;
+    b3: TFPReportDataBand;
+    procedure CreateBands;
+    procedure AddAllBandsToList;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  published
+    procedure TestAdd;
+    procedure TestItems;
+    procedure TestClear;
+    procedure TestDelete;
+    procedure TestFind1;
+    procedure TestFind2;
+  end;
+
+  { TTestVariableBase }
+
+  TTestVariableBase = Class(TTestCase)
+  Public
+    Class procedure AssertEquals(Const Msg : String; AExpected,AActual : TResultType); overload;
+  end;
+
+  { TTestVariable }
+
+  TTestVariable = Class(TTestVariableBase)
+  private
+    FVar: TFPReportVariable;
+  Protected
+    Procedure SetUp; override;
+    Procedure TearDown; override;
+    Property Variable : TFPReportVariable Read FVar;
+  Published
+    Procedure TestEmpty;
+    Procedure TestName;
+    Procedure TestBoolean;
+    Procedure TestInteger;
+    Procedure TestDateTime;
+    Procedure TestFloat;
+    Procedure TestString;
+    Procedure TestExpressionResult;
+  end;
+
+  { TTestVariables }
+
+  TTestVariables  = Class(TTestVariableBase)
+  private
+    FVar: TFPReportVariables;
+    FV : Array[0..2] of TFPReportVariable;
+    procedure AddThree;
+  Protected
+    Procedure SetUp; override;
+    Procedure TearDown; override;
+    Property Variables : TFPReportVariables Read FVar;
+  Published
+    Procedure TestEmpty;
+    Procedure TestAdd;
+    Procedure TestIndexOf;
+    Procedure TestFind;
+  end;
+
+implementation
+
+uses
+  TypInfo,
+  DateUtils,
+  fpTTF;
+
+type
+  TMemoFriend = class(TFPReportMemo);
+
+{ TTestVariables }
+
+procedure TTestVariables.SetUp;
+begin
+  inherited SetUp;
+  FVar:=TFPReportVariables.Create(Nil,TFPReportVariable);
+end;
+
+procedure TTestVariables.TearDown;
+begin
+  FreeAndNil(FVar);
+  inherited TearDown;
+end;
+
+procedure TTestVariables.TestEmpty;
+begin
+  AssertNotNull('Have variables',Variables);
+  AssertEquals('Variable count',0,Variables.Count);
+  AssertTrue('Variable class',Variables.ItemClass.InheritsFrom(TFPReportVariable));
+end;
+
+procedure TTestVariables.TestAdd;
+
+Var
+  V : TFPReportVariable;
+
+begin
+  V:=Variables.addVariable('aName');
+  AssertNotNull('Have result',V);
+  AssertEquals('Correct name','aName',V.Name);
+  AssertEquals('Correct type',rtString,V.DataType);
+  AssertEquals('Correct value','',V.AsString);
+  AssertEquals('Added to collection',1,Variables.Count);
+  AssertSame('In array',V,Variables[0]);
+  ExpectException('Cannot add twice',EReportError);
+  V:=Variables.addVariable('aName');
+end;
+
+procedure TTestVariables.AddThree;
+
+Var
+  I: integer;
+
+begin
+  For I:=0 to 2 do
+    FV[I]:=Variables.Addvariable('aName'+IntToStr(i+1));
+end;
+
+procedure TTestVariables.TestIndexOf;
+begin
+  AddThree;
+  AssertEquals('First',0,Variables.IndexOfVariable('aName1'));
+  AssertEquals('Second',1,Variables.IndexOfVariable('aName2'));
+  AssertEquals('Third',2,Variables.IndexOfVariable('aName3'));
+  AssertEquals('NonExisting',-1,Variables.IndexOfVariable('aName4'));
+end;
+
+procedure TTestVariables.TestFind;
+begin
+  AddThree;
+  AssertSame('First',FV[0],Variables.FindVariable('aName1'));
+  AssertSame('Second',FV[1],Variables.FindVariable('aName2'));
+  AssertSame('Third',FV[2],Variables.FindVariable('aName3'));
+  AssertNull('NonExisting',Variables.FindVariable('aName4'));
+end;
+
+{ TTestVariableBase }
+
+class procedure TTestVariableBase.AssertEquals(const Msg: String; AExpected,
+  AActual: TResultType);
+begin
+  AssertEquals(Msg,GetEnumName(TypeInfo(TResultType),Ord(AExpected)),GetEnumName(TypeInfo(TResultType),Ord(AActual)))
+end;
+
+{ TTestVariable }
+
+procedure TTestVariable.SetUp;
+begin
+  inherited SetUp;
+  FVar:=TFPReportVariable.Create(Nil);
+end;
+
+procedure TTestVariable.TearDown;
+begin
+  FreeandNil(FVar);
+  inherited TearDown;
+end;
+
+procedure TTestVariable.TestEmpty;
+begin
+  AssertNotNull('Have variable', Variable);
+  AssertEquals('Boolean type',rtBoolean,Variable.DataType);
+  AssertFalse('Boolean default value',Variable.AsBoolean);
+end;
+
+procedure TTestVariable.TestName;
+begin
+  Variable.Name:='me'; // OK
+  Variable.Name:='me.me'; // OK
+  ExpectException('Name must be identifier',EReportError);
+  Variable.Name:='me me'; // not OK
+end;
+
+procedure TTestVariable.TestBoolean;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  Variable.DataType:=rtBoolean;
+  AssertEquals('Boolean type remains',rtBoolean,Variable.DataType);
+  AssertFalse('Boolean default value',Variable.AsBoolean);
+  AssertEquals('Boolean as string','False',Variable.Value);
+  Variable.DataType:=rtFloat;
+  Variable.AsBoolean:=true;
+  AssertEquals('Boolean type remains',rtBoolean,Variable.DataType);
+  AssertEquals('Boolean as string','True',Variable.Value);
+  AssertTrue('Boolean value',Variable.AsBoolean);
+  R:=Variable.AsExpressionResult;
+  AssertEquals('Correct result',rtBoolean,r.resulttype);
+  AssertEquals('Correct value',True,r.resBoolean);
+  ExpectException('Cannot fetch as other type',EConvertError);
+  Variable.AsString;
+end;
+
+procedure TTestVariable.TestInteger;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  Variable.DataType:=rtInteger;
+  AssertEquals('Integer type remains',rtInteger,Variable.DataType);
+  AssertEquals('Integer default value',0,Variable.AsInteger);
+  AssertEquals('Integer as string','0',Variable.Value);
+  Variable.DataType:=rtFloat;
+  Variable.AsInteger:=123;
+  AssertEquals('Integer type remains',rtInteger,Variable.DataType);
+  AssertEquals('Integer as string','123',Variable.Value);
+  AssertEquals('Integer value',123,Variable.AsInteger);
+  R:=Variable.AsExpressionResult;
+  AssertEquals('Correct result',rtInteger,r.resulttype);
+  AssertEquals('Correct value',123,r.resInteger);
+  ExpectException('Cannot fetch as other type',EConvertError);
+  Variable.AsString;
+end;
+
+procedure TTestVariable.TestDateTime;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  Variable.DataType:=rtDateTime;
+  AssertEquals('DateTime type remains',rtDateTime,Variable.DataType);
+  AssertEquals('DateTime default value',0.0,Variable.AsDateTime);
+  AssertEquals('DateTime as string','00000000T000000',Variable.Value);
+  Variable.DataType:=rtDateTime;
+  Variable.AsDateTime:=Date;
+  AssertEquals('DateTime type remains',rtDateTime,Variable.DataType);
+  AssertEquals('DateTime as string',FormatDateTime('yyyymmdd"T"000000',Date),Variable.Value);
+  AssertEquals('DateTime value',Date,Variable.AsDateTime);
+  R:=Variable.AsExpressionResult;
+  AssertEquals('Correct result',rtDateTime,r.resulttype);
+  AssertEquals('Correct value',Date,r.resDateTime);
+  ExpectException('Cannot fetch as other type',EConvertError);
+  Variable.AsString;
+end;
+
+procedure TTestVariable.TestFloat;
+
+Var
+  R : TFPExpressionResult;
+
+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));
+  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 value',1.23,Variable.AsFloat);
+  R:=Variable.AsExpressionResult;
+  AssertEquals('Correct result',rtFloat,r.resulttype);
+  AssertEquals('Correct value',1.23,r.resFloat);
+  ExpectException('Cannot fetch as other type',EConvertError);
+  Variable.AsString;
+end;
+
+procedure TTestVariable.TestString;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  Variable.DataType:=rtString;
+  AssertEquals('String type remains',rtString,Variable.DataType);
+  AssertEquals('String default value','',Variable.AsString);
+  AssertEquals('String as string','',Variable.Value);
+  Variable.DataType:=rtBoolean;
+  Variable.AsString:='abc';
+  AssertEquals('String type remains',rtString,Variable.DataType);
+  AssertEquals('String as string','abc',Variable.Value);
+  AssertEquals('String value','abc',Variable.AsString);
+  R:=Variable.AsExpressionResult;
+  AssertEquals('Correct result',rtString,r.resulttype);
+  AssertEquals('Correct value','abc',r.resString);
+  ExpectException('Cannot fetch as other type',EConvertError);
+  Variable.AsFloat;
+end;
+
+procedure TTestVariable.TestExpressionResult;
+
+Var
+  R : TFPExpressionResult;
+
+begin
+  R.ResultType:=rtFloat;
+  R.ResFloat:=1.23;
+  Variable.AsExpressionResult:=R;
+  AssertEquals('Correct type ',rtFloat,Variable.DataType);
+  AssertEquals('Correct value',1.23,Variable.AsFloat);
+  R.ResultType:=rtBoolean;
+  R.ResBoolean:=True;
+  Variable.AsExpressionResult:=R;
+  AssertEquals('Correct type ',rtBoolean,Variable.DataType);
+  AssertEquals('Correct value',True,Variable.AsBoolean);
+  R.ResultType:=rtString;
+  R.ResString:='me';
+  Variable.AsExpressionResult:=R;
+  AssertEquals('Correct type ',rtString,Variable.DataType);
+  AssertEquals('Correct value','me',Variable.AsString);
+  R.ResultType:=rtDateTime;
+  R.ResDateTime:=Date;
+  Variable.AsExpressionResult:=R;
+  AssertEquals('Correct type ',rtDateTime,Variable.DataType);
+  AssertEquals('Correct value',Date,Variable.AsDateTime);
+  R.ResultType:=rtinteger;
+  R.ResInteger:=1234;
+  Variable.AsExpressionResult:=R;
+  AssertEquals('Correct type ',rtinteger,Variable.DataType);
+  AssertEquals('Correct value',1234,Variable.AsInteger);
+end;
+
+{ TTestCaseWithData }
+
+procedure TTestCaseWithData.InitializeData(const ACount: Integer);
+var
+  i: integer;
+begin
+  // data is coming from the stringlist this time
+  FSL := TStringList.Create;
+  if ACount < 1 then
+    Exit;
+  for i := 1 to ACount do
+    FSL.Add('Item ' + IntToStr(i));
+end;
+
+procedure TTestCaseWithData.SetReportData(const ADataCount: Byte);
+begin
+  if ADataCount < 1 then
+    Exit;
+  InitializeData(ADataCount);
+  FData := TFPReportUserData.Create(nil);
+  FData.OnGetValue := @DoGetDataValue;
+  FData.OnGetEOF := @DoGetDataEOF;
+end;
+
+procedure TTestCaseWithData.DoGetDataValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  if AValueName = 'element' then
+    AValue := FSL[FData.RecNo - 1];
+end;
+
+procedure TTestCaseWithData.DoGetDataEOF(Sender: TObject; var IsEOF: boolean);
+begin
+  if FData.RecNo > FSL.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TTestCaseWithData.SetUp;
+begin
+  inherited SetUp;
+end;
+
+procedure TTestCaseWithData.TearDown;
+begin
+  FreeAndNil(FData);
+  FreeAndNil(FSL);
+  inherited TearDown;
+end;
+
+{ TTestCaseWithDataAndReport }
+
+procedure TTestCaseWithDataAndReport.SetUp;
+begin
+  inherited SetUp;
+  FReport := TMyCustomReport.Create(nil);
+end;
+
+procedure TTestCaseWithDataAndReport.TearDown;
+begin
+  inherited TearDown;
+  FreeAndNil(FReport);
+end;
+
+{ TBaseReportComponentTest }
+
+procedure TBaseReportComponentTest.ExpectState(const aExpected: TFPReportState);
+begin
+  AssertEquals('ReportComponent.ReportState: ', AExpected, FC.ReportState);
+end;
+
+procedure TBaseReportComponentTest.AssertEquals(Msg: string; const aExpected, AActual: TFPReportState);
+begin
+  AssertEquals(Msg, GetEnumName(TypeInfo(TFPReportState), Ord(AExpected)),
+    GetEnumName(TypeInfo(TFPReportState), Ord(AActual)));
+end;
+
+procedure TBaseReportComponentTest.SetUp;
+begin
+  FC := TMyFPReportComponent.Create(nil);
+end;
+
+procedure TBaseReportComponentTest.TearDown;
+begin
+  FreeAndNil(FC);
+end;
+
+{ TTestReportComponent }
+
+procedure TTestReportComponent.TestCreate;
+begin
+  ExpectState(rsDesign);
+end;
+
+procedure TTestReportComponent.TestLayoutState;
+begin
+  FC.StartLayout;
+  ExpectState(rsLayout);
+  FC.EndLayout;
+  ExpectState(rsDesign);
+end;
+
+procedure TTestReportComponent.TestRenderState;
+begin
+  FC.StartRender;
+  ExpectState(rsRender);
+  FC.EndRender;
+  ExpectState(rsDesign);
+end;
+
+{ TMyFPReportComponent }
+
+procedure TMyFPReportComponent.StartLayout;
+begin
+  inherited StartLayout;
+end;
+
+procedure TMyFPReportComponent.EndLayout;
+begin
+  inherited EndLayout;
+end;
+
+procedure TMyFPReportComponent.StartRender;
+begin
+  inherited StartRender;
+end;
+
+procedure TMyFPReportComponent.EndRender;
+begin
+  inherited EndRender;
+end;
+
+{ TMyFPReportElement }
+
+procedure TMyFPReportElement.CallChange;
+begin
+  Changed;
+end;
+
+procedure TMyFPReportElement.ResetChanged;
+begin
+  FChangedCalled := 0;
+end;
+
+procedure TMyFPReportElement.DoChanged;
+begin
+  inherited DoChanged;
+  Inc(FChangedCalled);
+end;
+
+{ TBaseReportElementTest }
+
+procedure TBaseReportElementTest.SetUp;
+begin
+  inherited SetUp;
+  FC := TMyFPReportElement.Create(nil);
+end;
+
+procedure TBaseReportElementTest.TearDown;
+begin
+  FreeAndNil(FC);
+  inherited TearDown;
+end;
+
+{ TReportElementTest }
+
+procedure TReportElementTest.TestCreate;
+begin
+  AssertEquals('Create does not invoke changed', 0, FC.ChangedCalled);
+  AssertNotNull('Create creates frame', FC.Frame);
+  AssertEquals('Create creates frame of correct class', TFPReportFrame, FC.Frame.Classtype);
+  AssertNotNull('Create creates layout', FC.Layout);
+  AssertEquals('Create creates layout of correct class ', TFPReportLayout, FC.Layout.Classtype);
+  AssertEquals('Created element is visible', True, FC.Visible);
+  AssertNull('No parent at create', FC.Parent);
+end;
+
+procedure TReportElementTest.TestDoChange;
+begin
+  FC.CallChange;
+  AssertEquals('Change calls dochange', 1, FC.ChangedCalled);
+end;
+
+procedure TReportElementTest.TestChangeCount;
+begin
+  FC.BeginUpdate;
+  try
+    FC.CallChange;
+    AssertEquals('First Change does notcall dochange', 0, FC.ChangedCalled);
+    FC.CallChange;
+    AssertEquals('Second Change does not call dochange', 0, FC.ChangedCalled);
+  finally
+    FC.EndUpdate;
+  end;
+  AssertEquals('EndUpdate calls dochange once', 1, FC.ChangedCalled);
+end;
+
+procedure TReportElementTest.TestChangeCountNested;
+begin
+  FC.BeginUpdate;
+  try
+    FC.CallChange;
+    AssertEquals('First Change does notcall dochange', 0, FC.ChangedCalled);
+    FC.BeginUpdate;
+    try
+      FC.CallChange;
+      AssertEquals('Second Change does not call dochange', 0, FC.ChangedCalled);
+    finally
+      FC.EndUpdate;
+      AssertEquals('First endupdate does not call dochange', 0, FC.ChangedCalled);
+    end;
+  finally
+    FC.EndUpdate;
+  end;
+  AssertEquals('Second EndUpdate calls dochange once', 1, FC.ChangedCalled);
+end;
+
+procedure TReportElementTest.TestChangeCountNested2;
+begin
+  FC.BeginUpdate;
+  try
+    FC.CallChange;
+    AssertEquals('First Change does notcall dochange', 0, FC.ChangedCalled);
+    FC.BeginUpdate;
+    try
+      FC.CallChange;
+      AssertEquals('Second Change does not call dochange', 0, FC.ChangedCalled);
+      FC.CallChange;
+      AssertEquals('Third Change does not call dochange', 0, FC.ChangedCalled);
+    finally
+      FC.EndUpdate;
+      AssertEquals('First endupdate does not call dochange', 0, FC.ChangedCalled);
+    end;
+  finally
+    FC.EndUpdate;
+  end;
+  AssertEquals('Second EndUpdate calls dochange once', 1, FC.ChangedCalled);
+end;
+
+procedure TReportElementTest.TestVisibleChanges;
+begin
+  FC.ResetChanged;
+  FC.Visible := False;
+  AssertEquals('Setting visible calls change', 1, FC.ChangedCalled);
+end;
+
+procedure TReportElementTest.TestLayoutChanges;
+var
+  L: TFPreportLayout;
+begin
+  L := TFPreportLayout.Create(nil);
+  try
+    FC.Layout := L;
+    AssertEquals('Setting layout calls change', 1, FC.ChangedCalled);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestFrameChanges;
+var
+  F: TFPreportFrame;
+begin
+  F := TFPreportFrame.Create(nil);
+  try
+    FC.Frame := F;
+    AssertEquals('Setting frame calls change', 1, FC.ChangedCalled);
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestAssign;
+var
+  E: TFPReportElement;
+begin
+  E := TMyFPReportElement.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Frame.Width := 2;
+    E.Assign(FC);
+    AssertEquals('Assigned frame equal', True, FC.Frame.Equals(E.Frame));
+    AssertEquals('Assigned layout equal', True, FC.Layout.Equals(FC.Layout));
+  finally
+    E.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestEquals1;
+begin
+  AssertEquals('Self always returns equal', True, FC.Equals(FC));
+end;
+
+procedure TReportElementTest.TestEquals2;
+var
+  E: TFPReportElement;
+begin
+  E := TMyFPReportElement.Create(nil);
+  try
+    E.Assign(FC);
+    AssertEquals('Assigned element returns equal', True, FC.Equals(E));
+    AssertEquals('Assigned element returns equal', True, E.Equals(FC));
+  finally
+    E.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestEquals3;
+var
+  E: TFPReportElement;
+begin
+  E := TFPReportElement.Create(nil);
+  try
+    E.Assign(FC);
+    AssertEquals('Different class makes unequal', True, FC.Equals(E));
+    AssertEquals('Different class makes unequal', True, E.Equals(FC));
+  finally
+    E.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestEquals4;
+var
+  E: TFPReportElement;
+begin
+  E := TMyFPReportElement.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    E.Assign(FC);
+    E.Layout.Top := 2;
+    AssertEquals('Changed layout makes unequal', False, FC.Equals(E));
+    AssertEquals('Changed layout makes unequal', False, E.Equals(FC));
+  finally
+    E.Free;
+  end;
+end;
+
+procedure TReportElementTest.TestEquals5;
+var
+  E: TFPReportElement;
+begin
+  E := TMyFPReportElement.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    E.Assign(FC);
+    E.Frame.Lines := [flLeft];
+    AssertEquals('Changed frame makes unequal', False, FC.Equals(E));
+    AssertEquals('Changed frame makes unequal', False, E.Equals(FC));
+  finally
+    E.Free;
+  end;
+end;
+
+{ TTestReportFrame }
+
+procedure TTestReportFrame.TestCreate;
+begin
+  AssertEquals('Failed on 1', 1, FC.Frame.Width);
+  AssertEquals('Failed on 2',
+    GetEnumName(TYpeInfo(TFPPenStyle), Ord(psSolid)),
+    GetEnumName(TYpeInfo(TFPPenStyle), Ord(FC.Frame.Pen)));
+  if not (FC.Frame.Lines = []) then
+    Fail('Failed on 3');
+  AssertEquals('Failed on 4',
+    GetEnumName(TypeInfo(TFPReportFrameShape), Ord(fsNone)),
+    GetEnumName(TypeInfo(TFPReportFrameShape), Ord(FC.Frame.Shape)));
+end;
+
+procedure TTestReportFrame.TestWidthChange;
+begin
+  FC.Frame.Width := 2;
+  AssertEquals('Setting Width calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportFrame.TestColorChange;
+begin
+  FC.Frame.Color := 3;
+  AssertEquals('Setting Solor calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportFrame.TestPenStyleChange;
+begin
+  FC.Frame.Pen := psDot;
+  AssertEquals('Setting pen calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportFrame.TestShapeChange;
+begin
+  FC.Frame.Shape := fsRoundedRect;
+  AssertEquals('Setting pen calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportFrame.TestLinesChange;
+begin
+  FC.Frame.Lines := [flBottom];
+  AssertEquals('Setting pen calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportFrame.TestAssign;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    F.Width := 3;
+    F.Lines := [flBottom, flTop];
+    F.Color := 4;
+    F.Pen := psDot;
+    F.Shape := fsRoundedRect;
+    FC.Frame.Assign(F);
+    AssertSame('ReportElement not copied', FC, FC.Frame.ReportElement);
+    AssertEquals('Assert calls changed', 1, FC.ChangedCalled);
+    AssertEquals('Frame width equals 3', F.Width, FC.Frame.Width);
+    AssertEquals('Frame penstyle equals psDot',
+      GetEnumName(TYpeInfo(TFPPenStyle), Ord(F.Pen)),
+      GetEnumName(TYpeInfo(TFPPenStyle), Ord(FC.Frame.Pen)));
+    if not (FC.Frame.Lines = F.Lines) then
+      Fail('Frame lines not copied correctly');
+    AssertEquals('Frame shape correctly copied',
+      GetEnumName(TypeInfo(TFPReportFrameShape), Ord(F.Shape)),
+      GetEnumName(TypeInfo(TFPReportFrameShape), Ord(FC.Frame.Shape)));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals1;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    F.Width := 2;
+    AssertEquals('Width changed makes unequal', False, FC.Frame.Equals(F));
+    AssertEquals('Width changed makes unequal', False, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals2;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    F.Color := 2;
+    AssertEquals('Color changed makes unequal', False, FC.Frame.Equals(F));
+    AssertEquals('Color changed makes unequal', False, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals3;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    F.Pen := psDash;
+    AssertEquals('Pen changed makes unequal', False, FC.Frame.Equals(F));
+    AssertEquals('Pen changed makes unequal', False, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals4;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    F.Shape := fsShadow;
+    AssertEquals('Shape changed makes unequal', False, FC.Frame.Equals(F));
+    AssertEquals('Shape changed makes unequal', False, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals5;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    F.Lines := [flLeft, flRight];
+    AssertEquals('Lines changed makes unequal', False, FC.Frame.Equals(F));
+    AssertEquals('Lines changed makes unequal', False, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TTestReportFrame.TestEquals6;
+begin
+  AssertEquals('Same frame always equals', True, FC.Frame.Equals(FC.Frame));
+end;
+
+procedure TTestReportFrame.TestEquals7;
+var
+  F: TFPReportFrame;
+begin
+  F := TFPReportFrame.Create(nil);
+  try
+    FC.Frame.Width := 3;
+    FC.Frame.Lines := [flBottom, flTop];
+    FC.Frame.Color := 4;
+    FC.Frame.Pen := psDot;
+    FC.Frame.Shape := fsRoundedRect;
+    F.Assign(FC.Frame);
+    AssertEquals('Equals after assign', True, FC.Frame.Equals(F));
+    AssertEquals('Equals after assign', True, F.Equals(FC.Frame));
+  finally
+    F.Free;
+  end;
+end;
+
+
+{ TTestReportLayout }
+
+procedure TTestReportLayout.TestCreate;
+begin
+  AssertEquals('Top is zero', 0, FC.Layout.top);
+  AssertEquals('Left is zero', 0, FC.Layout.Left);
+  AssertEquals('Width is zero', 0, FC.Layout.Width);
+  AssertEquals('Height is zero', 0, FC.Layout.Width);
+end;
+
+procedure TTestReportLayout.TestTopChange;
+begin
+  FC.Layout.Top := 2;
+  AssertEquals('Setting top calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportLayout.TestLeftChange;
+begin
+  FC.Layout.Left := 2;
+  AssertEquals('Setting left calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportLayout.TestWidthChange;
+begin
+  FC.Layout.Width := 2;
+  AssertEquals('Setting width calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportLayout.TestHeightChange;
+begin
+  FC.Layout.Height := 2;
+  AssertEquals('Setting Height calls onChange', 1, FC.ChangedCalled);
+end;
+
+procedure TTestReportLayout.TestAssign;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    AssertEquals('Top correct', FC.Layout.Top, L.Top);
+    AssertEquals('Left correct', FC.Layout.Left, L.Left);
+    AssertEquals('Width correct', FC.Layout.Width, L.Width);
+    AssertEquals('Height correct', FC.Layout.Height, L.Height);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals1;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    FC.Layout.Top := 2;
+    AssertEquals('Top changed makes unequal', False, FC.Layout.Equals(L));
+    AssertEquals('Top changed makes unequal', False, L.Equals(FC.Layout));
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals2;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    FC.Layout.Left := 2;
+    AssertEquals('Left changed makes unequal', False, FC.Layout.Equals(L));
+    AssertEquals('Left changed makes unequal', False, L.Equals(FC.Layout));
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals3;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    FC.Layout.Width := 2;
+    AssertEquals('Width changed makes unequal', False, FC.Layout.Equals(L));
+    AssertEquals('Width changed makes unequal', False, L.Equals(FC.Layout));
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals4;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    FC.Layout.Height := 2;
+    AssertEquals('Height changed makes unequal', False, FC.Layout.Equals(L));
+    AssertEquals('Height changed makes unequal', False, L.Equals(FC.Layout));
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals5;
+var
+  L: TFPReportLayout;
+begin
+  L := TFPReportlayout.Create(nil);
+  try
+    FC.Layout.Top := 1;
+    FC.Layout.Left := 1;
+    FC.Layout.Width := 10;
+    FC.Layout.Height := 10;
+    L.Assign(FC.Layout);
+    AssertEquals('Assign results in equal', True, FC.Layout.Equals(L));
+    AssertEquals('Assign results in equal', True, L.Equals(FC.Layout));
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TTestReportLayout.TestEquals6;
+begin
+  AssertEquals('Assign results in equal', True, FC.Layout.Equals(FC.Layout));
+end;
+
+{ TTestReportChildren }
+
+procedure TTestReportChildren.SetUp;
+begin
+  FC := TMyFPReportElementWithChildren.Create(nil);
+  FChild := TFPReportElement.Create(nil);
+end;
+
+procedure TTestReportChildren.TearDown;
+begin
+  FreeAndNil(FC);
+  FreeAndNil(FChild);
+end;
+
+procedure TTestReportChildren.WrongParent;
+begin
+  FC.Parent := FChild;
+end;
+
+procedure TTestReportChildren.TestCreate;
+begin
+  AssertEquals('No children at create', 0, FC.ChildCount);
+end;
+
+procedure TTestReportChildren.TestSetParent1;
+begin
+  AssertException('Cannot set TReportElement as parent', EReportError, @WrongParent);
+end;
+
+procedure TTestReportChildren.TestSetParent2;
+begin
+  FChild.parent := FC;
+  AssertSame('Parent was saved', FC, FChild.parent);
+  AssertEquals('Changed was called', 1, FC.ChangedCalled);
+  AssertEquals('Parent childcount is 1', 1, FC.ChildCount);
+  AssertSame('Parent first child is OK', FChild, FC.Child[0]);
+end;
+
+procedure TTestReportChildren.TestSetParent3;
+var
+  E: TFPReportElementWithChildren;
+begin
+  FChild.parent := FC;
+  AssertSame('Parent was saved', FC, FChild.parent);
+  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;
+end;
+
+procedure TTestReportChildren.TestSetParent4;
+begin
+  FChild.parent := FC;
+  AssertSame('Parent was saved', FC, FChild.parent);
+  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);
+end;
+
+procedure TTestReportChildren.TestSetParent6;
+begin
+  FChild.parent := FC;
+  AssertSame('Parent was saved', FC, FChild.parent);
+  AssertEquals('Parent childcount is 1', 1, FC.ChildCount);
+  AssertSame('Parent first child is OK', FChild, FC.Child[0]);
+  FChild.parent := nil;
+  AssertNull('Child parent was removed when parent is freed', FChild.Parent);
+end;
+
+procedure TTestReportChildren.TestSetParent5;
+begin
+  FChild.parent := FC;
+  AssertSame('Parent was saved', FC, FChild.parent);
+  AssertEquals('Parent childcount is 1', 1, FC.ChildCount);
+  AssertSame('Parent first child is OK', FChild, FC.Child[0]);
+  FreeAndNil(FChild);
+  AssertEquals('Child removed when freed', 0, FC.ChildCount);
+end;
+
+{ TMyFPReportElementWithChildren }
+
+procedure TMyFPReportElementWithChildren.CallChange;
+begin
+  Changed;
+end;
+
+procedure TMyFPReportElementWithChildren.ResetChanged;
+begin
+  FChangedCalled := 0;
+end;
+
+procedure TMyFPReportElementWithChildren.DoChanged;
+begin
+  inherited DoChanged;
+  Inc(FChangedCalled);
+end;
+
+{ TTestFPPageSize }
+
+procedure TTestFPPageSize.TestCreate;
+var
+  F: TFPReportPaperSize;
+begin
+  F := TFPReportPaperSize.Create(1.23, 3.45);
+  try
+    AssertEquals('Width stored correctly', 1.23, F.Width, 0.001);
+    AssertEquals('Height stored correctly', 3.45, F.Height, 0.001);
+  finally
+    F.Free;
+  end;
+end;
+
+{ TTestFPPaperManager }
+
+procedure TTestFPPapers.Setup;
+begin
+  FM := TFPReportPaperManager.Create(nil);
+  AssertNotNull(FM);
+end;
+
+procedure TTestFPPapers.TearDown;
+begin
+  FreeAndNil(FM);
+end;
+
+procedure TTestFPPapers.RegisterPapers(ACount: integer; Local: boolean = True);
+var
+  F: TFPReportPaperManager;
+begin
+  if local then
+    F := FM
+  else
+    F := PaperManager;
+  if (ACount >= 1) then
+    F.RegisterPaper('P3', 1.0, 2.0);
+  if (ACount >= 2) then
+    F.RegisterPaper('P2', 4.0, 8.0);
+  if (ACount >= 3) then
+    F.RegisterPaper('P1', 16.0, 32.0);
+end;
+
+procedure TTestFPPaperManager.TestAccess;
+begin
+  case FAccess of
+    0: FM.PaperNames[-1];
+    1: FM.PaperNames[FM.PaperCount];
+    2: FM.PaperHeight[-1];
+    3: FM.PaperHeight[FM.PaperCount];
+    4: FM.PaperWidth[-1];
+    5: FM.PaperWidth[FM.PaperCount];
+    6: FM.WidthByName['NoPaper'];
+    7: FM.HeightByName['NoPaper'];
+  end;
+end;
+
+procedure TTestFPPaperManager.Setup;
+begin
+  inherited Setup;
+  FAccess := -1;
+end;
+
+procedure TTestFPPaperManager.TestCreate;
+begin
+  AssertEquals('No registered papers', 0, FM.PaperCount);
+end;
+
+procedure TTestFPPaperManager.TestRegister1;
+begin
+  RegisterPapers(1);
+  AssertEquals('1 registered paper', 1, FM.PaperCount);
+  AssertEquals('Correct name', 'P3', FM.PaperNames[0]);
+end;
+
+procedure TTestFPPaperManager.TestRegister2;
+begin
+  RegisterPapers(2);
+  AssertEquals('2 registered papers', 2, FM.PaperCount);
+  AssertEquals('Correct name paper 1', 'P2', FM.PaperNames[0]);
+  AssertEquals('Correct name paper 2', 'P3', FM.PaperNames[1]);
+end;
+
+procedure TTestFPPaperManager.TestRegister3;
+begin
+  RegisterPapers(3);
+  AssertEquals('3 registered papers', 3, FM.PaperCount);
+  AssertEquals('Correct name paper 1', 'P1', FM.PaperNames[0]);
+  AssertEquals('Correct name paper 2', 'P2', FM.PaperNames[1]);
+  AssertEquals('Correct name paper 3', 'P3', FM.PaperNames[2]);
+end;
+
+procedure TTestFPPaperManager.TestRegisterDuplicate;
+begin
+  RegisterPapers(2);
+  AssertEquals('2 registered papers', 2, FM.PaperCount);
+  AssertEquals('Correct name paper 1', 'P2', FM.PaperNames[0]);
+  AssertEquals('Correct name paper 2', 'P3', FM.PaperNames[1]);
+  try
+    FM.RegisterPaper('P3', 10.0, 20.0);
+    Fail('We expected an exception to be raised.');
+  except
+    on E: Exception do
+      begin
+        AssertEquals('Exception class', 'EReportError', E.ClassName);
+        AssertEquals('Exception message', 'Paper name P3 already exists', E.Message);
+      end;
+  end;
+end;
+
+procedure TTestFPPaperManager.TestClear;
+begin
+  RegisterPapers(2);
+  AssertEquals('2 registered papers', 2, FM.PaperCount);
+  AssertEquals('Correct name paper 1', 'P2', FM.PaperNames[0]);
+  AssertEquals('Correct name paper 2', 'P3', FM.PaperNames[1]);
+  FM.Clear;
+  AssertEquals('0 registered papers', 0, FM.PaperCount);
+end;
+
+procedure TTestFPPaperManager.TestFind1;
+begin
+  AssertEquals('No paper registered', -1, FM.IndexOfPaper('P1'));
+end;
+
+procedure TTestFPPaperManager.TestFind2;
+begin
+  RegisterPapers(3);
+  AssertEquals('No paper registered', -1, FM.IndexOfPaper('PA1'));
+end;
+
+procedure TTestFPPaperManager.TestFind3;
+begin
+  RegisterPapers(3);
+  AssertEquals('3 registered papers', 3, FM.PaperCount);
+  AssertEquals('Find P1 OK', 0, FM.IndexOfPaper('P1'));
+  AssertEquals('Find P2 OK', 1, FM.IndexOfPaper('P2'));
+  AssertEquals('Find P3 OK', 2, FM.IndexOfPaper('P3'));
+end;
+
+procedure TTestFPPaperManager.IllegalAccess1;
+begin
+  RegisterPapers(3);
+  FAccess := 0;
+  AssertException('Papername[-1]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess2;
+begin
+  RegisterPapers(3);
+  FAccess := 1;
+  AssertException('Papername[3]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess3;
+begin
+  RegisterPapers(3);
+  FAccess := 2;
+  AssertException('PaperHeight[-1]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess4;
+begin
+  RegisterPapers(3);
+  FAccess := 3;
+  AssertException('PaperHeight[3]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess5;
+begin
+  RegisterPapers(3);
+  FAccess := 4;
+  AssertException('PaperWidth[-1]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess6;
+begin
+  RegisterPapers(3);
+  FAccess := 5;
+  AssertException('PaperWidth[3]', EStringListError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess7;
+begin
+  RegisterPapers(3);
+  FAccess := 6;
+  AssertException('WidthByName[NoPaper]', EReportError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.IllegalAccess8;
+begin
+  RegisterPapers(3);
+  FAccess := 7;
+  AssertException('WidthByName[NoPaper]', EReportError, @TestAccess);
+end;
+
+procedure TTestFPPaperManager.TestWidth;
+begin
+  RegisterPapers(3);
+  AssertEquals('Paper width 0', 16.0, FM.PaperWidth[0]);
+  AssertEquals('Paper width 1', 4.0, FM.PaperWidth[1]);
+  AssertEquals('Paper width 2', 1.0, FM.PaperWidth[2]);
+  AssertEquals('Width[P1]', 16.0, FM.WidthByName['P1']);
+  AssertEquals('Width[P2]', 4.0, FM.WidthByName['P2']);
+  AssertEquals('Width[P3]', 1, FM.WidthByName['P3']);
+end;
+
+procedure TTestFPPaperManager.TestHeight;
+begin
+  RegisterPapers(3);
+  AssertEquals('Paper height 0', 32.0, FM.PaperHeight[0]);
+  AssertEquals('Paper height 1', 8.0, FM.PaperHeight[1]);
+  AssertEquals('Paper height 2', 2.0, FM.PaperHeight[2]);
+  AssertEquals('Height[P1]', 32.0, FM.HeightByName['P1']);
+  AssertEquals('Height[P2]', 8.0, FM.HeightByName['P2']);
+  AssertEquals('Height[P3]', 2, FM.HeightByName['P3']);
+end;
+
+{ TMyFPReportPageSize }
+
+procedure TMyFPReportPageSize.ResetChanged;
+begin
+  FChangedCalled := 0;
+end;
+
+procedure TMyFPReportPageSize.Changed;
+begin
+  Inc(FChangedCalled);
+  inherited Changed;
+end;
+
+{ TTestFPReportPageSize }
+
+procedure TTestFPReportPageSize.Setup;
+begin
+  inherited Setup;
+  FP := TMyFPReportPageSize.Create(nil);
+end;
+
+procedure TTestFPReportPageSize.TearDown;
+begin
+  FreeAndNil(FP);
+  inherited TearDown;
+end;
+
+procedure TTestFPReportPageSize.TestCreate;
+begin
+  AssertNull('No page', FP.Page);
+  AssertEquals('Zero width at create', 0.0, FP.Width);
+  AssertEquals('Zero height at create', 0.0, FP.Height);
+  AssertEquals('No paper name', '', FP.PaperName);
+end;
+
+procedure TTestFPReportPageSize.TestCreateWithPage;
+var
+  P: TFPReportPage;
+  F: TFPReportPageSize;
+begin
+  P := TFPReportPage.Create(nil);
+  try
+    F := TFPReportPageSize.Create(P);
+    try
+      AssertSame('Pagesize created with page has page as page', P, F.Page);
+    finally
+      F.Free;
+    end;
+  finally
+    P.Free
+  end;
+end;
+
+procedure TTestFPReportPageSize.TestCreateByPage;
+var
+  P: TFPReportPage;
+begin
+  P := TFPReportPage.Create(nil);
+  try
+    AssertSame('Pagesize created with page has page as page', P, P.PageSize.Page);
+  finally
+    P.Free
+  end;
+end;
+
+procedure TTestFPReportPageSize.TestChanged1;
+begin
+  FP.Width := 1.23;
+  AssertEquals('Setting width triggers change', 1, FP.ChangedCalled);
+end;
+
+procedure TTestFPReportPageSize.TestChanged2;
+begin
+  FP.Height := 1.23;
+  AssertEquals('Setting height triggers change', 1, FP.ChangedCalled);
+end;
+
+procedure TTestFPReportPageSize.TestChanged3;
+begin
+  FP.PaperName := 'ABC';
+  AssertEquals('Setting paper name without associated paper does not trigger change', 0, FP.ChangedCalled);
+end;
+
+procedure TTestFPReportPageSize.TestPaperName1;
+var
+  F: TFPReportPaperManager;
+begin
+  F := PaperManager;
+  if F.PaperCount = 0 then
+    Registerpapers(3, False);
+  FP.PaperName := F.PaperNames[0];
+  AssertEquals('Setting papername sets width', F.PaperWidth[0], FP.Width);
+  AssertEquals('Setting papername sets height', F.PaperHeight[0], FP.Height);
+  AssertEquals('Setting papername calls changed once', 1, FP.ChangedCalled);
+end;
+
+procedure TTestFPReportPageSize.TestPaperName2;
+var
+  F: TFPReportPaperManager;
+begin
+  F := PaperManager;
+  if F.PaperCount = 0 then
+    Registerpapers(3, False);
+  FP.PaperName := F.PaperNames[0];
+  AssertEquals('Setting papername sets width', F.PaperWidth[0], FP.Width);
+  AssertEquals('Setting papername sets height', F.PaperHeight[0], FP.Height);
+  FP.ResetChanged;
+  FP.PaperName := 'aloha'; // Non existing
+  AssertEquals('Setting non-existing papername leaves width', F.PaperWidth[0], FP.Width);
+  AssertEquals('Setting non-existing papername leaves height', F.PaperHeight[0], FP.Height);
+  AssertEquals('Setting non-existing papername does not call changed', 0, FP.ChangedCalled);
+end;
+
+procedure TTestFPReportPageSize.TestAssign;
+var
+  F: TMyFPreportPageSize;
+begin
+  F := TMyFPreportPageSize.Create(nil);
+  try
+    FP.PaperName := 'me';
+    FP.Width := 1.23;
+    FP.Height := 4.56;
+    F.Assign(FP);
+    AssertEquals('Assign assigns Width', FP.Width, F.Width);
+    AssertEquals('Assign assigns height', FP.Height, F.Height);
+    AssertEquals('Assign assigns papername', FP.PaperName, F.PaperName);
+    AssertEquals('Assign calls Changed once', 1, F.ChangedCalled);
+  finally
+    F.Free;
+  end;
+end;
+
+{ TMyFPReportPage }
+
+procedure TMyFPReportPage.SetupPage;
+begin
+  Orientation := poPortrait;
+  { paper size }
+  PageSize.PaperName := 'A4';
+  { page margins }
+  Margins.Left := 30;
+  Margins.Top := 20;
+  Margins.Right := 30;
+  Margins.Bottom := 20;
+end;
+
+procedure TMyFPReportPage.PrepareObjects;
+begin
+  Inc(FPrepareObjectsCalled);
+  inherited PrepareObjects;
+end;
+
+constructor TMyFPReportPage.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  Font.Name := 'LiberationSerif';
+end;
+
+procedure TMyFPReportPage.ResetChanged;
+begin
+  FChangedCalled := 0;
+end;
+
+procedure TMyFPReportPage.DoChanged;
+begin
+  Inc(FChangedCalled);
+  inherited DoChanged;
+end;
+
+{ TMyReportTitleBand }
+
+procedure TMyReportTitleBand.PrepareObjects;
+begin
+  Inc(FPrepareObjectsCalled);
+  inherited PrepareObjects;
+end;
+
+constructor TMyReportTitleBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  Layout.Height := 20;
+end;
+
+{ TMyDataBand }
+
+procedure TMyDataBand.PrepareObjects;
+begin
+  Inc(FPrepareObjectsCalled);
+  inherited PrepareObjects;
+end;
+
+constructor TMyDataBand.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  Layout.Height := 10;
+end;
+
+{ TTestReportPage }
+
+procedure TTestReportPage.Setup;
+begin
+  inherited Setup;
+  FP := TMyFPReportPage.Create(nil);
+end;
+
+procedure TTestReportPage.TearDown;
+begin
+  FreeAndNil(FP);
+  inherited TearDown;
+end;
+
+procedure TTestReportPage.TestCreate1;
+begin
+  AssertNull('Created page without parent has no report', FP.Report);
+  AssertNotNull('Created page has margins', FP.Margins);
+  AssertNotNull('Created page has pagesize', FP.PageSize);
+  AssertEquals('Orientation is portrait', Ord(poPortrait), Ord(FP.Orientation));
+  AssertEquals('No bands', 0, FP.BandCount);
+end;
+
+procedure TTestReportPage.TestCreate2;
+var
+  R: TFPReport;
+  P: TMyFPReportPage;
+begin
+  R := TFPReport.Create(nil);
+  try
+    P := TMyFPReportPage.Create(nil);
+    try
+      P.Report := R;
+      AssertSame('Page owner is report when created', R, P.Report);
+      AssertEquals('Report has one page', 1, R.PageCount);
+      AssertSame('Page added to pages', P, R.Pages[0]);
+    finally
+      R.Free;
+    end;
+    AssertNull('Report has notified page', P.Report);
+  finally
+    P.Free;
+  end;
+end;
+
+procedure TTestReportPage.TestCreate3;
+var
+  R: TFPReport;
+  P: TMyFPReportPage;
+begin
+  R := TFPReport.Create(nil);
+  P := TMyFPReportPage.Create(R); // Lets try passing Report as the AOwner in constructor
+  try
+    AssertSame('Page report is set', R, P.Report);
+    AssertSame('Page added to pages', P, R.Pages[0]);
+    P.Report := nil;
+    AssertEquals('No more pages', 0, R.PageCount);
+  finally
+    // This will free P as well, because R was set as the owner
+    R.Free;
+  end;
+end;
+
+procedure TTestReportPage.TestPageSize1;
+begin
+  FP.ResetChanged;
+  FP.BeginUpdate;
+  try
+    FP.PageSize.Width := 10;
+    FP.PageSize.Height := 20;
+  finally
+    FP.EndUpdate;
+  end;
+  AssertEquals('Changed called', 1, FP.ChangedCalled);
+  AssertEquals('Top is zero', 0, FP.Layout.Top);
+  AssertEquals('Left is zero', 0, FP.Layout.Left);
+  AssertEquals('Width is pagewidth', FP.PageSize.Width, FP.Layout.Width);
+  AssertEquals('Height is pageheight', FP.PageSize.Height, FP.Layout.Height);
+end;
+
+procedure TTestReportPage.TestPageSize2;
+begin
+  FP.ResetChanged;
+  FP.BeginUpdate;
+  try
+    FP.PageSize.Width := 10;
+    FP.PageSize.Height := 20;
+    FP.Margins.Left := 1;
+    FP.Margins.Right := 2;
+    FP.Margins.Top := 3;
+    FP.Margins.Bottom := 4;
+  finally
+    FP.EndUpdate;
+  end;
+  AssertEquals('Changed called', 1, FP.ChangedCalled);
+  AssertEquals('Top is top margin', 3, FP.Layout.Top);
+  AssertEquals('Left is left margin', 1, FP.Layout.Left);
+  AssertEquals('Width is pagewidth-rightmargin-leftmargin', 7, FP.Layout.Width);
+  AssertEquals('Height is pageheight-topmargin-bottommargin', 13, FP.Layout.Height);
+end;
+
+procedure TTestReportPage.TestPageSize3;
+begin
+  FP.ResetChanged;
+  FP.BeginUpdate;
+  try
+    FP.Orientation := poLandScape;
+    FP.PageSize.Width := 10;
+    FP.PageSize.Height := 20;
+    FP.Margins.Left := 1;
+    FP.Margins.Right := 2;
+    FP.Margins.Top := 3;
+    FP.Margins.Bottom := 4;
+  finally
+    FP.EndUpdate;
+  end;
+  AssertEquals('Changed called', 1, FP.ChangedCalled);
+  AssertEquals('Top is top margin', 3, FP.Layout.Top);
+  AssertEquals('Left is left margin', 1, FP.Layout.Left);
+  AssertEquals('Width is pageheight-rightmargin-leftmargin', 17, FP.Layout.Width);
+  AssertEquals('Height is pagewidth-topmargin-bottommargin', 3, FP.Layout.Height);
+end;
+
+procedure TTestReportPage.TestBand1;
+var
+  B: TFPReportCustomBand;
+begin
+  B := TFPReportCustomBand.Create(nil);
+  try
+    FP.ResetChanged;
+    B.Parent := FP;
+    AssertEquals('Changed called', 1, FP.ChangedCalled);
+    AssertSame('Parent stored correctly', FP, B.Page);
+    AssertEquals('Bandcount correct', 1, FP.BandCount);
+    AssertSame('Bands[0] correct', B, FP.Bands[0]);
+  finally
+    B.Free;
+  end;
+  AssertEquals('Bandcount correct', 0, FP.BandCount);
+end;
+
+procedure TTestReportPage.TestBand2;
+var
+  B: TFPReportCustomBand;
+  P: TMyFPReportPage;
+begin
+  B := TFPReportCustomBand.Create(nil);
+  try
+    P := TMyFPReportPage.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;
+    end;
+    AssertNull('Band notified that page is gone', B.Parent);
+    AssertNull('Band notified that page is gone', B.Page);
+  finally
+    B.Free;
+  end;
+end;
+
+procedure TTestReportPage.TestData;
+var
+  FData: TFPReportData;
+begin
+  FData := TFPReportData.Create(nil);
+  try
+    FP.Data := FData;
+  finally
+    FData.Free;
+  end;
+  AssertNull('Data is cleared', FP.Data);
+end;
+
+procedure TTestReportPage.TestAssign;
+var
+  E: TFPReportPage;
+begin
+  E := TFPReportPage.Create(nil);
+  try
+    FP.Layout.Top := 1;
+    FP.Frame.Width := 2;
+    E.Assign(FP);
+    AssertEquals('Failed on 1', True, FP.Frame.Equals(E.Frame));
+    AssertEquals('Failed on 2', True, FP.Layout.Equals(E.Layout));
+    AssertEquals('Failed on 3', Ord(E.Orientation), Ord(FP.Orientation));
+    AssertEquals('Failed on 4', True, FP.Margins.Equals(E.Margins));
+  finally
+    E.Free;
+  end;
+end;
+
+procedure TTestReportPage.TestFindBand;
+var
+  t: TFPReportTitleBand;
+  h: TFPReportPageHeaderBand;
+  f: TFPReportPageFooterBand;
+  d: TFPReportDataBand;
+begin
+  t := TFPReportTitleBand.Create(FP);
+  h := TFPReportPageHeaderBand.Create(FP);
+  f := TFPReportPageFooterBand.Create(FP);
+  d := TFPReportDataBand.Create(FP);
+  AssertTrue('failed on 1', h = FP.FindBand(TFPReportPageHeaderBand));
+  AssertTrue('failed on 2', t <> FP.FindBand(TFPReportPageHeaderBand));
+  AssertTrue('failed on 3', t = FP.FindBand(TFPReportTitleBand));
+  AssertTrue('failed on 4', f = FP.FindBand(TFPReportPageFooterBand));
+  AssertTrue('failed on 5', d = FP.FindBand(TFPReportDataBand));
+  AssertTrue('failed on 6', FP.FindBand(TFPReportChildBand) = nil);
+end;
+
+{ TMyFPReportData }
+
+procedure TMyFPReportData.ResetCounts;
+begin
+  FCC := 0;
+  FDFC := 0;
+  FEC := 0;
+  FFC := 0;
+  FNC := 0;
+  FOC := 0;
+end;
+
+procedure TMyFPReportData.DoInitDataFields;
+begin
+  inherited DoInitDataFields;
+  Inc(FDFC);
+end;
+
+procedure TMyFPReportData.DoOpen;
+begin
+  inherited DoOpen;
+  Inc(FOC);
+end;
+
+procedure TMyFPReportData.DoFirst;
+begin
+  inherited DoFirst;
+  Inc(FFC);
+end;
+
+procedure TMyFPReportData.DoNext;
+begin
+  inherited DoNext;
+  Inc(FNC);
+end;
+
+procedure TMyFPReportData.DoClose;
+begin
+  inherited DoClose;
+  Inc(FCC);
+end;
+
+function TMyFPReportData.DoEOF: boolean;
+begin
+  FOE := inherited DoEOF;
+  Inc(FEC);
+  Result := FReportEOF;
+end;
+
+{ TTestReportData }
+
+procedure TTestReportData.DoOpen(Sender: TObject);
+begin
+  FHandler := True;
+  AssertEquals('OnOpen called before DoOpen', 0, FD.OpenCount);
+  AssertEquals('OnOpen called before InitFieldDefs', 0, FD.InitDataFieldsCount);
+end;
+
+procedure TTestReportData.DoNext(Sender: TObject);
+begin
+  FHandler := True;
+  AssertEquals('DoNext not yet called in handler', 0, FD.NextCount);
+  AssertEquals('Recno is already 2 in donext', 2, FD.RecNo);
+end;
+
+procedure TTestReportData.Setup;
+begin
+  inherited Setup;
+  FD := TMyFPReportData.Create(nil);
+  FHandler := False;
+end;
+
+procedure TTestReportData.TearDown;
+begin
+  FreeAndNil(FD);
+  inherited TearDown;
+end;
+
+procedure TTestReportData.CreateFields;
+begin
+  FD.DataFields.AddField('string', rfkString).DisplayWidth := 10;
+  FD.DataFields.AddField('boolean', rfkBoolean).DisplayWidth := 20;
+  FD.DataFields.AddField('integer', rfkInteger).DisplayWidth := 30;
+  FD.DataFields.AddField('float', rfkFloat).DisplayWidth := 40;
+  FD.DataFields.AddField('datetime', rfkDateTime).DisplayWidth := 50;
+  FD.Datafields.AddField('stream', rfkStream).DisplayWidth := 60;
+end;
+
+procedure TTestReportData.DoFieldByName;
+var
+  F: TFPReportDataField;
+begin
+  F := FD.Datafields.FieldByName('ohlala');
+end;
+
+procedure TTestReportData.TestCreate;
+begin
+  AssertEquals('Closed recno is 0', 0, FD.RecNo);
+  AssertNotNull('DataFields created', FD.DataFields);
+  AssertEquals('Closed fieldcount is 0', 0, FD.DataFields.Count);
+  AssertSame('Datafields reportdata is self', FD, FD.DataFields.ReportData);
+end;
+
+procedure TTestReportData.TestOpen1;
+begin
+  FD.OnOpen := @DoOpen;
+  FD.Open;
+  AssertEquals('OnOpen Handler called', True, FHandler);
+  AssertEquals('DoOpen called once', 1, FD.OpenCount);
+  AssertEquals('InitFieldDefs called once', 1, FD.InitDataFieldsCount);
+  AssertEquals('Recno is 1', 1, FD.RecNo);
+end;
+
+procedure TTestReportData.TestNext;
+begin
+  FD.OnNext := @DoNext;
+  FD.Open;
+  FHandler := False;
+  FD.Next;
+  AssertEquals('OnNext Handler called', True, FHandler);
+  AssertEquals('DoNext Called once', 1, FD.NextCount);
+  AssertEquals('Recno is 2 after next', 2, FD.RecNo);
+end;
+
+procedure TTestReportData.TestInitFieldDefs;
+begin
+  FD.InitFieldDefs;
+  AssertEquals('InitFieldDefs called once', 1, FD.InitDataFieldsCount);
+end;
+
+procedure TTestReportData.TestInitFieldDefs_OnlyAllowedOnce;
+begin
+  FD.Open;
+  AssertEquals('Failed on 1', 1, FD.InitDataFieldsCount);
+  try
+    FD.InitFieldDefs;
+    Fail('Failed on 2. - we should not have reached here.');
+  except
+    on E: Exception do
+    begin
+      AssertEquals('Failed on 3', E.ClassName, 'EReportError');
+    end;
+  end;
+  AssertEquals('Failed on 4', 1, FD.InitDataFieldsCount);
+end;
+
+procedure TTestReportData.TestEOF1;
+begin
+  FD.ReportEOF := True;
+  AssertEquals('ReportEOF works correctly', True, FD.EOF);
+  AssertEquals('Inherited EOF returns false', False, FD.OldEOF);
+end;
+
+procedure TTestReportData.TestAddDatafield;
+var
+  F: TFPReportDataField;
+begin
+  F := FD.DataFields.AddField('test', rfkBoolean);
+  AssertEquals('Boolean field Added', Ord(rfkBoolean), Ord(F.FieldKind));
+  AssertEquals('test field name Added', 'test', F.fieldname);
+  AssertEquals('0 width field Added', 0, F.DisplayWidth);
+end;
+
+procedure TTestReportData.TestDatafieldAdd;
+var
+  I: TCollectionItem;
+  F: TFPReportDataField;
+begin
+  I := FD.Datafields.Add;
+  AssertEquals('add creates TFPReportDataField', TFPReportDataField, I.ClassType);
+  F := I as TFPReportDataField;
+  AssertEquals('Default field of string kind', Ord(rfkString), Ord(F.FieldKind));
+  AssertEquals('Default field name empty', '', F.FieldName);
+  AssertEquals('Default field with 0', 0, F.DisplayWidth);
+end;
+
+procedure TTestReportData.AssertField(Prefix: string; F: TFPReportDataField; AFieldName: string;
+  AFieldKind: TFPReportFieldKind; ADisplayWidth: integer = 0);
+var
+  S1, S2: string;
+begin
+  AssertEquals(Prefix + ' has correct field name', AfieldName, F.FieldName);
+  S1 := GetEnumName(TypeInfo(TFPReportFieldKind), Ord(AFieldKind));
+  S2 := GetEnumName(TypeInfo(TFPReportFieldKind), Ord(F.FieldKind));
+  AssertEquals(Prefix + ' has corrrect fieldkind', S1, S2);
+  AssertEquals(Prefix + ' has correct fieldwidth', ADisplayWidth, F.DisplayWidth);
+end;
+
+procedure TTestReportData.TestCreateFields;
+begin
+  CreateFields;
+  AssertEquals('Correct field count', 6, FD.FieldCount);
+  AssertField('Field 0', FD.DataFields[0], 'string', rfkString, 10);
+  AssertField('Field 1', FD.DataFields[1], 'boolean', rfkBoolean, 20);
+  AssertField('Field 2', FD.DataFields[2], 'integer', rfkInteger, 30);
+  AssertField('Field 3', FD.DataFields[3], 'float', rfkFloat, 40);
+  AssertField('Field 4', FD.DataFields[4], 'datetime', rfkDateTime, 50);
+  AssertField('Field 5', FD.DataFields[5], 'stream', rfkStream, 60);
+end;
+
+procedure TTestReportData.TestDatafieldIndexOf1;
+begin
+  CreateFields;
+  AssertEquals('Finds field at pos 0', 0, FD.DataFields.IndexOfField('string'));
+  AssertEquals('Finds field at pos 3', 3, FD.DataFields.IndexOfField('float'));
+  AssertEquals('Finds field at pos 5', 5, FD.DataFields.IndexOfField('stream'));
+  AssertEquals('Finds field (casing) at pos 3', 3, FD.DataFields.IndexOfField('Float'));
+end;
+
+procedure TTestReportData.TestDatafieldIndexOf2;
+begin
+  AssertEquals('No fields returns -1', -1, FD.DataFields.IndexOfField('string'));
+  CreateFields;
+  AssertEquals('Non-existing field returns -1', -1, FD.DataFields.IndexOfField('stringlslsl'));
+end;
+
+procedure TTestReportData.TestFindField1;
+begin
+  AssertNull('No fields returns Nil', FD.DataFields.FindField('string'));
+  CreateFields;
+  AssertNull('Non-existing fields returns Nil', FD.DataFields.FindField('stringsss'));
+end;
+
+procedure TTestReportData.TestFindField2;
+begin
+  CreateFields;
+  AssertSame('FindField returns correct field', FD.DataFields[0], FD.DataFields.FindField('string'));
+  AssertSame('FindField returns correct field', FD.DataFields[3], FD.DataFields.FindField('float'));
+  AssertSame('FindField returns correct field (case insensitive)', FD.DataFields[3], FD.DataFields.FindField('floaT'));
+end;
+
+procedure TTestReportData.TestFindByName1;
+begin
+  CreateFields;
+  AssertSame('FieldByName returns correct field', FD.DataFields[0], FD.DataFields.FieldByName('string'));
+end;
+
+procedure TTestReportData.TestFindByName2;
+begin
+  CreateFields;
+  AssertException('FieldByName (non-existent) raises exception', EReportError, @DoFieldByName);
+end;
+
+procedure TTestReportData.TestFieldAssign;
+var
+  F1, F2: TFPReportDataField;
+begin
+  F1 := TFPReportDataField.Create(nil);
+  try
+    f2 := TFPReportDataField.Create(nil);
+    try
+      F1.FieldKind := rfkBoolean;
+      F1.FieldName := 'bool';
+      F1.DisplayWidth := 12;
+      F2.Assign(F1);
+      AssertField('Assigned ', F2, 'bool', rfkBoolean, 12);
+    finally
+      F2.Free;
+    end;
+  finally
+    F1.Free;
+  end;
+end;
+
+procedure TTestReportData.TestGetValue;
+var
+  v: variant;
+begin
+  CreateFields;
+  v := FD.Datafields[0].GetValue;
+  AssertTrue('Failed on 1', V = Null);
+end;
+
+procedure TTestReportData.TestEasyAccessProperties;
+var
+  I: integer;
+begin
+  CreateFields;
+  for I := 0 to FD.FieldCount - 1 do
+    AssertEquals('FieldNames array OK', FD.DataFields[0].FieldName, FD.FieldNames[0]);
+  for I := 0 to FD.FieldCount - 1 do
+    AssertEquals('FieldWidth array OK', FD.DataFields[0].DisplayWidth, FD.FieldWidths[FD.FieldNames[0]]);
+  for I := 0 to FD.FieldCount - 1 do
+    AssertEquals('FieldTypes array OK', Ord(FD.DataFields[0].FieldKind), Ord(FD.FieldTypes[FD.FieldNames[0]]));
+end;
+
+{ TTestUserReportData }
+
+procedure TTestUserReportData.Setup;
+begin
+  FD := TFPReportUserData.Create(nil);
+  FD.DataFields.AddField('string', rfkString);
+  FD.OnGetValue := @DoValue;
+  inherited;
+end;
+
+procedure TTestUserReportData.TearDown;
+begin
+  FreeAndNil(FD);
+  inherited TearDown;
+end;
+
+procedure TTestUserReportData.DoValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  AssertSame('DoValue Sender is reportdata', FD, Sender);
+  AssertEquals('DoValue gets correct value name', FExpectName, AValueName);
+  AValue := FReturnValue;
+end;
+
+procedure TTestUserReportData.TestGetValue;
+begin
+  FExpectName := 'string';
+  FReturnValue := 10;
+  AssertEquals('Return value correct', 10, FD.DataFields[0].GetValue);
+  AssertEquals('FieldValues array value correct', 10, FD.FieldValues['string']);
+end;
+
+{ TTestUserReportData2 }
+
+procedure TTestUserReportData2.DoGetValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  if AValueName = 'element' then
+    AValue := FSL[FData.RecNo - 1];
+end;
+
+procedure TTestUserReportData2.DoGetEOF(Sender: TObject; var IsEOF: boolean);
+begin
+  if FData.RecNo > FSL.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TTestUserReportData2.Setup;
+begin
+  inherited Setup;
+  FData := TFPReportUserData.Create(nil);
+  FData.OnGetValue := @DoGetValue;
+  // data is coming from the stringlist this time
+  FSL := TStringList.Create;
+  FSL.Add('Item 1');
+  FSL.Add('Item 2');
+  FSL.Add('Item 3');
+  FSL.Add('Item 4');
+end;
+
+procedure TTestUserReportData2.TearDown;
+begin
+  FData.Free;
+  FSL.Free;
+  inherited TearDown;
+end;
+
+procedure TTestUserReportData2.TestGetValue;
+begin
+  FData.First;
+  AssertEquals('Failed on 1', 'Item 1', FData.FieldValues['element']);
+  FData.Next;
+  AssertEquals('Failed on 2', 'Item 2', FData.FieldValues['element']);
+  FData.Next;
+  AssertEquals('Failed on 3', 'Item 3', FData.FieldValues['element']);
+  FData.Next;
+  AssertEquals('Failed on 4', 'Item 4', FData.FieldValues['element']);
+  FData.Next;
+end;
+
+procedure TTestUserReportData2.TestOnGetEOF1;
+var
+  i: integer;
+begin
+  FData.First;
+  for i := 1 to FSL.Count do
+    FData.Next;
+  // Should be False, because we haven't assigned OnGetEOF event handler
+  AssertTrue('Failed on 1', FData.EOF = False);
+end;
+
+procedure TTestUserReportData2.TestOnGetEOF2;
+var
+  i: integer;
+begin
+  FData.OnGetEOF := @DoGetEOF;
+  FData.First;
+  for i := 1 to FSL.Count do
+    FData.Next;
+  AssertTrue('Failed on 1', FData.EOF = True);
+end;
+
+{ TTestDataBand }
+
+procedure TTestDataBand.Setup;
+begin
+  FDataBand := TFPReportDataBand.Create(nil);
+  inherited Setup;
+end;
+
+procedure TTestDataBand.TearDown;
+begin
+  FreeAndNil(FDataBand);
+  inherited TearDown;
+end;
+
+procedure TTestDataBand.TestData;
+var
+  D: TFPReportData;
+begin
+  D := TFPReportData.Create(nil);
+  try
+    FDataBand.Data := D;
+    AssertSame('Assigned data OK', D, FDataBand.Data)
+  finally
+    D.Free;
+  end;
+  AssertNull('Free notification of Data', FDataBand.Data);
+end;
+
+procedure TTestDataBand.TestDataPropertyAutoSet;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  D: TFPReportData;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  // DataBand should have been assigned p.Data automatically
+  AssertSame('Failed on 1', TFPReportData(Data), DataBand.Data);
+  D := TFPReportData.Create(nil);
+  try
+    DataBand.Data := D;
+    AssertTrue('Failed on 2', p.Data <> DataBand.Data);
+  finally
+    D.Free;
+  end;
+end;
+
+{ TTestCustomReport }
+
+procedure TTestCustomReport.HandleOnBeginReport;
+begin
+  Inc(FBeginReportCount);
+end;
+
+procedure TTestCustomReport.HandleOnEndReport;
+begin
+  Inc(FEndReportCount);
+end;
+
+procedure TTestCustomReport.InitializeData(const ACount: integer);
+var
+  i: integer;
+begin
+  // data is coming from the stringlist this time
+  FSL := TStringList.Create;
+  if ACount < 1 then
+    Exit;
+  for i := 1 to ACount do
+    FSL.Add('Item ' + IntToStr(i));
+end;
+
+procedure TTestCustomReport.SetReportData(const ADataCount: Byte);
+begin
+  if ADataCount < 1 then
+    Exit;
+  InitializeData(ADataCount);
+  FData := TFPReportUserData.Create(nil);
+  FData.OnGetValue := @DoGetDataValue;
+  FData.OnGetEOF := @DoGetDataEOF;
+  FData.OnGetNames := @DoGetDataFieldNames;
+end;
+
+procedure TTestCustomReport.DoGetDataValue(Sender: TObject; const AValueName: string; var AValue: variant);
+begin
+  if AValueName = 'element' then
+    AValue := FSL[FData.RecNo - 1];
+end;
+
+procedure TTestCustomReport.DoGetDataEOF(Sender: TObject; var IsEOF: boolean);
+begin
+  if FData.RecNo > FSL.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TTestCustomReport.Setup;
+begin
+  inherited Setup;
+  PaperManager.Clear;
+  PaperManager.RegisterStandardSizes;
+  Report := TMyCustomReport.Create(nil);
+  FBeginReportCount := 0;
+  FEndReportCount := 0;
+
+  gTTFontCache.Clear;
+  gTTFontCache.SearchPath.Text := 'fonts';
+  gTTFontCache.BuildFontCache;
+end;
+
+procedure TTestCustomReport.TearDown;
+begin
+  FreeAndNil(FRpt);
+  FreeAndNil(FData);
+  FreeAndNil(FSL);
+  inherited TearDown;
+end;
+
+procedure TTestCustomReport.DoGetDataFieldNames(Sender: TObject; List: TStrings);
+begin
+  List.Add('element');
+end;
+
+procedure TTestCustomReport.TestBeginReportEvent;
+begin
+  Report.OnBeginReport := @HandleOnBeginReport;
+  AssertEquals('Failed on 1', 0, FBeginReportCount);
+  Report.RunReport;
+  AssertEquals('Failed on 2', 1, FBeginReportCount);
+  AssertEquals('Failed on 3', 0, FEndReportCount);
+end;
+
+procedure TTestCustomReport.TestEndReportEvent;
+begin
+  Report.OnEndReport := @HandleOnEndReport;
+  AssertEquals('Failed on 1', 0, FEndReportCount);
+  Report.RunReport;
+  AssertEquals('Failed on 2', 1, FEndReportCount);
+  AssertEquals('Failed on 3', 0, FBeginReportCount);
+end;
+
+procedure TTestCustomReport.TestPagePrepareObjects;
+var
+  p: TMyFPReportPage;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page2';
+  p.Data := Data;
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page3';
+  p.Data := Data;
+
+  AssertEquals('Failed on 1', TMyFPReportPage(Report.Pages[0]).FPrepareObjectsCalled, 0);
+  AssertEquals('Failed on 2', TMyFPReportPage(Report.Pages[1]).FPrepareObjectsCalled, 0);
+  AssertEquals('Failed on 3', TMyFPReportPage(Report.Pages[2]).FPrepareObjectsCalled, 0);
+
+  Report.RunReport;
+  AssertEquals('Failed on 4', TMyFPReportPage(Report.Pages[0]).FPrepareObjectsCalled, 1);
+  AssertEquals('Failed on 5', TMyFPReportPage(Report.Pages[1]).FPrepareObjectsCalled, 1);
+  AssertEquals('Failed on 6', TMyFPReportPage(Report.Pages[2]).FPrepareObjectsCalled, 1);
+end;
+
+procedure TTestCustomReport.TestBandPrepareObjects;
+var
+  p: TMyFPReportPage;
+  TitleBand: TMyReportTitleBand;
+  DataBand: TMyDataBand;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  TitleBand := TMyReportTitleBand.Create(p);
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Data := FData;
+
+  AssertEquals('Failed on 1', 0, p.FPrepareObjectsCalled);
+  AssertEquals('Failed on 2', 0, TitleBand.FPrepareObjectsCalled);
+  AssertEquals('Failed on 3', 0, DataBand.FPrepareObjectsCalled);
+
+  Report.RunReport;
+  AssertEquals('Failed on 4', 1, p.FPrepareObjectsCalled);
+  AssertEquals('Failed on 5', 1, TitleBand.FPrepareObjectsCalled);
+  AssertEquals('Failed on 6', 2, DataBand.FPrepareObjectsCalled);
+end;
+
+procedure TTestCustomReport.TestRTObjects1;
+var
+  p: TMyFPReportPage;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page2';
+  p.Data := Data;
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page3';
+  p.Data := Data;
+
+  AssertEquals('Failed on 1', 0, Report.RTObjects.Count);
+
+  Report.RunReport;
+  AssertEquals('Failed on 2', 3, Report.RTObjects.Count);
+end;
+
+procedure TTestCustomReport.TestRTObjects2;
+var
+  p: TMyFPReportPage;
+  TitleBand: TMyReportTitleBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  TitleBand := TMyReportTitleBand.Create(p);
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Text := 'THE REPORT TITLE';
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+
+  {$IFDEF gdebug}
+//  writeln(Report.DebugPreparedPageAsJSON(0));
+  {$ENDIF}
+end;
+
+procedure TTestCustomReport.TestRTObjects3;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[element]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 2, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 2, rtPage.BandCount);
+
+  {$IFDEF gdebug}
+//  writeln(Report.DebugPreparedPageAsJSON(0));
+  {$ENDIF}
+end;
+
+procedure TTestCustomReport.TestRTObjects4_OneDataItem;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.SetupPage;
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[element]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+
+  {$IFDEF gdebug}
+//    writeln(Report.DebugPreparedPageAsJSON(0));
+  {$ENDIF}
+end;
+
+procedure TTestCustomReport.TestRTObjects5_TwoDataItems;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(2);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Top := 0;
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[element]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 2, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 2, rtPage.BandCount); { each data row has its own data band }
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  AssertEquals('Failed on 7', 1, rtPage.Bands[1].ChildCount);
+
+  {$IFDEF gdebug}
+//    writeln(Report.DebugPreparedPageAsJSON(0));
+  {$ENDIF}
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Page;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[PageNo]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', '1', Memo.Text);
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Page_with_text;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := 'Page [PageNo]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount);
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', 'Page 1', Memo.Text);
+end;
+
+procedure TTestCustomReport.TestInternalFunction_RecNo;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+  i: integer;
+begin
+  SetReportData(5);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[recno]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 5, rtPage.ChildCount);  // 5 rendered data bands because we have 5 data records
+  AssertEquals('Failed on 5', 5, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  for i := 0 to 4 do
+  begin
+    Memo := TFPReportMemo(rtPage.Bands[i].Child[0]);
+    AssertEquals('Failed on 7.'+IntToStr(i), IntToStr(i+1), Memo.Text); { recno is 1-based }
+  end;
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Today;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[today]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', FormatDateTime('yyyy-mm-dd', Today), Memo.Text);
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Today_with_text;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := 'Today is [today]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', 'Today is ' + FormatDateTime('yyyy-mm-dd', Today), Memo.Text);
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Author;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[author]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', '', Memo.Text); // we never set Report.Author
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Author_with_text;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  Report.Author := 'Graeme Geldenhuys';
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := 'The Author is [author].';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', 'The Author is Graeme Geldenhuys.', Memo.Text);
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Title;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := '[title]';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', '', Memo.Text); // we never set Report.Title
+end;
+
+procedure TTestCustomReport.TestInternalFunction_Title_with_text;
+var
+  p: TMyFPReportPage;
+  DataBand: TMyDataBand;
+  Memo: TFPReportMemo;
+  rtPage: TFPReportCustomPage;
+begin
+  SetReportData(1);
+
+  Report.Title := 'My Test Report';
+  p := TMyFPReportPage.Create(Report);
+  p.Name := 'Page1';
+  p.SetupPage;
+  p.Data := Data;
+
+  DataBand := TMyDataBand.Create(p);
+  DataBand.Layout.Height := 23;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Top := 5;
+  Memo.Layout.Left := 10;
+  Memo.Text := 'Report Title is "[title]".';
+
+  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
+  AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
+  rtPage := TFPReportCustomPage(Report.RTObjects[0]);
+  AssertEquals('Failed on 4', 1, rtPage.ChildCount); // 1 rendered data band because we have 1 data record
+  AssertEquals('Failed on 5', 1, rtPage.BandCount);
+  AssertEquals('Failed on 6', 1, rtPage.Bands[0].ChildCount);
+  Memo := TFPReportMemo(rtPage.Bands[0].Child[0]);
+  AssertEquals('Failed on 7', 'Report Title is "My Test Report".', Memo.Text);
+end;
+
+{ TTestReportMemo }
+
+
+procedure TTestReportMemo.CauseFontNotFoundException;
+begin
+  TMemoFriend(FMemo).RecalcLayout;
+end;
+
+procedure TTestReportMemo.SetUp;
+begin
+  inherited SetUp;
+  FMemo := TFPReportMemo.Create(nil);
+  FMemo.Layout.SetPosition(0, 0, 60, 5);
+end;
+
+procedure TTestReportMemo.TearDown;
+begin
+  FMemo.Free;
+  inherited TearDown;
+end;
+
+procedure TTestReportMemo.TestCreate;
+var
+  m: TFPReportMemo;
+begin
+  m := TFPReportMemo.Create(nil);
+  try
+    m.Text := 'abc 123';
+    AssertTrue('Failed on 1', m <> nil);
+  finally
+    m.Free;
+  end;
+end;
+
+procedure TTestReportMemo.TestPrepareTextBlocks;
+begin
+  gTTFontCache.Clear;
+  gTTFontCache.SearchPath.Text := 'fonts';
+  gTTFontCache.BuildFontCache;
+
+  FMemo.Layout.Width := 100;
+  FMemo.Text := 'abc 123';
+  FMemo.UseParentFont := False;
+  FMemo.Font.Name := 'Calibri';
+  FMemo.StretchMode := smActualHeight;
+  TMemoFriend(FMemo).CreateRTLayout;
+  TMemoFriend(FMemo).RecalcLayout;
+  AssertEquals('Failed on 2', 1, FMemo.TextLines.Count);
+end;
+
+procedure TTestReportMemo.TestPrepareTextBlocks_multiline_data;
+begin
+  gTTFontCache.Clear;
+  gTTFontCache.SearchPath.Text := 'fonts';
+  gTTFontCache.BuildFontCache;
+
+  FMemo.Layout.Width := 100;
+  FMemo.Text := 'abc'+LineEnding+'123';
+  FMemo.UseParentFont := False;
+  FMemo.Font.Name := 'Calibri';
+  FMemo.StretchMode := smActualHeight;
+  TMemoFriend(FMemo).CreateRTLayout;
+  TMemoFriend(FMemo).RecalcLayout;
+  AssertEquals('Failed on 2', 2, FMemo.TextLines.Count);
+end;
+
+procedure TTestReportMemo.TestPrepareTextBlocks_multiline_wraptext;
+begin
+  gTTFontCache.Clear;
+  gTTFontCache.SearchPath.Text := 'fonts';
+  gTTFontCache.BuildFontCache;
+
+  FMemo.Layout.Width := 6;
+  FMemo.Text := 'abc 123';
+  FMemo.UseParentFont := False;
+  FMemo.Font.Name := 'Calibri';
+  FMemo.StretchMode := smActualHeight;
+  TMemoFriend(FMemo).CreateRTLayout;
+  TMemoFriend(FMemo).RecalcLayout;
+  AssertEquals('Failed on 2', 2, FMemo.TextLines.Count);
+end;
+
+procedure TTestReportMemo.TestRGBToReportColor;
+var
+  c: TFPReportColor;
+begin
+  c := RGBToReportColor(255, 0, 0);
+  AssertEquals('failed on 1', IntToHex(clRed, 8), IntToHex(c, 8));
+  c := RGBToReportColor(0, 128, 0);
+  AssertEquals('failed on 2', IntToHex(clGreen, 8), IntToHex(c, 8));
+  c := RGBToReportColor(0, 0, 255);
+  AssertEquals('failed on 3', IntToHex(clBlue, 8), IntToHex(c, 8));
+end;
+
+procedure TTestReportMemo.TestHTMLColorToReportColor_length7;
+var
+  c: TFPReportColor;
+begin
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('#FF0000', clBlack);
+  AssertEquals('failed on 1', IntToHex(clRed, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('#008000', clBlack);
+  AssertEquals('failed on 2', IntToHex(clGreen, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('#0000FF', clBlack);
+  AssertEquals('failed on 3', IntToHex(clBlue, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('A0000FF', clBlack);
+  AssertEquals('failed on 4', IntToHex(clBlack, 8), IntToHex(c, 8));
+end;
+
+procedure TTestReportMemo.TestHTMLColorToReportColor_length6;
+var
+  c: TFPReportColor;
+begin
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('FF0000', clBlack);
+  AssertEquals('failed on 1', IntToHex(clRed, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('008000', clBlack);
+  AssertEquals('failed on 2', IntToHex(clGreen, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('0000FF', clBlack);
+  AssertEquals('failed on 3', IntToHex(clBlue, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('A0000FF', clBlack);
+  AssertEquals('failed on 4', IntToHex(clBlack, 8), IntToHex(c, 8));
+end;
+
+procedure TTestReportMemo.TestHTMLColorToReportColor_length3;
+var
+  c: TFPReportColor;
+begin
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('F00', clBlack);
+  AssertEquals('failed on 1', IntToHex(clRed, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('080', clBlack);
+  AssertEquals('failed on 2', IntToHex($008800, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('00F', clBlack);
+  AssertEquals('failed on 3', IntToHex(clBlue, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('A00F', clDkGray);
+  AssertEquals('failed on 4', IntToHex(clDkGray, 8), IntToHex(c, 8));
+
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('700', clBlack);
+  AssertEquals('failed on 5', IntToHex($770000, 8), IntToHex(c, 8));
+  c := TMemoFriend(FMemo).HtmlColorToFPReportColor('006', clBlack);
+  AssertEquals('failed on 6', IntToHex($000066, 8), IntToHex(c, 8));
+end;
+
+procedure TTestReportMemo.TestCreateTestBlock;
+var
+  tb: TFPTextBlock;
+begin
+  tb := TMemoFriend(FMemo).CreateTextBlock(false);
+  try
+    AssertTrue('failed on 1', tb is TFPTextBlock);
+    AssertFalse('failed on 2', tb is TFPHTTPTextBlock);
+  finally
+    tb.Free;
+  end;
+end;
+
+procedure TTestReportMemo.TestCreateTestBlock_IsURL;
+var
+  tb: TFPTextBlock;
+begin
+  tb := TMemoFriend(FMemo).CreateTextBlock(true);
+  try
+    AssertTrue('failed on 1', tb is TFPTextBlock);
+    AssertTrue('failed on 2', tb is TFPHTTPTextBlock);
+  finally
+    tb.Free;
+  end;
+end;
+
+procedure TTestReportMemo.TestSubStr;
+var
+  m: TMemoFriend;
+  lStartPos: integer;
+begin
+  m := TMemoFriend(FMemo);
+  AssertEquals('failed on 1', '', m.SubStr('','','', 1, lStartPos));
+  AssertEquals('failed on 1.1', -1, lStartPos);
+  AssertEquals('failed on 2', 'abc', m.SubStr('xxxabcyyy','xxx','yyy', 1, lStartPos));
+  AssertEquals('failed on 2.1', 4, lStartPos);
+  AssertEquals('failed on 3', 'abc', m.SubStr('xxx,abc;xxx',',',';', 1, lStartPos));
+  AssertEquals('failed on 3.1', 5, lStartPos);
+  AssertEquals('failed on 4', 'abc', m.SubStr('<d>abc</d>','<d>','</d>', 1, lStartPos));
+  AssertEquals('failed on 4.1', 4, lStartPos);
+  AssertEquals('failed on 5', 'abc1', m.SubStr('<d>abc1</d> <d>abc2</d>','<d>','</d>', 1, lStartPos));
+  AssertEquals('failed on 5.1', 4, lStartPos);
+  AssertEquals('failed on 6', 'abc2', m.SubStr('<d>abc1</d> <d>abc2</d>','<d>','</d>', 2, lStartPos));
+  AssertEquals('failed on 6.1', 16, lStartPos);
+  AssertEquals('failed on 7', '', m.SubStr('<d>abc1</d> <d>abc2</d>','<d>','</d>', 3, lStartPos));
+  AssertEquals('failed on 7.1', -1, lStartPos);
+  AssertEquals('failed on 8', 'abc1', m.SubStr('<d>abc1</d> <d>abc2</d>','<d>','</d>', 0, lStartPos));
+  AssertEquals('failed on 8.1', 4, lStartPos);
+  AssertEquals('failed on 9', 'abc1', m.SubStr('<d>abc1</d> <d>abc2</d>','<d>','</d>', -1, lStartPos));
+  AssertEquals('failed on 9.1', 4, lStartPos);
+end;
+
+procedure TTestReportMemo.TestTokenCount;
+var
+  m: TMemoFriend;
+  lStartPos: integer;
+begin
+  m := TMemoFriend(FMemo);
+  AssertEquals('failed on 1', '', m.SubStr('','','', 1, lStartPos));
+  AssertEquals('failed on 1.1', -1, lStartPos);
+  AssertEquals('failed on 2', 'abc', m.SubStr('xxxabcyyy','xxx','yyy', 1, lStartPos));
+  AssertEquals('failed on 2.1', 4, lStartPos);
+
+  AssertEquals('failed on 1', m.TokenCount('', ','), 0);
+  AssertEquals('failed on 2', m.TokenCount('adf adf', ','), 1);
+  AssertEquals('failed on 3', m.TokenCount('adf,', ','), 2);
+  AssertEquals('failed on 4', m.TokenCount('adf,adf', ','), 2);
+  AssertEquals('failed on 5', m.TokenCount('adf,adf,adf', ','), 3);
+  AssertEquals('failed on 6', m.TokenCount('adf,adf,adf,', ','), 4);
+  AssertEquals('failed on 6', m.TokenCount('0mm margin top and bottom.', ' '), 5);
+  AssertEquals('failed on 6', m.TokenCount('0mm margin top and bottom. ', ' '), 6);
+end;
+
+procedure TTestReportMemo.TestToken;
+var
+  m: TMemoFriend;
+  lStartPos: integer;
+begin
+  m := TMemoFriend(FMemo);
+  AssertEquals('failed on 1', m.Token('', ',', 1), '');
+  AssertEquals('failed on 2', m.Token('a,b,c', ',', 1), 'a');
+  AssertEquals('failed on 3', m.Token('a,b,c', ',', 2), 'b');
+  AssertEquals('failed on 4', m.Token('a,b,c', ',', 3), 'c');
+  AssertEquals('failed on 5', m.Token('a,b,c', ',', 4), '');
+  AssertEquals('failed on 6', m.Token('aa,bb,cc', ',', 1), 'aa');
+  AssertEquals('failed on 7', m.Token('aa,bb,cc', ',', 2), 'bb');
+  AssertEquals('failed on 8', m.Token('aa,bb,cc', ',', 3), 'cc');
+  AssertEquals('failed on 9', m.Token('aa,bb,cc', ',', 4), '');
+  AssertEquals('failed on 10', m.Token('aa,bb,cc,', ',', 4), '');
+  AssertEquals('failed on 11', m.Token('0mm margin top and bottom.', ' ', 5), 'bottom.');
+  AssertEquals('failed on 12', m.Token('0mm margin top and bottom. ', ' ', 5), 'bottom.');
+  AssertEquals('failed on 13', m.Token('0mm margin top and bottom. ', ' ', 6), '');
+end;
+
+{ TTestBandList }
+
+procedure TTestBandList.CreateBands;
+begin
+  b1 := TFPReportPageHeaderBand.Create(nil);
+  b2 := TFPReportTitleBand.Create(nil);
+  b3 := TFPReportDataBand.Create(nil);
+end;
+
+procedure TTestBandList.AddAllBandsToList;
+begin
+  FList.Add(b1);
+  FList.Add(b2);
+  FList.Add(b3);
+end;
+
+procedure TTestBandList.SetUp;
+begin
+  inherited SetUp;
+  FList := TBandList.Create;
+  CreateBands;
+end;
+
+procedure TTestBandList.TearDown;
+begin
+  FreeAndNil(FList);
+  FreeAndNil(b3);
+  FreeAndNil(b2);
+  FreeAndNil(b1);
+  inherited TearDown;
+end;
+
+procedure TTestBandList.TestAdd;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  AddAllBandsToList;
+  AssertEquals('Failed on 2', 3, FList.Count);
+end;
+
+procedure TTestBandList.TestItems;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  AddAllBandsToList;
+  AssertEquals('Failed on 2', 3, FList.Count);
+
+  AssertTrue('failed on 3', FList.Items[0] = b1);
+  AssertTrue('failed on 4', FList.Items[1] = b2);
+  AssertTrue('failed on 5', FList.Items[1] <> b1);
+  AssertTrue('failed on 6', FList.Items[2] = b3);
+end;
+
+procedure TTestBandList.TestClear;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  AddAllBandsToList;
+  AssertEquals('Failed on 2', 3, FList.Count);
+
+  FList.Clear;
+  AssertEquals('Failed on 3', 0, FList.Count);
+  AssertTrue('failed on 4', b1 <> nil); // List.Clear shouldn't free bands
+end;
+
+procedure TTestBandList.TestDelete;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  AddAllBandsToList;
+  AssertEquals('Failed on 2', 3, FList.Count);
+
+  FList.Delete(0);
+  AssertEquals('Failed on 3', 2, FList.Count);
+  AssertTrue('failed on 4', b1 <> nil); // List.Delete shouldn't free bands
+  AssertTrue('failed on 5', FList.Items[0] = b2);
+  AssertTrue('failed on 6', FList.Items[1] = b3);
+end;
+
+procedure TTestBandList.TestFind1;
+var
+  lBand: TFPReportCustomBand;
+  lResult: integer;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  AddAllBandsToList;
+  AssertEquals('Failed on 2', 3, FList.Count);
+
+  AssertTrue('failed on 3', FList.Find(TFPReportPageHeaderBand) <> nil);
+  AssertTrue('failed on 4', FList.Find(TFPReportPageHeaderBand) = b1);
+  AssertTrue('failed on 5', FList.Find(TFPReportTitleBand) = b2);
+  AssertTrue('failed on 6', FList.Find(TFPReportDataBand) = b3);
+
+  FList.Clear;
+  AssertTrue('failed on 7', FList.Find(TFPReportTitleBand) = nil);
+end;
+
+procedure TTestBandList.TestFind2;
+var
+  lBand: TFPReportCustomBand;
+  lResult: integer;
+begin
+  AssertEquals('Failed on 1', 0, FList.Count);
+  lResult := FList.Find(TFPReportPageHeaderBand, lBand);
+  AssertEquals('failed on 2', -1, lResult);
+  AssertTrue('failed on 3', lBand = nil);
+
+  AddAllBandsToList;
+  AssertEquals('Failed on 4', 3, FList.Count);
+
+  lResult := FList.Find(TFPReportPageHeaderBand, lBand);
+  AssertEquals('failed on 5', 0, lResult);
+  AssertTrue('failed on 6', lBand <> nil);
+  AssertTrue('failed on 7', lBand = b1);
+
+  lResult := FList.Find(TFPReportTitleBand, lBand);
+  AssertEquals('failed on 8', 1, lResult);
+  AssertTrue('failed on 9', lBand = b2);
+
+  lResult := FList.Find(TFPReportDataBand, lBand);
+  AssertEquals('failed on 10', 2, lResult);
+  AssertTrue('failed on 11', lBand = b3);
+
+  FList.Clear;
+  lResult := FList.Find(TFPReportTitleBand, lBand);
+  AssertTrue('failed on 12', lBand = nil);
+  AssertTrue('failed on 13', lResult = -1);
+end;
+
+initialization
+  RegisterTests(
+    [TTestReportComponent,
+    TReportElementTest,
+    TTestReportChildren,
+    TTestReportFrame,
+    TTestReportLayout,
+    TTestFPPageSize,
+    TTestFPPaperManager,
+    TTestFPReportPageSize,
+    TTestReportPage,
+    TTestReportData,
+    TTestUserReportData,
+    TTestUserReportData2,
+    TTestDataBand,
+    TTestCustomReport,
+    TTestReportMemo,
+    TTestBandList,
+    TTestVariable,
+    TTestVariables
+    ]);
+end.
+
+

+ 171 - 0
packages/fcl-report/test/tchtmlparser.pas

@@ -0,0 +1,171 @@
+unit tchtmlparser;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpcunit,
+  testregistry,
+  fpReportHTMLParser;
+
+type
+  TTestHTMLParser = class(TTestCase)
+  private
+    FParser: THTMLParser;
+    FTags: TStringList;
+    FText: TStringList;
+    procedure InitParser(const AText: string);
+    procedure CaptureTagsFound(NoCaseTag, ActualTag: string);
+    procedure CaptureTextFound(Text: string);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  public
+  published
+    procedure TestOneTagPair;
+    procedure TestNoTags;
+    procedure TestTagAndRemainingText;
+    procedure TestRegularTextAndTag;
+    procedure TestTagNoText;
+    procedure TestGetTagName;
+    procedure TestGetNameValPair;
+    procedure TestGetValFromNameVal;
+    procedure TestGetVal;
+  end;
+
+implementation
+
+{ TTestHTMLParser }
+
+procedure TTestHTMLParser.InitParser(const AText: string);
+begin
+  FParser := THTMLParser.Create(AText);
+  FParser.OnFoundTag := @CaptureTagsFound;
+  FParser.OnFoundText := @CaptureTextFound;
+  FParser.Exec;
+end;
+
+procedure TTestHTMLParser.CaptureTagsFound(NoCaseTag, ActualTag: string);
+begin
+  FTags.Add(NoCaseTag);
+end;
+
+procedure TTestHTMLParser.CaptureTextFound(Text: string);
+begin
+  FText.Add(Text);
+end;
+
+procedure TTestHTMLParser.SetUp;
+begin
+  inherited SetUp;
+  FParser := nil;
+  FTags := TStringList.Create;
+  FText := TStringList.Create;
+end;
+
+procedure TTestHTMLParser.TearDown;
+begin
+  FParser.Free;
+  FTags.Free;
+  FText.Free;
+  inherited TearDown;
+end;
+
+procedure TTestHTMLParser.TestOneTagPair;
+begin
+  InitParser('<i>italics</i>');
+  AssertEquals('Failed on 1', FTags[0], '<I>');
+  AssertEquals('Failed on 2', FTags[1], '</I>');
+  AssertEquals('Failed on 3', FText[0], 'italics');
+end;
+
+procedure TTestHTMLParser.TestNoTags;
+begin
+  InitParser('italics');
+  AssertEquals('Failed on 1', FTags.Text, '');
+  AssertEquals('Failed on 2', FText[0], 'italics');
+end;
+
+procedure TTestHTMLParser.TestTagAndRemainingText;
+begin
+  InitParser('<i>italics</i> regular text');
+  AssertEquals('Failed on 1', FTags[0], '<I>');
+  AssertEquals('Failed on 2', FTags[1], '</I>');
+  AssertEquals('Failed on 3', FText[0], 'italics');
+  AssertEquals('Failed on 4', FText[1], ' regular text');
+end;
+
+procedure TTestHTMLParser.TestRegularTextAndTag;
+begin
+  InitParser('regular text <i>italics</i>');
+  AssertEquals('Failed on 1', FTags[0], '<I>');
+  AssertEquals('Failed on 2', FTags[1], '</I>');
+  AssertEquals('Failed on 3', FText[0], 'regular text ');
+  AssertEquals('Failed on 4', FText[1], 'italics');
+end;
+
+procedure TTestHTMLParser.TestTagNoText;
+begin
+  InitParser('<i></i>');
+  AssertEquals('Failed on 1', FTags[0], '<I>');
+  AssertEquals('Failed on 2', FTags[1], '</I>');
+  AssertEquals('Failed on 3', FText.Text, '');
+end;
+
+procedure TTestHTMLParser.TestGetTagName;
+begin
+  AssertEquals('failed on 1', 'I', FParser.GetTagName('<I>'));
+  AssertTrue('failed on 2 - case preserved', FParser.GetTagName('<I>') <> 'i');
+  AssertEquals('failed on 3', '/I', FParser.GetTagName('</I>'));
+  AssertEquals('failed on 4', 'i', FParser.GetTagName('<i>'));
+  AssertEquals('failed on 5', 'a', FParser.GetTagName('<a href="#hello">'));
+  AssertEquals('failed on 6', 'a', FParser.GetTagName('<a href="http://www.freepascal.org">'));
+  AssertEquals('failed on 7 - multi character tag', 'table', FParser.GetTagName('<table cellpadding=5 cellspacing=10 class="main">'));
+end;
+
+procedure TTestHTMLParser.TestGetNameValPair;
+begin
+  AssertEquals('failed on 1', '', FParser.GetNameValPair('<I>', ''));
+  AssertEquals('failed on 2', '', FParser.GetNameValPair('</I>', 'href'));
+  AssertEquals('failed on 3', '', FParser.GetNameValPair('<i>', ''));
+  AssertEquals('failed on 4', 'href="#hello"', FParser.GetNameValPair('<a href="#hello">', 'href'));
+  AssertEquals('failed on 5', 'href="http://www.freepascal.org"', FParser.GetNameValPair('<a href="http://www.freepascal.org">', 'href'));
+  AssertEquals('failed on 6', 'cellpadding=5', FParser.GetNameValPair('<table cellpadding=5 cellspacing=10 class="main">', 'cellpadding'));
+  AssertEquals('failed on 7', 'cellspacing=10', FParser.GetNameValPair('<table cellpadding=5 cellspacing=10 class="main">', 'cellspacing'));
+  AssertEquals('failed on 8', 'class="main"', FParser.GetNameValPair('<table cellpadding=5 cellspacing=10 class="main">', 'class'));
+end;
+
+procedure TTestHTMLParser.TestGetValFromNameVal;
+begin
+  AssertEquals('failed on 1', '#hello', FParser.GetValFromNameVal('href="#hello"'));
+  AssertEquals('failed on 2', 'http://www.freepascal.org', FParser.GetValFromNameVal('href="http://www.freepascal.org"'));
+  AssertEquals('failed on 3', '5', FParser.GetValFromNameVal('cellpadding=5'));
+  AssertEquals('failed on 4', 'black', FParser.GetValFromNameVal('bgcolor=black'));
+  AssertEquals('failed on 5', 'main', FParser.GetValFromNameVal('class="main"'));
+  AssertEquals('failed on 6', 'http://www.freepascal.org/docs/docs.php?num=10', FParser.GetValFromNameVal('href="http://www.freepascal.org/docs/docs.php?num=10"'));
+end;
+
+procedure TTestHTMLParser.TestGetVal;
+begin
+  AssertEquals('failed on 1', '', FParser.GetVal('<I>', ''));
+  AssertEquals('failed on 2', '', FParser.GetVal('</I>', 'href'));
+  AssertEquals('failed on 3', '', FParser.GetVal('<i>', ''));
+  AssertEquals('failed on 4', '#hello', FParser.GetVal('<a href="#hello">', 'href'));
+  AssertEquals('failed on 5', 'http://www.freepascal.org', FParser.GetVal('<a href="http://www.freepascal.org">', 'href'));
+  AssertEquals('failed on 6', '5', FParser.GetVal('<table cellpadding=5 cellspacing=10 class="main">', 'cellpadding'));
+  AssertEquals('failed on 7', '10', FParser.GetVal('<table cellpadding=5 cellspacing=10 class="main">', 'cellspacing'));
+  AssertEquals('failed on 8', 'main', FParser.GetVal('<table cellpadding=5 cellspacing=10 class="main">', 'class'));
+  AssertEquals('failed on 9', 'http://www.freepascal.org/docs/docs.php?num=10', FParser.GetVal('<a href="http://www.freepascal.org/docs/docs.php?num=10">', 'href'));
+end;
+
+
+initialization
+  RegisterTests([TTestHTMLParser]);
+end.
+
+
+end.
+

+ 2085 - 0
packages/fcl-report/test/tcreportdom.pp

@@ -0,0 +1,2085 @@
+unit tcreportdom;
+
+{$mode objfpc}{$H+}
+{.$define writexml}
+
+interface
+
+uses
+  Classes, SysUtils,
+  {$IFDEF fptest}
+  TestFramework,
+  {$ELSE}
+  fpcunit, testutils, testregistry,
+  {$ENDIF}
+  DOM, fpcanvas,
+  fpreport, fpreportdom, xmlwrite;
+
+type
+
+  TReportDOMTester = class(TTestCase)
+  Private
+    FDoc : TXMLDocument;
+    FRoot : TDOMElement;
+    procedure AssertAttribute(const AName, AValue: DomString);
+    procedure AssertValueElement(const AName, AValue: DomString);
+    procedure FillBytes(S: TStream; AMax: Byte);
+    Procedure AssertNoAttribute(Const AName : DomString);
+  protected
+    FRD : TFPReportDOM;
+    procedure SetUp; override;
+    procedure TearDown; override;
+  end;
+
+
+  TTestReportDOM = class(TReportDOMTester)
+  Public
+    procedure DoPop;
+    procedure TestStream(DoReset: Boolean);
+  published
+    procedure TestCreate;
+    Procedure TestAdd;
+    Procedure TestPush;
+    Procedure TestFind1;
+    Procedure TestFind2;
+    procedure TestPop1;
+    procedure TestPop2;
+    procedure TestPop3;
+    Procedure TestStreamToHex;
+    Procedure TestStreamToHex2;
+    procedure TestStreamEquals1;
+    procedure TestStreamEquals2;
+    procedure TestStreamEquals3;
+    procedure TestStreamEquals4;
+    procedure TestHexToStream;
+    Procedure TestWriteInteger1;
+    procedure TestWriteInteger2;
+    Procedure TestWriteString1;
+    procedure TestWriteString2;
+    Procedure TestWriteString3;
+    procedure TestWriteString4;
+    Procedure TestWriteWideString1;
+    procedure TestWriteWideString2;
+    Procedure TestWriteWideString3;
+    procedure TestWriteWideString4;
+    Procedure TestWriteBoolean1;
+    procedure TestWriteBoolean2;
+    Procedure TestWriteBoolean3;
+    procedure TestWriteBoolean4;
+    Procedure TestWriteFloat1;
+    procedure TestWriteFloat2;
+    Procedure TestWriteFloat3;
+    procedure TestWriteFloat4;
+    procedure TestWriteFloat5;
+    procedure TestWriteFloat6;
+    Procedure TestWriteDateTime1;
+    Procedure TestWriteDateTime2;
+    procedure TestWriteDateTime3;
+    procedure TestWriteDateTime4;
+    procedure TestWriteDateTime5;
+    procedure TestWriteDateTime6;
+    Procedure TestWriteStream1;
+    procedure TestWriteStream2;
+    Procedure TestWriteIntegerDiff1;
+    procedure TestWriteIntegerDiff2;
+    Procedure TestWriteStringDiff1;
+    procedure TestWriteStringDiff2;
+    Procedure TestWriteWideStringDiff1;
+    procedure TestWriteWideStringDiff2;
+    Procedure TestWriteBooleanDiff1;
+    procedure TestWriteBooleanDiff2;
+    Procedure TestWriteFloatDiff1;
+    procedure TestWriteFloatDiff2;
+    Procedure TestWriteDateTimeDiff1;
+    Procedure TestWriteDateTimeDiff2;
+    procedure TestWriteDateTimeDiff3;
+    procedure TestWriteDateTimeDiff4;
+    Procedure TestWriteStreamDiff1;
+    Procedure TestWriteStreamDiff2;
+    Procedure TestWriteStreamDiff3;
+    Procedure TestReadInteger1;
+    procedure TestReadInteger2;
+    procedure TestReadInteger3;
+    procedure TestReadInteger4;
+    procedure TestReadInteger5;
+    procedure TestReadInteger6;
+    Procedure TestReadString1;
+    procedure TestReadString2;
+    Procedure TestReadString3;
+    procedure TestReadString4;
+    Procedure TestReadWideString1;
+    procedure TestReadWideString2;
+    Procedure TestReadWideString3;
+    procedure TestReadWideString4;
+    Procedure TestReadDateTime1;
+    Procedure TestReadDateTime2;
+    procedure TestReadDateTime3;
+    procedure TestReadDateTime4;
+    procedure TestReadDateTime5;
+    procedure TestReadDateTime6;
+    procedure TestReadDateTime7;
+    procedure TestReadDateTime8;
+    procedure TestReadDateTime9;
+    procedure TestReadDateTime10;
+    Procedure TestReadBoolean1;
+    procedure TestReadBoolean2;
+    Procedure TestReadBoolean3;
+    procedure TestReadBoolean4;
+    procedure TestReadBoolean5;
+    procedure TestReadBoolean6;
+    procedure TestReadBoolean7;
+    procedure TestReadBoolean8;
+    Procedure TestReadFloat1;
+    procedure TestReadFloat2;
+    Procedure TestReadFloat3;
+    procedure TestReadFloat4;
+    procedure TestReadFloat5;
+    procedure TestReadFloat6;
+    procedure TestReadFloat7;
+    procedure TestReadStream1;
+    procedure TestReadStream2;
+    procedure TestReadStream3;
+    procedure TestReadStream4;
+    procedure TestReadStream5;
+    procedure TestReadStream6;
+  end;
+
+
+  TTestReportFrameDom = Class(TReportDOMTester)
+  private
+    FF,F2 : TFPReportFrame;
+    procedure FillFF;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestWriteDiff;
+    procedure TestRead;
+  end;
+
+
+  TTestReportLayoutDom = Class(TReportDOMTester)
+  private
+    FL,F2 : TFPReportLayout;
+    procedure FillFL;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestWriteDiff;
+    procedure TestRead;
+  end;
+
+
+  TTestReportElementDOM =  Class(TReportDOMTester)
+  private
+    FE,F2 : TFPReportElement;
+    procedure FillFE;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite1;
+    procedure TestWriteDiff1;
+    procedure TestWriteDiff2;
+    procedure TestWriteDiff3;
+    procedure TestRead1;
+    procedure TestRead2;
+  end;
+
+
+implementation
+
+{ ---------------------------------------------------------------------
+  General routines
+  ---------------------------------------------------------------------}
+
+procedure TReportDOMTester.SetUp;
+begin
+  FDoc:=TXMLDocument.Create;
+  FRoot:=FDoc.CreateElement('XMLReport');
+  FDoc.AppendChild(FRoot);
+  FRD:=TFPReportDOM.Create(FDoc,Nil);
+end;
+
+{$ifdef writexml}
+Var
+  WC : Integer;
+{$endif}
+
+procedure TReportDOMTester.TearDown;
+
+begin
+{$ifdef writexml}
+  Inc(wc);
+  WriteXML(FDoc,'xml-'+inttostr(wc)+'.xml');
+{$endif}
+  FreeAndNil(FRD);
+  FreeAndNil(FDoc);
+end;
+
+procedure TReportDOMTester.FillBytes(S : TStream; AMax : Byte);
+
+Var
+  B : Byte;
+
+begin
+  For B:=0 to AMax do
+    S.WriteBuffer(B,SizeOf(B));
+end;
+
+procedure TReportDOMTester.AssertAttribute(Const AName, AValue : DomString);
+
+Var
+  S: String;
+
+begin
+  S:='Attribute '+AName+' exists';
+  AssertEquals(S,True,FRD.CurrentElement.HasAttribute(AName));
+  S:='Attribute '+AName+' has correct value';
+  AssertEquals(S,AValue,FRD.CurrentElement[AName]);
+end;
+
+procedure TReportDOMTester.AssertValueElement(Const AName, AValue : DomString);
+
+Var
+  S: String;
+  N : TDomNode;
+
+begin
+  S:='Element with name '+AName+' exists';
+  N:=FRD.CurrentElement.FindNode(AName);
+  AssertNotNull(S,N);
+  S:='Element with name '+AName+' is TDOMElement';
+  AssertEquals(S,TDomElement,N.ClassType);
+  N:=N.FirstChild;
+  S:='Value element under element with name '+AName+' exists';
+  AssertNotNull(S,N);
+  AssertEquals('Value node is of type text',TEXT_NODE,N.NodeType);
+  AssertEquals('Value node has correct content',AValue,N.NodeValue);
+end;
+
+procedure TReportDOMTester.AssertNoAttribute(const AName: DomString);
+
+Var
+  S : String;
+
+begin
+  S:='No attribute of name '+AName;
+  AssertEquals(S,False, FRD.CurrentElement.hasAttribute(AName));
+end;
+
+{ ---------------------------------------------------------------------
+  Actual test routines
+  ---------------------------------------------------------------------}
+
+
+procedure TTestReportDOM.TestCreate;
+begin
+  AssertSame('XML Document property is set',FDoc,FRD.Document);
+  AssertSame('Root element is set ',FRoot,FRD.RootNode);
+  AssertSame('Current element is set ',FRoot,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestAdd;
+
+Var
+  E1,E2 : TDomElement;
+
+begin
+  E1:=FRD.CurrentElement;
+  E2:=FRD.NewDOMElement('MyElement');
+  AssertNotNull('NewDOMElement returns result',E2);
+  AssertSame('NewDomElement is child of current element',E2,E1.FindNode('MyElement'));
+  AssertEquals('New node element created with correct name','MyElement',E2.NodeName);
+  AssertSame('New node element is current element',E2,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestPush;
+
+Var
+  E1,E2 : TDomElement;
+
+begin
+  E1:=FRD.NewDOMElement('element1');
+  AssertSame('Current element equals created',E1,FRD.CurrentElement);
+  E2:=FRD.PushElement('element2');
+  AssertSame('Current element equals first created',E1,E2);
+  AssertEquals('New node pushed with correct name','element2',FRD.CurrentElement.NodeName);
+end;
+
+procedure TTestReportDOM.TestFind1;
+Var
+  E1,E2,E3 : TDomElement;
+
+begin
+  E1:=FRD.NewDOMElement('element1');
+  E2:=FRD.NewDomElement('element2');
+  FRD.CurrentElement:=E1;
+  E3:=FRD.FindChild('element2');
+  AssertSame('Found element',E2,E3);
+end;
+
+procedure TTestReportDOM.TestFind2;
+
+Var
+  E1,E2,E3 : TDomElement;
+
+begin
+  E1:=FRD.NewDOMElement('element1');
+  E2:=FRD.NewDomElement('element2');
+  FRD.CurrentElement:=E1;
+  E3:=FRD.FindChild('element3');
+  AssertNull('NonExisting element is null',E3);
+end;
+
+
+procedure TTestReportDOM.TestPop1;
+
+
+Var
+  E1,E2,E3 : TDomElement;
+
+begin
+  E1:=FRD.NewDOMElement('element1');
+  AssertSame('Current element equals created',E1,FRD.CurrentElement);
+  E2:=FRD.PushElement('element2');
+  AssertSame('Current element equals first created',E1,E2);
+  E3:=FRD.CurrentElement;
+  AssertEquals('New node pushed with correct name','element2',E3.NodeName);
+  AssertSame('Pop returns current element',E3,FRD.PopElement);
+  AssertSame('Current element after pop is correct',E1,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.DoPop;
+
+begin
+  FRD.PopElement;
+end;
+
+procedure TTestReportDOM.TestPop2;
+
+begin
+  AssertException('Pop on empty stack raise exception',EReportDom,@DoPop);
+end;
+
+procedure TTestReportDOM.TestPop3;
+
+begin
+  FRD.NewDOMElement('element1');
+  FRD.PushElement('element2');
+  FRD.PopElement;
+  AssertException('Pop on empty stack raise exception',EReportDom,@DoPop);
+end;
+
+
+procedure TTestReportDOM.TestStream(DoReset : Boolean);
+
+Var
+  S : TMemoryStream;
+  B : Byte;
+  T,H : String;
+
+begin
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,255);
+    S.Position:=0;
+    T:=FRD.StreamToHex(S);
+    AssertEquals('Stream position is zero',0,S.Position);
+    AssertEquals('Correct number of bytes returned by streamtohex',512,Length(T));
+    For B:=0 to 255 do
+      begin
+      H:=Copy(T,1,2);
+      Delete(T,1,2);
+      AssertEquals(Format('Correct value at position %d',[b]),H,HexStr(B,2));
+      end;
+  Finally
+    S.Free;
+  end;
+end;
+
+
+
+procedure TTestReportDOM.TestStreamToHex;
+
+begin
+  TestStream(True);
+end;
+
+procedure TTestReportDOM.TestStreamToHex2;
+
+begin
+  TestStream(False);
+end;
+
+
+procedure TTestReportDOM.TestHexToStream;
+
+
+Var
+  S : TMemoryStream;
+  SS : TStringStream;
+  H : String;
+
+begin
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,255);
+    H:=FRD.StreamToHex(S);
+    SS:=FRD.HexToStringStream(H);
+    try
+      AssertEquals('Size of stream is OK',256,SS.Size);
+      AssertEquals('HexToStringStream OK',True,FRD.StreamsEqual(S,SS));
+    finally
+      SS.Free;
+    end;
+  finally
+    S.Free;
+  end;
+end;
+
+
+procedure TTestReportDOM.TestStreamEquals1;
+
+Var
+  S : TMemoryStream;
+
+begin
+  S:=TMemoryStream.Create;
+  try
+    AssertEquals('Same stream always equal',True,FRD.StreamsEqual(S,S));
+  finally
+    S.Free;
+  end;
+end;
+
+
+procedure TTestReportDOM.TestStreamEquals2;
+
+Var
+  S1,S2 : TMemoryStream;
+
+begin
+  S1:=TMemoryStream.Create;
+  try
+    FillBytes(S1,255);
+    S2:=TMemoryStream.Create;
+    try
+      FillBytes(S2,255);
+      AssertEquals('Same content always equal',True,FRD.StreamsEqual(S1,S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestStreamEquals3;
+
+Var
+  S1,S2 : TMemoryStream;
+
+begin
+  S1:=TMemoryStream.Create;
+  try
+    FillBytes(S1,255);
+    S2:=TMemoryStream.Create;
+    try
+      FillBytes(S2,254);
+      AssertEquals('Different sizes makes not equal',False,FRD.StreamsEqual(S1,S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestStreamEquals4;
+
+Var
+  S1,S2 : TMemoryStream;
+  B : Byte;
+
+begin
+  S1:=TMemoryStream.Create;
+  try
+    FillBytes(S1,255);
+    AssertEquals(0,S1.Seek(0,soFromBeginning));
+    B:=10;
+    S1.WriteBuffer(B,1);
+    B:=12;
+    S1.Position:=0;
+    S1.ReadBuffer(B,1);
+    AssertEquals(10,B);
+    AssertEquals(256,S1.Size);
+    S2:=TMemoryStream.Create;
+    try
+      FillBytes(S2,255);
+      AssertEquals('Different streams makes not equal',False,FRD.StreamsEqual(S1,S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+
+
+procedure TTestReportDOM.TestWriteInteger1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteInteger('Int',1,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Int','1');
+end;
+
+procedure TTestReportDOM.TestWriteInteger2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteInteger('Int',1,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Int','1');
+end;
+
+procedure TTestReportDOM.TestWriteString1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','Aloha',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Str','Aloha');
+end;
+
+procedure TTestReportDOM.TestWriteString2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','Aloha',psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Str','Aloha');
+end;
+
+procedure TTestReportDOM.TestWriteString3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Str','');
+end;
+
+procedure TTestReportDOM.TestWriteString4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','',psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Str','');
+
+end;
+
+procedure TTestReportDOM.TestWriteWideString1;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideString('WideStr',W,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('WideStr',W);
+end;
+
+procedure TTestReportDOM.TestWriteWideString2;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideString('WideStr',W,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('WideStr',W);
+end;
+
+procedure TTestReportDOM.TestWriteWideString3;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='';
+  FRD.WriteWideString('WideStr',W,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('WideStr','');
+end;
+
+procedure TTestReportDOM.TestWriteWideString4;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='';
+  FRD.WriteWideString('WideStr',W,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('WideStr',W);
+end;
+
+
+procedure TTestReportDOM.TestWriteBoolean1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',True,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Bool','1');
+end;
+
+procedure TTestReportDOM.TestWriteBoolean2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',False,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Bool','0');
+end;
+
+procedure TTestReportDOM.TestWriteBoolean3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',True,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Bool','1');
+end;
+
+procedure TTestReportDOM.TestWriteBoolean4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',False,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Bool','0');
+end;
+
+procedure TTestReportDOM.TestWriteFloat1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',1.23,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Float','1.230000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloat2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',1.23,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Float','1.230000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloat3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',-1.23,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Float','-1.230000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloat4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',-1.23,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Float','-1.230000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloat5;
+
+Var
+  E : TDOMElement;
+  x : extended;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  X:=0.000000000;
+  FRD.WriteFloat('Float',x,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Float','0.000000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloat6;
+
+Var
+  E : TDOMElement;
+  x : extended;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  X:=0.000000000;
+  FRD.WriteFloat('Float',x,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Float','0.000000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime1;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTime('Date',D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Date','20080918');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime2;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTime('Date',D,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Date','20080918');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime3;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Date','20080918110355123');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime4;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Date','20080918110355123');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime5;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Date','00000000110355123');
+end;
+
+procedure TTestReportDOM.TestWriteDateTime6;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertValueElement('Date','00000000110355123');
+end;
+
+procedure TTestReportDOM.TestWriteStream1;
+Var
+  E : TDOMElement;
+  S : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    FRD.WriteStream('Stream',S,psAttr);
+    AssertSame('Current element not changed',E,FRD.CurrentElement);
+    AssertAttribute('Stream','000102030405060708090A0B0C0D0E0F');
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteStream2;
+
+Var
+  E : TDOMElement;
+  S : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    FRD.WriteStream('Stream',S,psElement);
+    AssertSame('Current element not changed',E,FRD.CurrentElement);
+    AssertValueElement('Stream','000102030405060708090A0B0C0D0E0F');
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteIntegerDiff1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteIntegerDiff('Int',1,0,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Int','1');
+end;
+
+procedure TTestReportDOM.TestWriteIntegerDiff2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteIntegerDiff('Int',1,1,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Int');
+end;
+
+procedure TTestReportDOM.TestWriteStringDiff1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteStringDiff('Str','Aloha','mopa',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Str','Aloha');
+end;
+
+procedure TTestReportDOM.TestWriteStringDiff2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteStringDiff('Str','Aloha','Aloha',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Str');
+end;
+
+procedure TTestReportDOM.TestWriteWideStringDiff1;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideStringDiff('WideStr',W,W,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('WideStr');
+end;
+
+procedure TTestReportDOM.TestWriteWideStringDiff2;
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideStringDiff('WideStr',W,W+' me',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('WideStr',W);
+end;
+
+procedure TTestReportDOM.TestWriteBooleanDiff1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBooleanDiff('Bool',True,False,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Bool','1');
+end;
+
+procedure TTestReportDOM.TestWriteBooleanDiff2;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBooleanDiff('Bool',True,True,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Bool');
+end;
+
+procedure TTestReportDOM.TestWriteFloatDiff1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloatDiff('Float',1.23,1.24,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Float','1.230000000E+0000');
+end;
+
+procedure TTestReportDOM.TestWriteFloatDiff2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloatDiff('Float',1.23,1.23,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Float');
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff1;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTimeDiff('Date',D,D+1,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Date','20080918');
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff2;
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTimeDiff('Date',D,D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Date');
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff3;
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(0,0,0,1);
+  FRD.WriteDateTimeDiff('Date',D,D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertNoAttribute('Date');
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff4;
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTimeDiff('Date',D,D+EncodeTime(0,0,0,1),psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertAttribute('Date','20080918');
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff1;
+
+Var
+  E : TDOMElement;
+  S : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    FRD.WriteStreamDiff('Stream',S,S,psAttr);
+    AssertSame('Current element not changed',E,FRD.CurrentElement);
+    AssertNoAttribute('Stream');
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff2;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    T:=TMemoryStream.Create;
+    try
+      FillBytes(T,15);
+      FRD.WriteStreamDiff('Stream',S,T,psAttr);
+      AssertSame('Current element not changed',E,FRD.CurrentElement);
+      AssertNoAttribute('Stream');
+    finally
+      FreeAndNil(T);
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff3;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    T:=TMemoryStream.Create;
+    try
+      FillBytes(T,16);
+      FRD.WriteStreamDiff('Stream',S,T,psAttr);
+      AssertSame('Current element not changed',E,FRD.CurrentElement);
+      AssertAttribute('Stream','000102030405060708090A0B0C0D0E0F');
+    finally
+      FreeAndNil(T);
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestReadInteger1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteInteger('Int',1,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading integer property',1,FRD.ReadInteger('Int',-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadInteger2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteInteger('Int',1,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading integer property with wrong storagetype',-1,FRD.ReadInteger('Int',-1,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadInteger3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent integer property (attr)',-1,FRD.ReadInteger('Int',-1,psAttr));
+end;
+
+procedure TTestReportDOM.TestReadInteger4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent integer property (element)',-1,FRD.ReadInteger('Int',-1,psElement));
+end;
+
+procedure TTestReportDOM.TestReadInteger5;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Int','Aloha',psAttr);
+  AssertEquals('Reading wrongly typed integer property (attr)',-1,FRD.ReadInteger('Int',-1,psAttr));
+end;
+
+
+procedure TTestReportDOM.TestReadInteger6;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Int','Aloha',psElement);
+  AssertEquals('Reading wrongly typed integer property (element)',-1,FRD.ReadInteger('Int',-1,psElement));
+end;
+
+procedure TTestReportDOM.TestReadString1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','Aloha',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading string property (attr) ','Aloha',FRD.ReadString('Str','(none)',psattr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadString2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Str','Aloha',psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading string property (element)','Aloha',FRD.ReadString('Str','(none)',psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadString3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent string property (attr)','(none)',FRD.ReadString('Str','(none)',psAttr));
+end;
+
+procedure TTestReportDOM.TestReadString4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent string property (element)','(none)',FRD.ReadString('Str','(none)',psElement));
+end;
+
+procedure TTestReportDOM.TestReadWideString1;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideString('WideStr',W,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading wide string property (attr) ',W,FRD.ReadWideString('WideStr','(none)',psattr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadWideString2;
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  FRD.WriteWideString('WideStr',W,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading wide string property (element) ',W,FRD.ReadWideString('WideStr','(none)',psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadWideString3;
+
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  AssertEquals('Reading non-existing wide string property (attr) ',W,FRD.ReadWideString('WideStr',W,psattr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadWideString4;
+Var
+  E : TDOMElement;
+  W : WideString;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  W:='Wide Aloha';
+  AssertEquals('Reading non-existing wide string property (element) ',W,FRD.ReadWideString('WideStr',W,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime1;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTime('Date',D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading date property (Attr)',D,FRD.ReadDateTime('Date',D-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime2;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteDateTime('Date',D,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading date property (Attr)',D,FRD.ReadDateTime('Date',D-1,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime3;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading date/time property (Attr)',D,FRD.ReadDateTime('Date',D-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime4;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date',D,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading date/time property (element)',D,FRD.ReadDateTime('Date',D-1,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime5;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeTime(11,03,55,123);
+  AssertEquals('Reading non-existent time property (element)',D,FRD.ReadDateTime('Date',D,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime6;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeTime(11,03,55,123);
+  AssertEquals('Reading non-existent time property (element)',D,FRD.ReadDateTime('Date',D,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime7;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteString('Date','20080918',psAttr);
+  AssertEquals('Reading date-only property (element)',D,FRD.ReadDateTime('Date',D-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime8;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18);
+  FRD.WriteString('Date','20080918aaa',psAttr);
+  AssertEquals('Reading wrong time/date-ok property (element)',D-1,FRD.ReadDateTime('Date',D-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime9;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,0);
+  FRD.WriteString('Date','20080918110355',psAttr);
+  AssertEquals('Reading wrong time (no millisec)/date property (element)',DateTimeToStr(D),DateTimeToStr(FRD.ReadDateTime('Date',D-1,psAttr)));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime10;
+
+Var
+  E : TDOMElement;
+  D : TDateTime;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  D:=EncodeDate(2008,9,18)+EncodeTime(11,03,55,0);
+  FRD.WriteString('Date','200809',psAttr);
+  AssertEquals('Reading wrong date property (element)',D-1,FRD.ReadDateTime('Date',D-1,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',True,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading true boolean property (attr)',True,FRD.ReadBoolean('Bool',False,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',False,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading true boolean property (attr)',False,FRD.ReadBoolean('Bool',True,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean3;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',True,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading true boolean property (element)',True,FRD.ReadBoolean('Bool',False,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean4;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteBoolean('Bool',False,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading true boolean property (element)',False,FRD.ReadBoolean('Bool',True,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean5;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Bool','Aloha',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading invalid boolean property',True,FRD.ReadBoolean('Bool',True,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean6;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Bool','Aloha',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading invalid boolean property',False,FRD.ReadBoolean('Bool',False,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean7;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent boolean property',True,FRD.ReadBoolean('Bool',True,psAttr));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean8;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading non-existent boolean property (element)',True,FRD.ReadBoolean('Bool',True,psElement));
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat1;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',1.23,psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading float property (attr)',1.23,FRD.ReadFloat('Float',2.34,psAttr),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat2;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteFloat('Float',1.23,psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading float property (attr)',1.23,FRD.ReadFloat('Float',2.34,psElement),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat3;
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Float','1.23a',psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading wrong float property (element)',2.34,FRD.ReadFloat('Float',2.34,psElement),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat4;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Float','1.23a',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading wrong float property (attr)',2.34,FRD.ReadFloat('Float',2.34,psAttr),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat5;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading nonexistent float property (attr)',2.34,FRD.ReadFloat('Float',2.34,psAttr),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat6;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertEquals('Reading nonexistent float property (element)',2.34,FRD.ReadFloat('Float',2.34,psElement),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat7;
+
+Var
+  E : TDOMElement;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Float','1',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  AssertEquals('Reading integer-formatted float property (attr)',1,FRD.ReadFloat('Float',2.34,psAttr),0.001);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadStream1;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    FRD.WriteStream('Stream',S,psAttr);
+    AssertSame('Current element not changed',E,FRD.CurrentElement);
+    T:=TMemoryStream.Create;
+    try
+      AssertEquals('Reading stream data (attr)',True,FRD.ReadStream('Stream',T,psAttr));
+      AssertEquals('Read stream equals written stream',True,FRD.StreamsEqual(S,T));
+    finally
+      T.Free;
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream2;
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,15);
+    FRD.WriteStream('Stream',S,psElement);
+    AssertSame('Current element not changed',E,FRD.CurrentElement);
+    T:=TMemoryStream.Create;
+    try
+      AssertEquals('Reading stream data (element)',True,FRD.ReadStream('Stream',T,psElement));
+      AssertEquals('Read stream equals written stream',True,FRD.StreamsEqual(S,T));
+    finally
+      T.Free;
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream3;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Stream','',psElement);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  T:=TMemoryStream.Create;
+  try
+    AssertEquals('Reading stream data (element)',False,FRD.ReadStream('Stream',T,psElement));
+    AssertEquals('Read stream is empty',0,T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream4;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  FRD.WriteString('Stream','',psAttr);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  T:=TMemoryStream.Create;
+  try
+    AssertEquals('Reading stream data (attr)',False,FRD.ReadStream('Stream',T,psAttr));
+    AssertEquals('Read stream is empty',0,T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream5;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  T:=TMemoryStream.Create;
+  try
+    AssertEquals('Reading non-existent stream data (attr)',False,FRD.ReadStream('Stream',T,psAttr));
+    AssertEquals('Read stream is empty',0,T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream6;
+
+Var
+  E : TDOMElement;
+  S,T : TMemoryStream;
+
+begin
+  E:=FRD.NewDOMElement('MyElement');
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  T:=TMemoryStream.Create;
+  try
+    AssertEquals('Reading non-existent stream data (element)',False,FRD.ReadStream('Stream',T,psElement));
+    AssertEquals('Read stream is empty',0,T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+{ TTestReportFrameDom }
+
+procedure TTestReportFrameDom.Setup;
+begin
+  inherited Setup;
+  FF:=TFPReportframe.Create(Nil);
+  F2:=TFPReportframe.Create(Nil);
+  FRD.NewDOMElement('Shape');
+end;
+
+procedure TTestReportFrameDom.TearDown;
+begin
+  FreeAndNil(FF);
+  FreeAndNil(F2);
+  inherited TearDown;
+end;
+
+Procedure FillFrame(FF : TFPReportFrame);
+
+begin
+  FF.Width:=2;
+  FF.Pen:=psDot;
+  FF.Shape:=fsRoundedRect;
+  FF.Color:=23;
+  FF.Lines:=[flTop,flBottom];
+end;
+
+procedure TTestReportFrameDom.FillFF;
+
+begin
+  FillFrame(FF);
+end;
+
+procedure TTestReportFrameDom.TestWrite;
+begin
+  FillFF;
+  FF.WriteXML(FRD,Nil);
+  AssertEquals('Width saved',2,FRD.ReadInteger('W',1));
+  AssertEquals('Pen saved',ord(psDot),FRD.ReadInteger('W',1));
+  AssertEquals('Color saved',ord(23),FRD.ReadInteger('C',1));
+  AssertEquals('Shape saved',ord(fsRoundedRect),FRD.ReadInteger('S',0));
+  AssertEquals('Lines saved',Integer([flTop,flBottom]),FRD.ReadInteger('L',0));
+end;
+
+procedure TTestReportFrameDom.TestWriteDiff;
+
+begin
+  FillFF;
+  FF.WriteXML(FRD,F2);
+  AssertEquals('Width saved',2,FRD.ReadInteger('W',1));
+  AssertEquals('Pen saved',ord(psDot),FRD.ReadInteger('W',1));
+  AssertEquals('Color saved',ord(23),FRD.ReadInteger('C',1));
+  AssertEquals('Shape saved',ord(fsRoundedRect),FRD.ReadInteger('S',0));
+  AssertEquals('Lines saved',Integer([flTop,flBottom]),FRD.ReadInteger('L',0));
+
+end;
+
+procedure TTestReportFrameDom.TestRead;
+begin
+  FillFF;
+  FF.WriteXML(FRD,Nil);
+  F2.ReadXML(FRD);
+  AssertEquals('Width loaded',FF.Width,F2.Width);
+  AssertEquals('Pen loaded',Ord(FF.Pen),Ord(F2.Pen));
+  AssertEquals('Color loaded',Ord(FF.Color),Ord(F2.Color));
+  AssertEquals('Shape loaded',Ord(FF.Shape),Ord(F2.Shape));
+  AssertEquals('Lines loaded',Integer(FF.Lines),Integer(F2.Lines));
+end;
+
+{ TTestReportLayoutDom }
+
+Procedure FillLayout(FL : TFPReportLayout);
+
+begin
+  FL.Top:=1.2;
+  FL.Left:=3.4;
+  FL.Width:=5.6;
+  FL.Height:=7.8;
+end;
+
+procedure TTestReportLayoutDom.FillFL;
+begin
+  FillLayout(FL);
+end;
+
+procedure TTestReportLayoutDom.Setup;
+begin
+  inherited Setup;
+  FL:=TFPReportLayout.Create(Nil);
+  F2:=TFPReportLayout.Create(Nil);
+  FRD.NewDOMElement('layout');
+end;
+
+procedure TTestReportLayoutDom.TearDown;
+begin
+  FreeAndNil(Fl);
+  FreeAndNil(F2);
+  inherited TearDown;
+end;
+
+procedure TTestReportLayoutDom.TestWrite;
+begin
+  FillFL;
+  FL.WriteXML(FRD,Nil);
+  AssertEquals('Top saved',1.2,FRD.ReadFloat('t',0.0));
+  AssertEquals('Left saved',3.4,FRD.ReadFloat('l',0.0));
+  AssertEquals('Width saved',5.6,FRD.ReadFloat('w',0.0));
+  AssertEquals('Height saved',7.8,FRD.ReadFloat('h',0.0));
+end;
+
+procedure TTestReportLayoutDom.TestWriteDiff;
+begin
+  FillFL;
+  FL.WriteXML(FRD,F2);
+  AssertEquals('Top saved',1.2,FRD.ReadFloat('t',0.0));
+  AssertEquals('Left saved',3.4,FRD.ReadFloat('l',0.0));
+  AssertEquals('Width saved',5.6,FRD.ReadFloat('w',0.0));
+  AssertEquals('Height saved',7.8,FRD.ReadFloat('h',0.0));
+end;
+
+procedure TTestReportLayoutDom.TestRead;
+begin
+  FillFL;
+  FL.WriteXML(FRD,Nil);
+  F2.ReadXML(FRD);
+  AssertEquals('Top saved',FL.Top,F2.Top);
+  AssertEquals('Left saved',FL.Left,F2.Left);
+  AssertEquals('Width saved',FL.Width,F2.Width);
+  AssertEquals('Height saved',FL.Height,F2.Height);
+end;
+
+{ TTestReportElementDOM }
+
+procedure TTestReportElementDOM.FillFE;
+begin
+  FillLayout(FE.Layout);
+  FillFrame(FE.Frame);
+end;
+
+procedure TTestReportElementDOM.Setup;
+begin
+  inherited Setup;
+  FE:=TFPReportElement.Create(Nil);
+  F2:=TFPReportElement.Create(Nil);
+  FRD.NewDomElement('element');
+end;
+
+procedure TTestReportElementDOM.TearDown;
+begin
+  FreeAndNil(F2);
+  FreeAndNil(FE);
+  inherited TearDown;
+end;
+
+procedure TTestReportElementDOM.TestWrite1;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  FE.WriteXML(FRD);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  E:=FRD.FindChild('Layout');
+  AssertNotNull('Layout was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Top saved',1.2,FRD.ReadFloat('t',0.0));
+    AssertEquals('Left saved',3.4,FRD.ReadFloat('l',0.0));
+    AssertEquals('Width saved',5.6,FRD.ReadFloat('w',0.0));
+    AssertEquals('Height saved',7.8,FRD.ReadFloat('h',0.0));
+  finally
+    FRD.PopElement;
+  end;
+  E:=FRD.FindChild('Frame');
+  AssertNotNull('Frame was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Width saved',2,FRD.ReadInteger('W',1));
+    AssertEquals('Pen saved',ord(psDot),FRD.ReadInteger('W',1));
+    AssertEquals('Color saved',ord(23),FRD.ReadInteger('C',1));
+    AssertEquals('Shape saved',ord(fsRoundedRect),FRD.ReadInteger('S',0));
+    AssertEquals('Lines saved',Integer([flTop,flBottom]),FRD.ReadInteger('L',0));
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementDOM.TestWriteDiff1;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  FE.WriteXML(FRD,F2);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  E:=FRD.FindChild('Layout');
+  AssertNotNull('Layout was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Top saved',1.2,FRD.ReadFloat('t',0.0));
+    AssertEquals('Left saved',3.4,FRD.ReadFloat('l',0.0));
+    AssertEquals('Width saved',5.6,FRD.ReadFloat('w',0.0));
+    AssertEquals('Height saved',7.8,FRD.ReadFloat('h',0.0));
+  finally
+    FRD.PopElement;
+  end;
+  E:=FRD.FindChild('Frame');
+  AssertNotNull('Frame was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Width saved',2,FRD.ReadInteger('W',1));
+    AssertEquals('Pen saved',ord(psDot),FRD.ReadInteger('W',1));
+    AssertEquals('Color saved',ord(23),FRD.ReadInteger('C',1));
+    AssertEquals('Shape saved',ord(fsRoundedRect),FRD.ReadInteger('S',0));
+    AssertEquals('Lines saved',Integer([flTop,flBottom]),FRD.ReadInteger('L',0));
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementDOM.TestWriteDiff2;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  F2.Layout.Assign(FE.Layout);
+  FE.WriteXML(FRD,F2);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  E:=FRD.FindChild('Layout');
+  AssertNotNull('Layout was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Top saved',1.2,FRD.ReadFloat('t',0.0));
+    AssertEquals('Left saved',3.4,FRD.ReadFloat('l',0.0));
+    AssertEquals('Width saved',5.6,FRD.ReadFloat('w',0.0));
+    AssertEquals('Height saved',7.8,FRD.ReadFloat('h',0.0));
+  finally
+    FRD.PopElement;
+  end;
+  E:=FRD.FindChild('Frame');
+  AssertNotNull('Frame was saved',E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Width saved',2,FRD.ReadInteger('W',1));
+    AssertEquals('Pen saved',ord(psDot),FRD.ReadInteger('W',1));
+    AssertEquals('Color saved',ord(23),FRD.ReadInteger('C',1));
+    AssertEquals('Shape saved',ord(fsRoundedRect),FRD.ReadInteger('S',0));
+    AssertEquals('Lines saved',Integer([flTop,flBottom]),FRD.ReadInteger('L',0));
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementDOM.TestWriteDiff3;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  F2.Frame.Assign(FE.Frame);
+  FE.WriteXML(FRD,F2);
+  AssertSame('Current element not changed',E,FRD.CurrentElement);
+  E:=FRD.FindChild('Layout');
+  AssertNotNull('Layout was saved',E);
+  E:=FRD.FindChild('Frame');
+  AssertNull('Frame was not saved',E);
+end;
+
+procedure TTestReportElementDOM.TestRead1;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  FE.WriteXML(FRD,Nil);
+  F2.ReadXML(FRD);
+  AssertEquals('Layout was read',True,FE.Layout.Equals(F2.Layout));
+  AssertEquals('Frame was read',True,FE.Frame.Equals(F2.Frame));
+end;
+
+procedure TTestReportElementDOM.TestRead2;
+
+Var
+  E : TDomElement;
+
+begin
+  FillFE;
+  E:=FRD.CurrentElement;
+  F2.Frame.Assign(FE.Frame);
+  // Only layout is written
+  FE.WriteXML(FRD,F2);
+  FreeAndNil(F2);
+  F2:=TFPReportElement.Create(Nil);
+  F2.ReadXML(FRD);
+  AssertEquals('Layout was read',True,FE.Layout.Equals(F2.Layout));
+  AssertEquals('Frame was not read',False,FE.Frame.Equals(F2.Frame));
+end;
+
+initialization
+  RegisterTests({$IFDEF fptest} 'ReportDOM', {$ENDIF}
+       [TTestReportDOM{$IFDEF fptest}.Suite{$ENDIF},
+        TTestReportFrameDom{$IFDEF fptest}.Suite{$ENDIF},
+        TTestReportLayoutDom{$IFDEF fptest}.Suite{$ENDIF},
+        TTestReportElementDOM{$IFDEF fptest}.Suite{$ENDIF}
+        ]);
+end.
+

+ 1618 - 0
packages/fcl-report/test/tcreportstreamer.pp

@@ -0,0 +1,1618 @@
+unit tcreportstreamer;
+
+{$mode objfpc}{$H+}
+{.$define writejson}
+{.$define verbosedebug}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry, fpcanvas, fpjson,
+  fpreport,fpreportstreamer;
+
+type
+
+  TReportStreamTester = class(TTestCase)
+  Private
+    procedure FillBytes(S: TStream; AMax: Byte);
+  protected
+    FRD : TFPReportJSONStreamer;
+    procedure SetUp; override;
+    procedure TearDown; override;
+  end;
+
+
+  TTestReportDOM = class(TReportStreamTester)
+  Public
+    procedure TestStream(DoReset: Boolean);
+  published
+    procedure TestCreate;
+    procedure TestAdd;
+    procedure TestFind1;
+    procedure TestFind2;
+    procedure TestPush;
+    procedure TestPop1;
+    procedure TestStreamToHex;
+    procedure TestStreamToHex2;
+    procedure TestStreamEquals1;
+    procedure TestStreamEquals2;
+    procedure TestStreamEquals3;
+    procedure TestStreamEquals4;
+    procedure TestHexToStream;
+    procedure TestWriteInteger1;
+    procedure TestWriteString1;
+    procedure TestWriteString2;
+    procedure TestWriteBoolean1;
+    procedure TestWriteBoolean2;
+    procedure TestWriteFloat1;
+    procedure TestWriteFloat2;
+    procedure TestWriteFloat3;
+    procedure TestWriteDateTime1;
+    procedure TestWriteDateTime2;
+    procedure TestWriteDateTime3;
+    procedure TestWriteStream1;
+    procedure TestWriteIntegerDiff1;
+    procedure TestWriteIntegerDiff2;
+    procedure TestWriteStringDiff1;
+    procedure TestWriteStringDiff2;
+    procedure TestWriteBooleanDiff1;
+    procedure TestWriteBooleanDiff2;
+    procedure TestWriteFloatDiff1;
+    procedure TestWriteFloatDiff2;
+    procedure TestWriteDateTimeDiff1;
+    procedure TestWriteDateTimeDiff2;
+    procedure TestWriteDateTimeDiff3;
+    procedure TestWriteStreamDiff1;
+    procedure TestWriteStreamDiff2;
+    procedure TestWriteStreamDiff3;
+    procedure TestReadInteger1;
+    procedure TestReadInteger2;
+    procedure TestReadInteger3;
+    procedure TestReadString1;
+    procedure TestReadString2;
+    procedure TestReadString3;
+    procedure TestReadDateTime1;
+    procedure TestReadDateTime2;
+    procedure TestReadDateTime3;
+    procedure TestReadDateTime4;
+    procedure TestReadDateTime5;
+    procedure TestReadBoolean1;
+    procedure TestReadBoolean2;
+    procedure TestReadBoolean3;
+    procedure TestReadBoolean4;
+    procedure TestReadFloat1;
+    procedure TestReadFloat2;
+    procedure TestReadFloat3;
+    procedure TestReadFloat4;
+    procedure TestReadStream1;
+    procedure TestReadStream2;
+    procedure TestReadStream3;
+    procedure TestALL;
+  end;
+
+
+  TTestReportFrameDom = Class(TReportStreamTester)
+  private
+    FF,F2 : TFPReportFrame;
+    procedure FillFF;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestWriteDiff;
+    procedure TestRead;
+  end;
+
+
+  TTestReportLayoutDom = Class(TReportStreamTester)
+  private
+    FL,F2 : TFPReportLayout;
+    procedure FillFL;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestWriteDiff;
+    procedure TestRead;
+  end;
+
+
+  TTestReportElementDOM =  Class(TReportStreamTester)
+  private
+    FE,F2 : TFPReportElement;
+    procedure FillFE;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite1;
+    procedure TestWriteDiff1;
+    procedure TestWriteDiff2;
+    procedure TestRead1;
+    procedure TestRead2;
+  end;
+
+
+  TTestReportElementWithChildrenDOM = class(TReportStreamTester)
+  private
+    FE, F2: TFPReportElementWithChildren;
+    procedure FillFE;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestRead;
+  end;
+
+
+  TTestReportPageHeader = class(TReportStreamTester)
+  private
+    FE, F2: TFPReportPageHeaderBand;
+    procedure FillFE;
+  protected
+    procedure Setup; override;
+    procedure TearDown; override;
+  published
+    procedure TestWrite;
+    procedure TestWrite2;
+    procedure TestRead;
+  end;
+
+
+implementation
+
+
+{ TTestReportDOM }
+
+procedure TTestReportDOM.TestStream(DoReset: Boolean);
+Var
+  S : TMemoryStream;
+  B : Byte;
+  T,H : String;
+begin
+  S:=TMemoryStream.Create;
+  try
+    FillBytes(S,255);
+    S.Position:=0;
+    T:=FRD.StreamToHex(S);
+    AssertEquals('Stream position is zero',0,S.Position);
+    AssertEquals('Correct number of bytes returned by streamtohex',512,Length(T));
+    For B:=0 to 255 do
+      begin
+      H:=Copy(T,1,2);
+      Delete(T,1,2);
+      AssertEquals(Format('Correct value at position %d',[b]),H,HexStr(B,2));
+      end;
+  Finally
+    S.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestCreate;
+begin
+  AssertTrue('Failed on 1', Assigned(FRD.JSON));
+end;
+
+procedure TTestReportDOM.TestAdd;
+var
+  E1, E2: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E1 := FRD.JSON;
+  E2 := TJSONObject(FRD.NewElement('MyElement'));
+  AssertNotNull('NewElement returns result', E2);
+  AssertSame('NewElement is child of current element', E2, E1.Find('MyElement'));
+  AssertEquals('New element created with correct name', '{ "MyElement" : {} }', E1.AsJSON);
+//  AssertSame('New element is current element',E2,FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestFind1;
+var
+  E1, E2, E3: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+
+  E1 := TJSONObject(FRD.NewElement('element1'));
+  E2 := TJSONObject(FRD.NewElement('element2'));
+
+  FRD.CurrentElement := E1;
+  E3 := TJSONObject(FRD.FindChild('element2'));
+
+  AssertEquals('Failed on 1', '{ "element1" : { "element2" : {} } }', FRD.JSON.AsJSON);
+  AssertSame('Found element', E2, E3);
+end;
+
+procedure TTestReportDOM.TestFind2;
+var
+  E1, E2, E3: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+
+  E1 := TJSONObject(FRD.NewElement('element1'));
+  E2 := TJSONObject(FRD.NewElement('element2'));
+
+  FRD.CurrentElement := E1;
+  E3 := TJSONObject(FRD.FindChild('element3'));
+
+  AssertNull('NonExisting element is null', E3);
+end;
+
+procedure TTestReportDOM.TestPush;
+var
+  E1, E2: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+
+  E1 := TJSONObject(FRD.NewElement('element1'));
+  AssertSame('Current element equals created', E1, FRD.CurrentElement);
+
+  E2 := TJSONObject(FRD.NewElement('element2'));
+  AssertEquals('New node pushed with correct name', '{ "element2" : {} }', E1.AsJSON);
+end;
+
+procedure TTestReportDOM.TestPop1;
+var
+  E1, E2, E3: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+
+  E1 := TJSONObject(FRD.NewElement('element1'));
+  AssertSame('Failed on 1', E1, FRD.CurrentElement);
+
+  E2 := TJSONObject(FRD.PushElement('element2'));
+  AssertSame('Failed on 2', E2, FRD.CurrentElement);
+  AssertEquals('Failed on 3', '{ "element2" : {} }', E1.AsJSON);
+  E3 := FRD.CurrentElement;
+
+  AssertSame('Failed on 4', E3, TJSONObject(FRD.PopElement));
+end;
+
+procedure TTestReportDOM.TestStreamToHex;
+begin
+  TestStream(True);
+end;
+
+procedure TTestReportDOM.TestStreamToHex2;
+begin
+  TestStream(False);
+end;
+
+procedure TTestReportDOM.TestStreamEquals1;
+var
+  S: TMemoryStream;
+begin
+  S := TMemoryStream.Create;
+  try
+    AssertEquals('Same stream always equal', True, FRD.StreamsEqual(S, S));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestStreamEquals2;
+var
+  S1, S2: TMemoryStream;
+begin
+  S1 := TMemoryStream.Create;
+  try
+    FillBytes(S1, 255);
+    S2 := TMemoryStream.Create;
+    try
+      FillBytes(S2, 255);
+      AssertEquals('Same content always equal', True, FRD.StreamsEqual(S1, S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestStreamEquals3;
+var
+  S1, S2: TMemoryStream;
+begin
+  S1 := TMemoryStream.Create;
+  try
+    FillBytes(S1, 255);
+    S2 := TMemoryStream.Create;
+    try
+      FillBytes(S2, 254);
+      AssertEquals('Different sizes makes not equal', False, FRD.StreamsEqual(S1, S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestStreamEquals4;
+var
+  S1, S2: TMemoryStream;
+  B: byte;
+begin
+  S1 := TMemoryStream.Create;
+  try
+    FillBytes(S1, 255);
+    AssertEquals(0, S1.Seek(0, soFromBeginning));
+    B := 10;
+    S1.WriteBuffer(B, 1);
+    B := 12;
+    S1.Position := 0;
+    S1.ReadBuffer(B, 1);
+    AssertEquals(10, B);
+    AssertEquals(256, S1.Size);
+    S2 := TMemoryStream.Create;
+    try
+      FillBytes(S2, 255);
+      AssertEquals('Different streams makes not equal', False, FRD.StreamsEqual(S1, S2));
+    finally
+      S2.Free;
+    end;
+  finally
+    S1.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestHexToStream;
+var
+  S: TMemoryStream;
+  SS: TStringStream;
+  H: string;
+begin
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 255);
+    H := FRD.StreamToHex(S);
+    SS := FRD.HexToStringStream(H);
+    try
+      AssertEquals('Size of stream is OK', 256, SS.Size);
+      AssertEquals('HexToStringStream OK', True, FRD.StreamsEqual(S, SS));
+    finally
+      SS.Free;
+    end;
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteInteger1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteInteger('Int', 1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Int" : 1 } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteString1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Str', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Str" : "Aloha" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteString2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Str', '');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Str" : "" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteBoolean1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBoolean('Bool', True);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Bool" : true } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteBoolean2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBoolean('Bool', False);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Bool" : false } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteFloat1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteFloat('Float', 1.23);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', 1.23, FRD.JSON.FindPath('MyElement.Float').AsFloat);
+end;
+
+procedure TTestReportDOM.TestWriteFloat2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteFloat('Float', -1.23);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', -1.23, FRD.JSON.FindPath('MyElement.Float').AsFloat);
+end;
+
+procedure TTestReportDOM.TestWriteFloat3;
+var
+  E: TJSONObject;
+  x: Extended;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  x := 0.0;
+  FRD.WriteFloat('Float', x);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', 0.0, FRD.JSON.FindPath('MyElement.Float').AsFloat);
+end;
+
+procedure TTestReportDOM.TestWriteDateTime1;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Date" : "20080918T000000" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteDateTime2;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18) + EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Date" : "20080918T110355" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteDateTime3;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeTime(11,03,55,123);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Date" : "00000000T110355" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteStream1;
+var
+  E: TJSONObject;
+  S: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 15);
+    FRD.WriteStream('Stream', S);
+    AssertSame('Current element not changed', E, FRD.CurrentElement);
+    AssertEquals('Failed on 1', '{ "MyElement" : { "Stream" : "000102030405060708090A0B0C0D0E0F" } }', FRD.JSON.AsJSON);
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteIntegerDiff1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteIntegerDiff('Int', 1, 0);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Int" : 1 } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteIntegerDiff2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteIntegerDiff('Int', 1, 1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteStringDiff1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteStringDiff('Str', 'Aloha', 'mopa');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Str" : "Aloha" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteStringDiff2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteStringDiff('Str', 'Aloha', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteBooleanDiff1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBooleanDiff('Bool', True, False);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Bool" : true } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteBooleanDiff2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBooleanDiff('Bool', True, True);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteFloatDiff1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteFloatDiff('Float', 1.23, 1.24);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', 1.23, FRD.JSON.FindPath('MyElement.Float').AsFloat);
+end;
+
+procedure TTestReportDOM.TestWriteFloatDiff2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteFloatDiff('Float', 1.23, 1.23);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertNull('Failed on 1', FRD.JSON.FindPath('MyElement.Float'));
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff1;
+var
+  E: TJSONObject;
+  D1, D2: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D1 := EncodeDate(2008, 9, 18);
+  D2 := EncodeDate(2001, 10, 28);
+  FRD.WriteDateTimeDiff('Date', D1, D2);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Date" : "20080918T000000" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff2;
+var
+  E: TJSONObject;
+  D1: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D1 := EncodeDate(2008, 9, 18) + EncodeTime(0,0,0,1);
+  FRD.WriteDateTimeDiff('Date', D1, D1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteDateTimeDiff3;
+var
+  E: TJSONObject;
+  D1: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D1 := EncodeDate(2008, 9, 18);
+  FRD.WriteDateTimeDiff('Date', D1, D1 + EncodeTime(0,0,0,1));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Failed on 1', '{ "MyElement" : { "Date" : "20080918T000000" } }', FRD.JSON.AsJSON);
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff1;
+var
+  E: TJSONObject;
+  S: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 15);
+    FRD.WriteStreamDiff('Stream', S, S);
+    AssertSame('Current element not changed', E, FRD.CurrentElement);
+    AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff2;
+var
+  E: TJSONObject;
+  S, T: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 15);
+    T := TMemoryStream.Create;
+    try
+      FillBytes(T, 15);
+      FRD.WriteStreamDiff('Stream', S, T);
+      AssertSame('Current element not changed', E, FRD.CurrentElement);
+      AssertEquals('Failed on 1', '{ "MyElement" : {} }', FRD.JSON.AsJSON);
+    finally
+      FreeAndNil(T);
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestWriteStreamDiff3;
+var
+  E: TJSONObject;
+  S, T: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 15);
+    T := TMemoryStream.Create;
+    try
+      FillBytes(T, 16);
+      FRD.WriteStreamDiff('Stream', S, T);
+      AssertSame('Current element not changed', E, FRD.CurrentElement);
+      AssertEquals('Failed on 1', '{ "MyElement" : { "Stream" : "000102030405060708090A0B0C0D0E0F" } }', FRD.JSON.AsJSON)
+    finally
+      FreeAndNil(T);
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestReadInteger1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteInteger('Int', 1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading integer property', 1, FRD.ReadInteger('Int', -1));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadInteger2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  AssertEquals('Reading non-existent integer property', -1, FRD.ReadInteger('Int', -1));
+  AssertEquals('Reading non-existent integer property', -2, FRD.ReadInteger('Int', -2));
+end;
+
+procedure TTestReportDOM.TestReadInteger3;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Int', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading wrongly typed integer property', -1, FRD.ReadInteger('Int', -1));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadString1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Str', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading string property', 'Aloha', FRD.ReadString('Str', '(none)'));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadString2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading non-existent string property', '(none)', FRD.ReadString('Str', '(none)'));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadString3;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteInteger('Str', 1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading wrongly typed string property', '(none)', FRD.ReadString('Str', '(none)'));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadDateTime1;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading date property', D, FRD.ReadDateTime('Date', D-1));
+end;
+
+procedure TTestReportDOM.TestReadDateTime2;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18) + EncodeTime(11, 3, 55, 123);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading date property', D, FRD.ReadDateTime('Date', D-1));
+end;
+
+procedure TTestReportDOM.TestReadDateTime3;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeTime(11, 3, 55, 123);
+
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading non-existent time property', D, FRD.ReadDateTime('Date', D));
+end;
+
+procedure TTestReportDOM.TestReadDateTime4;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18);
+  FRD.WriteString('Date', '20080918');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading date-only property', D, FRD.ReadDateTime('Date', D));
+end;
+
+procedure TTestReportDOM.TestReadDateTime5;
+var
+  E: TJSONObject;
+  D: TDateTime;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  D := EncodeDate(2008, 9, 18);
+  FRD.WriteDateTime('Date', D);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading date property', D, FRD.ReadDateTime('Date', D-1));
+end;
+
+procedure TTestReportDOM.TestReadBoolean1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBoolean('Bool', True);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading true boolean property', True, FRD.ReadBoolean('Bool', False));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteBoolean('Bool', False);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading true boolean property', False, FRD.ReadBoolean('Bool', True));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean3;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Bool', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading wrongly typed boolean property', False, FRD.ReadBoolean('Bool', False));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadBoolean4;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading non-existant boolean property', False, FRD.ReadBoolean('Bool', False));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat1;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteFloat('Float', 1.23);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading float property', 1.23, FRD.ReadFloat('Float', 2.34), 0.001);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat2;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteString('Float', 'Aloha');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading wrongly typed float property', 2.34, FRD.ReadFloat('Float', 2.34), 0.001);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat3;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading non existant float property', 2.34, FRD.ReadFloat('Float', 2.34), 0.001);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadFloat4;
+var
+  E: TJSONObject;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  FRD.WriteInteger('Float', 1);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  AssertEquals('Reading integer formatted float property', 1.0, FRD.ReadFloat('Float', 2.34), 0.001);
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+end;
+
+procedure TTestReportDOM.TestReadStream1;
+var
+  E: TJSONObject;
+  S, T: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  S := TMemoryStream.Create;
+  try
+    FillBytes(S, 15);
+    FRD.WriteStream('Stream', S);
+    AssertSame('Current element not changed', E, FRD.CurrentElement);
+    T := TMemoryStream.Create;
+    try
+      AssertEquals('Reading stream data', True, FRD.ReadStream('Stream', T));
+      AssertEquals('Read stream equals written stream', True, FRD.StreamsEqual(S, T));
+    finally
+      T.Free;
+    end;
+  finally
+    FreeAndNil(S);
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream2;
+var
+  E: TJSONObject;
+  T: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+
+  FRD.WriteString('Stream', '');
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  T := TMemoryStream.Create;
+  try
+    AssertEquals('Reading empty stream data', False, FRD.ReadStream('Stream', T));
+    AssertEquals('Read stream is empty', 0, T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestReadStream3;
+var
+  E: TJSONObject;
+  T: TMemoryStream;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+  AssertSame('Current element not changed', E, FRD.CurrentElement);
+  T := TMemoryStream.Create;
+  try
+    AssertEquals('Reading non-existent stream data', False, FRD.ReadStream('Stream', T));
+    AssertEquals('Read stream is empty', 0, T.Size);
+  finally
+    T.Free;
+  end;
+end;
+
+procedure TTestReportDOM.TestALL;
+var
+  E: TJSONObject;
+  rp: TFPReportPage;
+begin
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  E := TJSONObject(FRD.NewElement('MyElement'));
+
+  rp := TFPReportPage.Create(nil);
+  try
+    rp.WriteElement(FRD);
+    {$ifdef verbosedebug}
+    writeln('--------------');
+    Writeln(FRD.JSON.AsJSON);
+    writeln('--------------');
+    {$endif}
+    With FRD.JSON do
+      begin
+      AssertEquals('Failed on 1', '', FindPath('MyElement.Name').Asstring);
+      AssertEquals('Failed on 2,',0.0, FindPath('MyElement.Layout.Top').AsFloat);
+      AssertEquals('Failed on 3,',0.0, FindPath('MyElement.Layout.Left').AsFloat);
+      AssertEquals('Failed on 4,',0.0, FindPath('MyElement.Layout.Height').AsFloat);
+      AssertEquals('Failed on 5,',0.0, FindPath('MyElement.Layout.Width').AsFloat);
+      end;
+  finally
+    rp.Free;
+  end;
+end;
+
+
+
+{ ---------------------------------------------------------------------
+  General routines
+  ---------------------------------------------------------------------}
+
+procedure TReportStreamTester.SetUp;
+begin
+  FRD := TFPReportJSONStreamer.Create(nil);
+end;
+
+procedure TReportStreamTester.TearDown;
+begin
+{$ifdef writejson}
+  writeln(FRD.JSON.FormatJSON);
+{$endif}
+  FreeAndNil(FRD);
+end;
+
+procedure TReportStreamTester.FillBytes(S : TStream; AMax : Byte);
+Var
+  B : Byte;
+begin
+  For B:=0 to AMax do
+    S.WriteBuffer(B,SizeOf(B));
+end;
+
+
+{ ---------------------------------------------------------------------
+  Actual test routines
+  ---------------------------------------------------------------------}
+
+
+
+{ TTestReportFrameDom }
+
+procedure TTestReportFrameDom.Setup;
+begin
+  inherited Setup;
+  FF:=TFPReportframe.Create(Nil);
+  F2:=TFPReportframe.Create(Nil);
+end;
+
+procedure TTestReportFrameDom.TearDown;
+begin
+  FreeAndNil(FF);
+  FreeAndNil(F2);
+  inherited TearDown;
+end;
+
+procedure TTestReportFrameDom.TestWrite;
+var
+  FDoc: TJSONObject;
+begin
+  FillFF;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+
+  FF.WriteElement(FRD, nil);
+  // compare via JSON directly
+  AssertEquals('Failed on 1', 2, FDoc.Get('Width', 1));
+  AssertEquals('Failed on 2', 'psDot', FDoc.Get('Pen', 'psSolid'));
+  AssertEquals('Failed on 3', 'fsRoundedRect', FDoc.Get('Shape', 'fsNone'));
+  AssertEquals('Failed on 4', 23, FDoc.Get('Color', 0));
+  AssertEquals('Failed on 5', Integer([flTop,flBottom]), FDoc.Get('Lines', 0));
+  // compare via streamer interface
+  AssertEquals('Failed on 6', 2, FRD.ReadInteger('Width', 1));
+  AssertEquals('Failed on 7', 'psDot', FRD.ReadString('Pen', 'psSolid'));
+  AssertEquals('Failed on 8', 'fsRoundedRect', FRD.ReadString('Shape', 'fsNone'));
+  AssertEquals('Failed on 9', 23, FRD.ReadInteger('Color', 0));
+  AssertEquals('Failed on 10', Integer([flTop,flBottom]), FRD.ReadInteger('Lines', 0));
+end;
+
+procedure TTestReportFrameDom.TestWriteDiff;
+var
+  FDoc: TJSONObject;
+begin
+  FillFF;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+  FF.WriteElement(FRD, F2);
+  AssertEquals('Failed on 1', 2, FDoc.Get('Width', 0));
+  AssertEquals('Failed on 2', 'psDot', FDoc.Get('Pen', 'psSolid'));
+  AssertEquals('Failed on 3', 'fsRoundedRect', FDoc.Get('Shape', 'fsNone'));
+  AssertEquals('Failed on 4', 23, FDoc.Get('Color', 0));
+  AssertEquals('Failed on 5', Integer([flTop,flBottom]), FDoc.Get('Lines', 0));
+end;
+
+procedure TTestReportFrameDom.TestRead;
+var
+  FDoc: TJSONObject;
+begin
+  FillFF;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+  FF.WriteElement(FRD, nil);
+  F2.ReadElement(FRD);
+
+  AssertEquals('Failed on 1', FF.Width, F2.Width);
+  AssertEquals('Failed on 2', Ord(FF.Pen), Ord(F2.Pen));
+  AssertEquals('Failed on 3', Ord(FF.Color), Ord(F2.Color));
+  AssertEquals('Failed on 4', Ord(FF.Shape), Ord(F2.Shape));
+  AssertEquals('Failed on 5', Integer(FF.Lines), Integer(F2.Lines));
+end;
+
+Procedure FillFrame(FF : TFPReportFrame);
+begin
+  FF.Width:=2;
+  FF.Pen:=psDot;
+  FF.Shape:=fsRoundedRect;
+  FF.Color:=23;
+  FF.Lines:=[flTop,flBottom];
+end;
+
+procedure TTestReportFrameDom.FillFF;
+begin
+  FillFrame(FF);
+end;
+
+{ TTestReportLayoutDom }
+
+Procedure FillLayout(FL : TFPReportLayout);
+begin
+  FL.Top:=1.2;
+  FL.Left:=3.4;
+  FL.Width:=5.6;
+  FL.Height:=7.8;
+end;
+
+procedure TTestReportLayoutDom.FillFL;
+begin
+  FillLayout(FL);
+end;
+
+procedure TTestReportLayoutDom.Setup;
+begin
+  inherited Setup;
+  FL:=TFPReportLayout.Create(Nil);
+  F2:=TFPReportLayout.Create(Nil);
+end;
+
+procedure TTestReportLayoutDom.TearDown;
+begin
+  FreeAndNil(FL);
+  FreeAndNil(F2);
+  inherited TearDown;
+end;
+
+procedure TTestReportLayoutDom.TestWrite;
+var
+  FDoc: TJSONObject;
+begin
+  FillFL;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+  FL.WriteElement(FRD, nil);
+  // compare json directly
+  AssertEquals('Failed on 1', 1.2, FDoc.Get('Top', 0.0));
+  AssertEquals('Failed on 2', 3.4, FDoc.Get('Left', 0.0));
+  AssertEquals('Failed on 3', 5.6, FDoc.Get('Width', 0.0));
+  AssertEquals('Failed en 4', 7.8, FDoc.Get('Height', 0.0));
+  // compare via streamer interface
+  AssertEquals('Failed on 5', 1.2, FRD.ReadFloat('Top', 0.0));
+  AssertEquals('Failed on 6', 3.4, FRD.ReadFloat('Left', 0.0));
+  AssertEquals('Failed on 7', 5.6, FRD.ReadFloat('Width', 0.0));
+  AssertEquals('Failed en 8', 7.8, FRD.ReadFloat('Height', 0.0));
+end;
+
+procedure TTestReportLayoutDom.TestWriteDiff;
+var
+  FDoc: TJSONObject;
+begin
+  FillFL;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+  FL.WriteElement(FRD, F2);
+  AssertEquals('Failed on 1', 1.2, FDoc.Get('Top', 0.0));
+  AssertEquals('Failed on 2', 3.4, FDoc.Get('Left', 0.0));
+  AssertEquals('Failed on 3', 5.6, FDoc.Get('Width', 0.0));
+  AssertEquals('Failed en 4', 7.8, FDoc.Get('Height', 0.0));
+end;
+
+procedure TTestReportLayoutDom.TestRead;
+var
+  FDoc: TJSONObject;
+begin
+  FillFL;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FDoc := FRD.JSON;
+  FL.WriteElement(FRD, nil);
+  F2.ReadElement(FRD);
+  AssertEquals('Failed on 1', FL.Top, F2.Top);
+  AssertEquals('Failed on 2', FL.Left, F2.Left);
+  AssertEquals('Failed on 3', FL.Width, F2.Width);
+  AssertEquals('Failed on 4', FL.Height, F2.Height);
+end;
+
+{ TTestReportElementDOM }
+
+procedure TTestReportElementDOM.FillFE;
+begin
+  FillLayout(FE.Layout);
+  FillFrame(FE.Frame);
+end;
+
+procedure TTestReportElementDOM.Setup;
+begin
+  inherited Setup;
+  FE:=TFPReportElement.Create(Nil);
+  F2:=TFPReportElement.Create(Nil);
+//  FRD.JSON.Add('element');
+end;
+
+procedure TTestReportElementDOM.TearDown;
+begin
+  FreeAndNil(F2);
+  FreeAndNil(FE);
+  inherited TearDown;
+end;
+
+procedure TTestReportElementDOM.TestWrite1;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+
+  E := TJSONObject(FRD.FindChild('Layout'));
+  AssertNotNull('Failed on 1', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 2', 1.2, FRD.ReadFloat('Top', 0.0));
+    AssertEquals('Failed on 3', 3.4, FRD.ReadFloat('Left', 0.0));
+    AssertEquals('Failed on 4', 5.6, FRD.ReadFloat('Width', 0.0));
+    AssertEquals('Failed en 5', 7.8, FRD.ReadFloat('Height', 0.0));
+  finally
+    FRD.PopElement;
+  end;
+
+  E := TJSONObject(FRD.FindChild('Frame'));
+  AssertNotNull('Failed on 6', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 7', 2, FRD.ReadInteger('Width', 1));
+    AssertEquals('Failed on 8', 'psDot', FRD.ReadString('Pen', 'psSolid'));
+    AssertEquals('Failed on 9', 'fsRoundedRect', FRD.ReadString('Shape', 'fsNone'));
+    AssertEquals('Failed on 10', 23, FRD.ReadInteger('Color', 0));
+    AssertEquals('Failed on 11', Integer([flTop,flBottom]), FRD.ReadInteger('Lines', 0));
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementDOM.TestWriteDiff1;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  AssertTrue('Failed on 0.1', FRD is TFPReportJSONStreamer);
+  E := FRD.CurrentElement;
+  FE.WriteElement(FRD, F2);
+  AssertSame('Failed on 0.2', E, FRD.CurrentElement);
+
+  E := TJSONObject(FRD.FindChild('Layout'));
+  AssertNotNull('Failed on 1', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 2', 1.2, FRD.ReadFloat('Top', 0.0));
+    AssertEquals('Failed on 3', 3.4, FRD.ReadFloat('Left', 0.0));
+    AssertEquals('Failed on 4', 5.6, FRD.ReadFloat('Width', 0.0));
+    AssertEquals('Failed en 5', 7.8, FRD.ReadFloat('Height', 0.0));
+  finally
+    FRD.PopElement;
+  end;
+
+  E := TJSONObject(FRD.FindChild('Frame'));
+  AssertNotNull('Failed on 6', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 7', 2, FRD.ReadInteger('Width', 1));
+    AssertEquals('Failed on 8', 'psDot', FRD.ReadString('Pen', 'psSolid'));
+    AssertEquals('Failed on 9', 'fsRoundedRect', FRD.ReadString('Shape', 'fsNone'));
+    AssertEquals('Failed on 10', 23, FRD.ReadInteger('Color', 0));
+    AssertEquals('Failed on 11', Integer([flTop,flBottom]), FRD.ReadInteger('Lines', 0));
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementDOM.TestWriteDiff2;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  E := FRD.CurrentElement;
+  F2.Frame.Assign(FE.Frame);
+  FE.WriteElement(FRD, F2);
+  AssertSame('Failed on 1', E, FRD.CurrentElement);
+
+  E := TJSONObject(FRD.FindChild('Layout'));
+  AssertNotNull('Failed on 2', E); // Layout was saved
+
+  E := TJSONObject(FRD.FindChild('Frame'));
+  AssertNull('Failed on 3', E);  // Frame was not saved
+end;
+
+procedure TTestReportElementDOM.TestRead1;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+  F2.ReadElement(FRD);
+
+  AssertEquals('Failed on 1', True, FE.Layout.Equals(F2.Layout));
+  AssertEquals('Failed on 2', True, FE.Frame.Equals(F2.Frame));
+  AssertEquals('Failed on 3', True, FE.Equals(F2));
+
+  F2.Visible := False;
+  AssertEquals('Failed on 4', False, FE.Equals(F2));
+end;
+
+procedure TTestReportElementDOM.TestRead2;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+
+  F2.Frame.Assign(FE.Frame);
+
+  // Only layout is written
+  FE.WriteElement(FRD, F2);
+  FreeAndNil(F2);
+  F2 := TFPReportElement.Create(Nil);
+  F2.ReadElement(FRD);
+
+  AssertEquals('Failed on 1', True, FE.Layout.Equals(F2.Layout));
+  AssertEquals('Failed on 2', False, FE.Frame.Equals(F2.Frame));
+  AssertEquals('Failed on 3', False, FE.Equals(F2));
+end;
+
+
+{ TTestReportElementWithChildrenDOM }
+
+procedure TTestReportElementWithChildrenDOM.FillFE;
+var
+  E: TFPReportElement;
+begin
+  FillLayout(FE.Layout);
+  FillFrame(FE.Frame);
+
+  // child 1
+  E := TFPReportMemo.Create(FE);
+  E.Name := 'Memo1';
+  E.Visible := True;
+  E.Layout.Left := 1;
+
+  // child 2
+  E := TFPReportMemo.Create(FE);
+  E.Name := 'Memo2';
+  E.Visible := False;
+  E.Layout.Left := 2;
+end;
+
+procedure TTestReportElementWithChildrenDOM.Setup;
+begin
+  inherited Setup;
+  FE := TFPReportElementWithChildren.Create(Nil);
+  FE.Name := 'Component1';
+  F2 := TFPReportElementWithChildren.Create(Nil);
+  F2.Name := 'Component2';
+end;
+
+procedure TTestReportElementWithChildrenDOM.TearDown;
+begin
+  FreeAndNil(F2);
+  FreeAndNil(FE);
+  inherited TearDown;
+end;
+
+procedure TTestReportElementWithChildrenDOM.TestWrite;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+
+  E := TJSONObject(FRD.FindChild('Layout'));
+  AssertNotNull('Failed on 1', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 2', 1.2, FRD.ReadFloat('Top', 0.0));
+    AssertEquals('Failed on 3', 3.4, FRD.ReadFloat('Left', 0.0));
+    AssertEquals('Failed on 4', 5.6, FRD.ReadFloat('Width', 0.0));
+    AssertEquals('Failed en 5', 7.8, FRD.ReadFloat('Height', 0.0));
+  finally
+    FRD.PopElement;
+  end;
+
+  E := TJSONObject(FRD.FindChild('Children'));
+  AssertNotNull('Failed on 6', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 7', 2, FRD.ChildCount);
+    // child 1
+    E := TJSONObject(FRD.GetChild(0));
+    FRD.PushElement(E);
+    try
+      AssertEquals('Failed on 8', True, FRD.ReadBoolean('Visible', False));
+    finally
+      FRD.PopElement;
+    end;
+    // child 2
+    E := TJSONObject(FRD.GetChild(1));
+    FRD.PushElement(E);
+    try
+      AssertEquals('Failed on 9', False, FRD.ReadBoolean('Visible', True));
+    finally
+      FRD.PopElement;
+    end;
+  finally
+    FRD.PopElement;
+  end;
+end;
+
+procedure TTestReportElementWithChildrenDOM.TestRead;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+  F2.ReadElement(FRD);
+
+  AssertEquals('Failed on 1', True, FE.Layout.Equals(F2.Layout));
+  AssertEquals('Failed on 2', True, FE.Frame.Equals(F2.Frame));
+  AssertEquals('Failed on 3', True, FE.Equals(F2));
+
+  F2.Visible := False;
+  AssertEquals('Failed on 4', False, FE.Equals(F2));
+end;
+
+{ TTestReportPageHeader }
+
+procedure TTestReportPageHeader.FillFE;
+var
+  E: TFPReportMemo;
+begin
+  FillLayout(FE.Layout);
+  FillFrame(FE.Frame);
+
+  // child 1
+  E := TFPReportMemo.Create(FE);
+  E.Name := 'Memo1';
+  E.Visible := True;
+  E.Layout.Left := 1;
+
+  // child 2
+  E := TFPReportMemo.Create(FE);
+  E.Name := 'Memo2';
+  E.Visible := False;
+  E.Layout.Left := 2;
+  E.TextAlignment.Horizontal := taCentered;
+  E.TextAlignment.Vertical := tlCenter;
+end;
+
+procedure TTestReportPageHeader.Setup;
+begin
+  inherited Setup;
+  FE := TFPReportPageHeaderBand.Create(Nil);
+  FE.Name := 'Component1';
+  F2 := TFPReportPageHeaderBand.Create(Nil);
+  F2.Name := 'Component2';
+end;
+
+procedure TTestReportPageHeader.TearDown;
+begin
+  FreeAndNil(F2);
+  FreeAndNil(FE);
+  inherited TearDown;
+end;
+
+procedure TTestReportPageHeader.TestWrite;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  AssertTrue('Failed on 0', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+
+  E := TJSONObject(FRD.FindChild('Layout'));
+  AssertNotNull('Failed on 1', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 2', 1.2, FRD.ReadFloat('Top', 0.0));
+    AssertEquals('Failed on 3', 3.4, FRD.ReadFloat('Left', 0.0));
+    AssertEquals('Failed on 4', 5.6, FRD.ReadFloat('Width', 0.0));
+    AssertEquals('Failed en 5', 7.8, FRD.ReadFloat('Height', 0.0));
+  finally
+    FRD.PopElement;
+  end;
+
+  E := TJSONObject(FRD.FindChild('Children'));
+  AssertNotNull('Failed on 6', E);
+  FRD.PushElement(E);
+  try
+    AssertEquals('Failed on 7', 2, FRD.ChildCount);
+    // child 1
+    E := TJSONObject(FRD.GetChild(0));
+    FRD.PushElement(E);
+    try
+      AssertEquals('Failed on 8', True, FRD.ReadBoolean('Visible', False));
+    finally
+      FRD.PopElement;
+    end;
+    // child 2
+    E := TJSONObject(FRD.GetChild(1));
+    FRD.PushElement(E);
+    try
+      AssertEquals('Failed on 9', False, FRD.ReadBoolean('Visible', True));
+    finally
+      FRD.PopElement;
+    end;
+  finally
+    FRD.PopElement;
+  end;
+
+  E := TJSONObject(FRD.FindChild('VisibleOnPage'));
+  AssertNotNull('Failed on 10', E);
+  AssertEquals('Failed on 11', 'vpAll', E.Value);
+end;
+
+procedure TTestReportPageHeader.TestWrite2;
+var
+  E: TJSONObject;
+begin
+  FillFE;
+  FE.VisibleOnPage := vpNotOnFirst;
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+
+  E := TJSONObject(FRD.FindChild('VisibleOnPage'));
+  AssertNotNull('Failed on 2', E);
+  AssertEquals('Failed on 3', 'vpNotOnFirst', E.Value);
+end;
+
+procedure TTestReportPageHeader.TestRead;
+begin
+  FillFE;
+  FE.VisibleOnPage := vpNotOnFirst; // a non-default value
+  AssertTrue('Failed on 1', FRD is TFPReportJSONStreamer);
+  FE.WriteElement(FRD, nil);
+  F2.ReadElement(FRD);
+
+  AssertEquals('Failed on 2', True, FE.Layout.Equals(F2.Layout));
+  AssertEquals('Failed on 3', True, FE.Frame.Equals(F2.Frame));
+  AssertEquals('Failed on 4', True, FE.Equals(F2));
+
+  F2.Visible := False;
+  AssertEquals('Failed on 5', False, FE.Equals(F2));
+end;
+
+
+initialization
+  RegisterTests(
+      [
+        TTestReportDOM,
+        TTestReportFrameDom,
+        TTestReportLayoutDom,
+        TTestReportElementDOM,
+        TTestReportElementWithChildrenDOM,
+        TTestReportPageHeader
+      ]);
+end.
+

+ 111 - 0
packages/fcl-report/test/testfpreport.lpi

@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <LRSInOutputDirectory Value="False"/>
+      </Flags>
+      <SessionStorage Value="InIDEConfig"/>
+      <MainUnit Value="0"/>
+      <Title Value="testfpreport"/>
+    </General>
+    <VersionInfo>
+      <Language Value=""/>
+      <CharSet Value=""/>
+    </VersionInfo>
+    <MacroValues Count="1">
+      <Macro1 Name="fptest" Value="/home/graemeg/devel/fptest"/>
+    </MacroValues>
+    <BuildModes Count="1">
+      <Item1 Name="default" Default="True"/>
+      <SharedMatrixOptions Count="1">
+        <Item1 ID="908531805490" Modes="default" Type="IDEMacro" MacroName="fptest" Value="/home/graemeg/devel/fptest"/>
+      </SharedMatrixOptions>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <IgnoreBinaries Value="False"/>
+      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
+      <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="3">
+      <Item1>
+        <PackageName Value="fclreport"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="FPCUnitConsoleRunner"/>
+      </Item2>
+      <Item3>
+        <PackageName Value="FCL"/>
+      </Item3>
+    </RequiredPackages>
+    <Units Count="5">
+      <Unit0>
+        <Filename Value="testfpreport.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="tcbasereport.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="tcreportstreamer.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+      <Unit3>
+        <Filename Value="tchtmlparser.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit3>
+      <Unit4>
+        <Filename Value="regtests.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit4>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="testfpreport"/>
+    </Target>
+    <SearchPaths>
+      <OtherUnitFiles Value="$(fptest)/src;$(fptest)/3rdparty/epiktimer"/>
+      <UnitOutputDirectory Value="units"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <CStyleOperator Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <Linking>
+      <Debugging>
+        <UseHeaptrc Value="True"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-dfptestX"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="4">
+      <Item1>
+        <Name Value="ECodetoolError"/>
+      </Item1>
+      <Item2>
+        <Name Value="EFOpenError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EStringListError"/>
+      </Item3>
+      <Item4>
+        <Name Value="EReportError"/>
+      </Item4>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 25 - 0
packages/fcl-report/test/testfpreport.lpr

@@ -0,0 +1,25 @@
+program testfpreport;
+
+{$mode objfpc}{$H+}
+
+uses
+  Classes,consoletestrunner,regtests;
+
+type
+  TMyTestRunner = class(TTestRunner)
+  protected
+  // override the protected methods of TTestRunner to customize its behavior
+  end;
+
+var
+  Application: TMyTestRunner;
+
+begin
+  DefaultFormat:=fPlain;
+  DefaultRunAllTests:=True;
+  Application := TMyTestRunner.Create(nil);
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+

+ 12 - 0
packages/fcl-report/todo.txt

@@ -0,0 +1,12 @@
+A collection of TODOs and random ideas
+
+Core engine
+===========
+
+* Subreport
+* Crosstab element
+* No dependency on freetype for windows
+* In ExportReport, first attach export handler to custom elements, 
+  to avoid repeated lookups during run.
+* Scripting support ?
+* reinstate XML read/write ?

+ 1 - 0
packages/fpmake_add.inc

@@ -134,4 +134,5 @@
   add_odata(ADirectory+IncludeTrailingPathDelimiter('odata'));
   add_pastojs(ADirectory+IncludeTrailingPathDelimiter('pastojs'));
   add_libgc(ADirectory+IncludeTrailingPathDelimiter('libgc'));
+  add_fcl_report(ADirectory+IncludeTrailingPathDelimiter('fcl-report'));
   

+ 6 - 0
packages/fpmake_proc.inc

@@ -764,3 +764,9 @@ begin
   with Installer do
 {$include libgc/fpmake.pp}
 end;
+
+procedure add_fcl_report(const ADirectory: string);
+begin
+  with Installer do
+{$include fcl-report/fpmake.pp}
+end;