Sign in Join
Wiki copytemporary

FPReport Custom elements: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: FPReport Custom elements

OldNewLine
1+FPReport contains support for four basic reporting elements:
2+
3+- Memo
4+- Shape
5+- Image
6+- Checkbox
7+
8+While these will get you a long way, sometimes something extra is needed.
9+
10+On this page we explain how to create a new element.
11+
12+We'll take the sample TReportPolygon as an example.
13+
14+FPReport by default contains 2 custom reporting elements:
15+
16+- BarCode
17+- QRCode (based on [Nayuki's QR code generator](https://www.nayuki.io/page/qr-code-generator-library))
18+
19+Both elements are developed using the technique explained on this page.
20+
21+## Create a new element class
22+
23+Any printable element must descend from TFPReportElement. It must expose published properties which allow the user to control behaviour of the element.
24+
25+``` pascal
26+TReportPolygon = class(TFPReportElement)
27+Published
28+ Property Corners : Cardinal Read FCorners Write SetCorners;
29+ Property LineWidth : Cardinal Read FLineWidth Write SetCLineWidth;
30+ Property Color : TFPReportColor Read FColor Write FColor;
31+ // In degrees
32+ Property RotateAngle : Double Read FRotateAngle Write FRotateAngle;
33+end;
34+```
35+
36+The element itself must only know how to read/write its properties from/to a report streamer, and how to copy properties to another instance.
37+
38+### Copying properties for cloning
39+
40+Copying properties to another instance is done in the usual fashion, using the **Assign** method:
41+
42+``` pascal
43+procedure TReportPolygon.Assign(Source: TPersistent);
44+
45+Var
46+ P : TReportPolygon;
47+
48+begin
49+ if (Source is TReportPolygon) then
50+ begin
51+ P:=Source as TReportPolygon;
52+ Corners:=P.Corners;
53+ Color:=P.Color;
54+ LineWidth:=P.LineWidth;
55+ RotateAngle:=P.RotateAngle;
56+ end;
57+ inherited Assign(Source);
58+end;
59+```
60+
61+This is important because during layouting, the assign method is used to clone the design elements before layouting them on the page.
62+
63+### Writing properties to a stream
64+
65+When saving a report to file, all elements are written to a stream using a **TFPReportStreamer** class. (unit *fpreportstreamer*)
66+
67+Writing properties to the streamer must be implemented in the **WriteElement** method, or the **DoWriteLocalProperties** method:
68+
69+``` pascal
70+procedure TReportPolygon.DoWriteLocalProperties(AWriter: TFPReportStreamer;
71+ AOriginal: TFPReportElement);
72+
73+Var
74+ P : TReportPolygon;
75+
76+begin
77+ inherited DoWriteLocalProperties(AWriter, AOriginal);
78+ if AOriginal is TReportPolygon then
79+ begin
80+ P:=AOriginal as TReportPolygon;
81+ AWriter.WriteIntegerDiff('Color', Color, P.Color);
82+ AWriter.WriteIntegerDiff('Corners',Corners,P.Color);
83+ AWriter.WriteIntegerDiff('LineWidth',LineWidth,P.LineWidth);
84+ AWriter.WriteFloatDiff('RotateAngle',RotateAngle,P.RotateAngle);
85+ end
86+ else
87+ begin
88+ AWriter.WriteInteger('Color', Color);
89+ AWriter.WriteInteger('Corners',Corners);
90+ AWriter.WriteInteger('LineWidth',LineWidth);
91+ AWriter.WriteFloat('RotateAngle',RotateAngle);
92+ end;
93+end;
94+```
95+
96+The WriteElement can be called during layouting. During layouting, a copy is made of each element, and the propertie values can differ from the values set during design. To minimize the size of the stream, only a diff may be written: **AOriginal** is the element as designed, and element, do be able to write only properties that have changed.
97+
98+### Reading properties from a stream
99+
100+When the report design is read from a stream, every element is read from stream. This happens using the same **TFPReportStreamer** class (unit *fpreportstreamer*) as is used to write the element.
101+
102+The ReadElement method is responsible for reading the element's design from stream:
103+
104+``` pascal
105+procedure TReportPolygon.ReadElement(AReader: TFPReportStreamer);
106+begin
107+ inherited ReadElement(AReader);
108+ Color:= AReader.ReadInteger('Color', clBlack);
109+ Corners:=AReader.ReadInteger('Corners',3);
110+ LineWidth:=AReader.ReadInteger('LineWidth',1);
111+ RotateAngle:=AReader.ReadFloat('RotateAngle',0);
112+end;
113+```
114+
115+## Registering the new element
116+
117+Before the element can be used in a stream or rendered (in PDF, image, HTML or on screen) it must be registered. This is done using the element factory, gElementFactory=
118+
119+``` pascal
120+ gElementFactory.RegisterClass('Polygon',TReportPolygon);
121+```
122+
123+When reading a report design from stream, this tells the streaming engine that whenever it encounters a 'Polygon' element type, it must create an instance of **TReportPolygon**, and read it from stream.
124+
125+The element factory also keeps the various hooks for rendering the element.
126+
127+## Rendering the new element
128+
129+An element by itself does not know how to render: it is the task of a renderer to know this. The renderers know how to render the standard fpreport elements.
130+
131+For all unknown elements, the renderer engine will look in the element factory for a handler to actually render the list. It has already rendered the frame (if any) There are 2 types of render callback:
132+
133+1. Renderer-Specific handler.
134+
135+
136+
137+This render callback is specific to the current renderer. It's parameters are:
138+
139+- aPos: Offset of the position on the page.
140+- aElement: the element to render
141+- aExporter: the renderer which is currently rendering
142+- aDPI : the DPI the renderer is using.
143+
144+``` pascal
145+ TFPReportElementExporterCallBack = Procedure(aPos : TFPReportPoint; aElement : TFPReportElement; AExporter : TFPReportExporter; ADPI: Integer);
146+```
147+
148+1. Fallback Image renderer:
149+
150+
151+
152+This handler must render the element to a TFPCustomImage instance. The image has the expected size.
153+
154+The image will then be placed correctly on the page by the renderer.
155+
156+``` pascal
157+ TFPReportImageRenderCallBack = Procedure(aElement : TFPReportElement; aImage: TFPCustomImage);
158+```
159+
160+It is sufficient to have a fallback renderer, but it can be advantageous to have renderer-specific callbacks.
161+
162+For the HTML renderer, complex graphics can be rendered using the fallback renderer.
163+
164+### Image fallback renderer
165+
166+To render an element on a **TFPCustomImage** descendent, the **TFPImageCanvas** canvas class can be used:
167+
168+``` pascal
169+Procedure RenderPolygonToImage(aElement : TFPReportElement;AImage : TFPCustomImage);
170+
171+Var
172+ C : TFPImageCanvas;
173+ P : TReportPolygon;
174+
175+begin
176+ P:=AElement as TReportPolygon;
177+ C:=TFPImageCanvas.Create(AImage);
178+ try
179+ if (AElement.Frame.BackgroundColor<>fpreport.clNone) then
180+ begin
181+ C.Brush.FPColor:=ColorToRGBTriple(AElement.Frame.BackgroundColor);
182+ C.Brush.Style:=bsSolid;
183+ C.FillRect(0,0,AImage.Width-1,AImage.Height-1);
184+ end;
185+ PaintPolygon(C,Point(0,0),AImage.Width,AImage.Height,P.Corners,P.RotateAngle,P.linewidth,ColorToRGBTriple(P.Color));
186+ finally
187+ C.Free;
188+ end;
189+end;
190+```
191+
192+The actual paint is then done in PaintPolygon:
193+
194+``` pascal
195+Procedure PaintPolygon(Canvas : TFPCustomCanvas; AOffset : TPoint; AWidth,AHeight : Integer; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TFPColor);
196+
197+Var
198+ CX,CY,R,I : Integer;
199+ P : Array of TPoint;
200+ A,Step : Double;
201+
202+begin
203+ Canvas.Pen.FPColor:=AColor;
204+ Canvas.Pen.Width:=aLineWidth;
205+ Canvas.Pen.Style:=psSolid;
206+ if ANumber<3 then
207+ exit;
208+ CX:=AOffset.x+AWidth div 2;
209+ CY:=AOffset.y+AHeight div 2;
210+ if aWidth<aHeight then
211+ R:=AWidth div 2
212+ else
213+ R:=AHeight div 2;
214+ SetLength(P,ANumber);
215+ A:=AStartAngle;
216+ Step:=(2*Pi)/ANumber;
217+ For I:=0 to ANumber-1 do
218+ begin
219+ P[i].X:=CX+Round(R*Cos(a));
220+ P[i].Y:=CY-Round(R*Sin(a));
221+ A:=A+Step;
222+ end;
223+ For I:=0 to ANumber-2 do
224+ Canvas.Line(P[I],P[I+1]);
225+ Canvas.Line(P[ANumber-1],P[0]);
226+ SetLength(P,0);
227+end;
228+```
229+
230+This routine will be more or less verbatim copied for all other renderers.
231+
232+To tell the rendering engin about this routine, it must be registered in the element factory. A good place for this is right after the registration of the new element:
233+
234+``` pascal
235+begin
236+ gElementFactory.RegisterClass('Polygon',TReportPolygon);
237+ // Fallback renderer
238+ gElementFactory.RegisterImageRenderer(TReportPolygon,@RenderPolygonToImage);
239+```
240+
241+### Renderer-specific callback: PDF
242+
243+Converting an element to image and then rendering the image on the page may not always be the most efficient or accurate method.
244+
245+For example, the PDF creator has built in support for rendering polygons. This can be used to substantually speed up the drawing of a polygon when rendering to PDF (and at the same time makes the PDF file much smaller):
246+
247+``` pascal
248+Procedure RenderPolygonInPDF(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
249+
250+Var
251+ PR : TFPReportExportPDF;
252+ PG : TReportPolygon;
253+ C : TPDFCoord;
254+ I,ANumber : Integer;
255+ P : Array of TPDFCoord;
256+ R,A,Step : Double;
257+ APage: TPDFPage;
258+
259+begin
260+ PR:=RE as TFPReportExportPDF;
261+ PG:=E as TReportPolygon;
262+ APage:=PR.CurrentPage;
263+ ANumber:=PG.Corners;
264+ if ANumber<3 then
265+ exit;
266+ C.X:=AOffset.Left+E.RTLayout.Left+E.RTLayout.Width / 2;
267+ C.Y:=AOffset.Top+E.RTLayout.Top+E.RTLayout.Height / 2;
268+ if E.RTLayout.Width<E.RTLayout.Height then
269+ R:=E.RTLayout.Width / 2
270+ else
271+ R:=E.RTLayout.Height / 2;
272+ SetLength(P,ANumber);
273+ A:=PG.RotateAngle;
274+ Step:=(2*Pi)/ANumber;
275+ For I:=0 to ANumber-1 do
276+ begin
277+ P[i].X:=C.X+R*Cos(a);
278+ P[i].Y:=C.Y-R*Sin(a);
279+ A:=A+Step;
280+ end;
281+ APage.SetColor(PG.Color,True);
282+ APage.DrawPolyGon(P,PG.LineWidth);
283+ APage.StrokePath;
284+end;
285+```
286+
287+Registering this callback is again done on the element factory.
288+
289+In this case in addition to the element class and element class, the renderer class must be specified as well.
290+
291+``` pascal
292+ gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportPDF,@RenderPolygonInPDF);
293+```
294+
295+### Renderer-specific callback: LCL
296+
297+For completeness' sake we'll also show how to render in the LCL renderer: this renderer is used in the designer tool to design and preview the report. It is therefor a good idea to always idea to register an LCL renderer.
298+
299+It resembles the FPImage fallback renderer:
300+
301+``` pascal
302+Procedure RenderPolygonInLCL(AOffset : TFPReportPoint; E: TFPReportElement; RE : TFPReportExporter; ADPI : Integer);
303+var
304+ PR : TFPReportExportCanvas;
305+ PG : TReportPolygon;
306+ R : Trect;
307+ rPt : TFPReportPoint;
308+ pt : TPoint;
309+
310+begin
311+ PR:=RE as TFPReportExportCanvas;
312+ PG:=E as TReportPolygon;
313+ rpt.Left:=AOffset.Left+E.RTLayout.Left;
314+ rpt.Top:=AOffset.Top+E.RTLayout.top;
315+ Pt:=PR.CoordToPoint(rpt,0,0);
316+ R.TopLeft:=pt;
317+ R.Right:=R.Left+PR.HmmToPixels(E.RTLayout.Width);
318+ R.Bottom:= R.Top+PR.VmmToPixels(E.RTLayout.Height);
319+ PR.Canvas.Brush.Color:=e.Frame.BackgroundColor;
320+ PR.Canvas.Brush.Style:=bsSolid;
321+ PR.Canvas.FillRect(R);
322+ LCLPaintPolygon(PR.Canvas,R,PG.Corners,PG.RotateAngle,PG.LineWidth,PR.RGBToBGR(PG.Color));
323+end;
324+```
325+
326+The paint procedure is a copy of the one for the image renderer:
327+
328+``` pascal
329+Procedure LCLPaintPolygon(Canvas : TCanvas; ARect : Trect; ANumber : Integer; AStartAngle : Double; ALineWidth : Integer; AColor : TColor);
330+
331+Var
332+ CX,CY,R,I : Integer;
333+ P : Array of TPoint;
334+ A,Step : Double;
335+ AWidth,AHeight : Integer;
336+
337+begin
338+ AWidth:=ARect.Right-ARect.Left+1;
339+ AHeight:=ARect.Bottom-ARect.Top+1;
340+ Canvas.Pen.Color:=AColor;
341+ Canvas.Pen.style:=psSolid;
342+ Canvas.Pen.Width:=aLineWidth;
343+ if ANumber<3 then
344+ exit;
345+ CX:=ARect.Left+AWidth div 2;
346+ CY:=ARect.Top+AHeight div 2;
347+ if aWidth<aHeight then
348+ R:=AWidth div 2
349+ else
350+ R:=AHeight div 2;
351+ SetLength(P,ANumber);
352+ A:=AStartAngle;
353+ Step:=(2*Pi)/ANumber;
354+ For I:=0 to ANumber-1 do
355+ begin
356+ P[i].X:=CX+Round(R*Cos(a));
357+ P[i].Y:=CY-Round(R*Sin(a));
358+ A:=A+Step;
359+ end;
360+ For I:=0 to ANumber-2 do
361+ Canvas.Line(P[I],P[I+1]);
362+ Canvas.Line(P[ANumber-1],P[0]);
363+end;
364+```
365+
366+Again, the render callback must be registered:
367+
368+``` pascal
369+gElementFactory.RegisterElementRenderer(TReportPolygon,TFPReportExportCanvas,@RenderPolygonInLCL);
370+```
371+
372+After this, the rendering in LCL will be done directly, without copying bitmaps.
373+
374+## More info
375+
376+See the **reportpolygon.pas** unit in the demo applications, it contains renderers for the 5 known renderers.
377+
378+---
379+*From the Free Pascal wiki, page "FPReport Custom elements", as it stood on 2020-02-16 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history