1 {
2   LazReport PDF export
3 
4  Copyright (C) 2016 alexs alexs75.at.yandex.ru
5 
6  The module is designed to create an image of the report with the exact
7  positioning of objects and subsequent binding to the worksheet
8 
9  This library is free software; you can redistribute it and/or modify it
10  under the terms of the GNU Library General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or (at your
12  option) any later version with the following modification:
13 
14  As a special exception, the copyright holders of this library give you
15  permission to link this library with independent modules to produce an
16  executable, regardless of the license terms of these independent modules,and
17  to copy and distribute the resulting executable under terms of your choice,
18  provided that you also meet, for each linked independent module, the terms
19  and conditions of the license of that module. An independent module is a
20  module which is not derived from or based on this library. If you modify
21  this library, you may extend this exception to your version of the library,
22  but you are not obligated to do so. If you do not wish to do so, delete this
23  exception statement from your version.
24 
25  This program is distributed in the hope that it will be useful, but WITHOUT
26  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
27  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
28  for more details.
29 
30  You should have received a copy of the GNU Library General Public License
31  along with this library; if not, write to the Free Software Foundation,
32  Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.
33 }
34 
35 
36 unit lr_e_fclpdf;
37 
38 {$mode objfpc}{$H+}
39 
40 interface
41 
42 uses
43   Classes, SysUtils,
44   Graphics,
45   LazStringUtils,
46   LR_Class, LR_ChBox, LR_BarC, LR_Shape, LR_RRect, fpPDF, fpTTF;
47 
48 type
49   TExportFonts = class;
50   TlrPdfExportFilter = class;
51 
52   { TExportFontItem }
53 
54   TExportFontItem = class
55   private
56     FFontColor: TColor;
57     FFontName: string;
58     FFontSize: Integer;
59     FFontStyle: TFontStyles;
60     FOwner:TExportFonts;
61     FDefaultFont: boolean;
62     //
63     FPdfFont:integer;
64     FTTFFontInfo: TFPFontCacheItem;
GetBoldnull65     function GetBold: boolean;
GetItalicnull66     function GetItalic: boolean;
67     procedure SetFontSize(AValue: Integer);
68     //
TextWidthnull69     function TextWidth(const AText: utf8string) : single;
TextHeightnull70     function TextHeight(const AText: utf8string) : single;
71   public
72     constructor Create(AOwner:TExportFonts; AFontName:string; AFontStyle: TFontStyles);
73     destructor Destroy; override;
74     procedure Activate;
75     property FontStyle: TFontStyles read FFontStyle;
76     property FontSize:Integer read FFontSize write SetFontSize;
77     property FontColor:TColor read FFontColor write FFontColor;
78     property Bold:boolean read GetBold;
79     property Italic:boolean read GetItalic;
80     property DefaultFont:boolean read FDefaultFont;
81     property FontName:string read FFontName;
82   end;
83 
84   { TExportFonts }
85 
86   TExportFonts = class
87   private
88     //FDefaultFontBold: TExportFontItem;
89     FDefaultFontNormal: TExportFontItem;
90     FOwner:TlrPdfExportFilter;
91     FList:TFPList;
GetCountnull92     function GetCount: integer;
GetItemnull93     function GetItem(Index: integer): TExportFontItem;
94   public
95     constructor Create(AOwner:TlrPdfExportFilter);
96     destructor Destroy; override;
97     procedure Clear;
AddItemnull98     function AddItem(AFontName: string; AFontStyle:TFontStyles = []): TExportFontItem;
FindItemnull99     function FindItem(AFontName: string; AFontStyle:TFontStyles = []):TExportFontItem;
100     property DefaultFontNormal:TExportFontItem read FDefaultFontNormal;
101     //property DefaultFontBold:TExportFontItem read FDefaultFontBold;
102     property Count:integer read GetCount;
103     property Item[Index:integer]:TExportFontItem read GetItem;
104   end;
105 
106   { TlrPdfExportFilter }
107 
108   TlrPdfExportFilter = class(TfrExportFilter)
109   private
110     FPDFDocument: TPDFDocument;
111     FCurSection: TPDFSection;
112     FCurPageNo : integer;
113     FCurPage: TPDFPage;
114     FFontItems:TExportFonts;
115     FCurFont: TExportFontItem;
116     procedure SetupFonts;
117     procedure InitFonts;
118   private
119     InternalGapX:integer;
120     InternalGapY:integer;
121     procedure WriteTextRectJustify(AExportFont: TExportFontItem; X, Y, W, H: TPDFFloat; const Text: string; Trimmed: boolean);
122     procedure WriteTextRect(AExportFont:TExportFontItem; X, Y, W{, H}:TPDFFloat; AText:string; AHAlign:TAlignment; angle: TPDFFloat);
123     procedure DrawRect(X, Y, W, H: TPDFFloat; ABorderColor, AFillColor: TColor;
124       AFrames: TfrFrameBorders; ABorderWidth: TPDFFloat);
125     procedure DrawRectView(AView: TfrView);
126     procedure WriteURL(X, Y, W, H: TPDFFloat; AUrlText:string);
127     procedure DrawLine(X1, Y1, X2, Y2: TPDFFloat; ABorderColor: TColor; ABorderWidth: TPDFFloat);
128     procedure DrawEllipse(X, Y, W, H: TPDFFloat; ABorderColor, AFillColor: TColor;
129       AFrames: TfrFrameBorders; ABorderWidth: TPDFFloat);
130     procedure DrawImage(X, Y, W, H: integer; ABmp:TLazreportBitmap);
131     procedure DrawLRObjectInternal(View:TfrView);
132   private
133     procedure DoMemoView(View:TfrMemoView);
134     procedure DoImageView(View:TfrPictureView);
135     procedure DoLineView(View:TfrLineView);
136     procedure DoCheckBoxView(View:TfrCheckBoxView);
137     procedure DoShapeView(View:TfrShapeView);
138     procedure DoBarCodeView(View:TfrCustomBarCodeView);
139     procedure DoRoundRectView(View:TfrRoundRectView);
140   public
141     constructor Create(AStream: TStream); override;
142     destructor Destroy; override;
143     procedure OnBeginDoc; override;
144     procedure OnEndDoc; override;
145     procedure OnBeginPage; override;
146     procedure OnEndPage; override;
147     procedure OnData(x, y: Integer; View: TfrView); override;
148     procedure OnText({%H-}x, {%H-}y: Integer; const {%H-}Text: String; {%H-}View: TfrView); override;
149     procedure OnExported({%H-}x, {%H-}y: Integer; {%H-}View: TfrView); override;
150   end;
151 
152 implementation
153 uses LazFileUtils, Forms, LR_Utils, LazUTF8, Printers, FPReadBMP, FPReadPNG, FPReadJPEG;
154 
155 const
156   cInchToMM = 25.4;
157 
ConvetUnitsnull158 function ConvetUnits(AUnits:TPDFFloat):TPDFFloat; inline;
159 begin
160   Result := (AUnits * cInchToMM) / gTTFontCache.DPI;
161 end;
162 
ConvetUnits1null163 function ConvetUnits1(AUnits:TPDFFloat):TPDFFloat; inline;
164 begin
165   Result:= AUnits * gTTFontCache.DPI / cInchToMM;
166 end;
167 
ColorToPdfColornull168 function ColorToPdfColor(C:Graphics.TColor):TARGBColor;
169 var
170   A:array [1..4] of byte absolute C;
171 begin
172   if C = clWindow then
173     Result:=clWhite
174   else
175     Result:={A[1] shl 24 +} A[1] shl 16 + A[2] shl 8 + A[3];
176 end;
177 
178 type
179   TfrHackView = class(TfrView);
180 
181 { TExportFonts }
182 
GetCountnull183 function TExportFonts.GetCount: integer;
184 begin
185   Result:=FList.Count;
186 end;
187 
GetItemnull188 function TExportFonts.GetItem(Index: integer): TExportFontItem;
189 begin
190   Result:=TExportFontItem(FList[Index]);
191 end;
192 
193 constructor TExportFonts.Create(AOwner: TlrPdfExportFilter);
194 begin
195   inherited Create;
196   FOwner:=AOwner;
197   FList:=TFPList.Create;
198 end;
199 
200 destructor TExportFonts.Destroy;
201 begin
202   Clear;
203   FreeAndNil(FList);
204   inherited Destroy;
205 end;
206 
207 procedure TExportFonts.Clear;
208 var
209   I: Integer;
210 begin
211   for I:=0 to FList.Count-1 do
212     TExportFontItem(FList[i]).Free;
213   FList.Clear;
214 end;
215 
MakePSNamenull216 function MakePSName(AFontName: string; AFontStyle: TFontStyles):string;
217 begin
218   Result:=AFontName;
219 
220   if Graphics.fsBold in AFontStyle then
221     Result:=Result + '-Bold';
222 
223   if Graphics.fsItalic in AFontStyle then
224     Result:=Result + '-Oblique';
225 
226   if Graphics.fsUnderline in AFontStyle then
227     Result:=Result + '-Underline';
228 
229   if Graphics.fsStrikeOut in AFontStyle then
230     Result:=Result + '-StrikeOut';
231 end;
232 
TExportFonts.AddItemnull233 function TExportFonts.AddItem(AFontName: string; AFontStyle: TFontStyles
234   ): TExportFontItem;
235 var
236   S1, S2, S3, S: String;
237 
238 begin
239   Result:=FindItem(AFontName, AFontStyle);
240   if Assigned(Result) then exit;
241 
242   if Assigned(gTTFontCache.Find(AFontName, Graphics.fsBold in AFontStyle, Graphics.fsItalic in AFontStyle)) then
243   begin
244     Result:=TExportFontItem.Create(Self, AFontName, AFontStyle);
245     S1:=ExtractFileDir(Result.FTTFFontInfo.FileName);
246     S2:=ExtractFileName(Result.FTTFFontInfo.FileName);
247     S3:=AFontName;
248     FOwner.FPDFDocument.FontDirectory:=S1;
249     S:=MakePSName(AFontName, AFontStyle);
250     Result.FPdfFont:=FOwner.FPDFDocument.AddFont(S2, S);
251   end
252   else
253     Result:=FDefaultFontNormal;
254 end;
255 
FindItemnull256 function TExportFonts.FindItem(AFontName: string; AFontStyle: TFontStyles
257   ): TExportFontItem;
258 var
259   K: TExportFontItem;
260   i: Integer;
261 begin
262   Result:=nil;
263 
264   if AFontName = 'default' then
265   begin
266 {    if Graphics.fsBold in AFontStyle then
267       Result:=FDefaultFontBold
268     else}
269       Result:=FDefaultFontNormal;
270   end
271   else
272   begin
273     for i:=0 to FList.Count-1 do
274     begin
275       K:=TExportFontItem(FList[i]);
276       if (K.FontName = AFontName) and (K.FontStyle = AFontStyle) then
277       begin
278         Result:=K;
279         exit;
280       end
281     end;
282   end;
283 end;
284 
285 { TExportFontItem }
286 
TExportFontItem.GetBoldnull287 function TExportFontItem.GetBold: boolean;
288 begin
289   Result:=Graphics.fsBold in FFontStyle;
290 end;
291 
GetItalicnull292 function TExportFontItem.GetItalic: boolean;
293 begin
294   Result:=Graphics.fsItalic in FFontStyle;
295 end;
296 
297 procedure TExportFontItem.SetFontSize(AValue: Integer);
298 begin
299   if AValue = 0 then
300     FFontSize:=10
301   else
302     FFontSize:=AValue;
303 end;
304 
TExportFontItem.TextWidthnull305 function TExportFontItem.TextWidth(const AText: utf8string): single;
306 begin
307   Result:=ConvetUnits(FTTFFontInfo.TextWidth(AText, FFontSize));
308 end;
309 
TextHeightnull310 function TExportFontItem.TextHeight(const AText: utf8string): single;
311 var
312   ADescender: single;
313 begin
314   Result:=FTTFFontInfo.TextHeight(AText, FFontSize, ADescender);
315   Result:=ConvetUnits(Result + ADescender);
316 {  FTH:=ConvetUnits(AExportFont.FTTFFontInfo.TextHeight(AText, AExportFont.FontSize, ADescender));
317   FTH:=FTH + ConvetUnits(ADescender);}
318 end;
319 
320 constructor TExportFontItem.Create(AOwner: TExportFonts; AFontName: string;
321   AFontStyle: TFontStyles);
322 begin
323   inherited Create;
324   FOwner:=AOwner;
325   FOwner.FList.Add(Self);
326   FFontStyle:=AFontStyle;
327   FFontName:=AFontName;
328   FTTFFontInfo:=gTTFontCache.Find(AFontName, Graphics.fsBold in AFontStyle, Graphics.fsItalic in AFontStyle);
329   if not Assigned(FTTFFontInfo) then
330     raise Exception.CreateFmt('fpTTF:in gTTFontCache not found font "%s" info.', [AFontName]);
331 end;
332 
333 destructor TExportFontItem.Destroy;
334 begin
335   inherited Destroy;
336 end;
337 
338 procedure TExportFontItem.Activate;
339 begin
340   FOwner.FOwner.FCurPage.SetFont(FPdfFont, FontSize);
341   FOwner.FOwner.FCurPage.SetColor(ColorToPdfColor(FontColor), false);
342 end;
343 
344 { TlrPdfExportFilter }
345 
346 procedure TlrPdfExportFilter.SetupFonts;
347 //Find default font name
DefFontNamenull348 function DefFontName:string;
349 const
350   DefFontNames : array [1..4] of string =
351   // TODO: Check if Arial is better default choice in windows/linux/mac
352   {$IFDEF MSWINDOWS}
353      ('Arial', 'Liberation Sans', 'FreeSans', 'DejaVu Sans');
354   {$ELSE}
355      ('Liberation Sans', 'Arial', 'FreeSans', 'DejaVu Sans');
356   {$ENDIF}
357 var
358   i: Integer;
359 begin
360   for i:=1 to 4 do
361     if Assigned(gTTFontCache.Find(DefFontNames[i], false, false)) then
362     begin
363       Result:=DefFontNames[i];
364       exit;
365     end;
366   raise Exception.Create('Not found Sans font');
367 end;
368 
369 var
370   i: Integer;
371   sDefFontName:string;
372 begin
373   sDefFontName:=DefFontName;
374   FFontItems.FDefaultFontNormal:=FFontItems.AddItem(sDefFontName, []);
375 end;
376 
377 procedure TlrPdfExportFilter.InitFonts;
378 procedure CreateFontDirList;
379 {$IFDEF WINDOWS}
380 var
381   s: String;
382 {$ENDIF}
383 begin
384   {$IFDEF WINDOWS}
385   s := SHGetFolderPathUTF8(20); // CSIDL_FONTS = 20
386   if s <> '' then
387     gTTFontCache.SearchPath.Add(s);
388   {$ENDIF}
389   {$IFDEF linux}
390   //tested on Fedora 24
391   gTTFontCache.SearchPath.Add('/usr/share/cups/fonts/');
392   gTTFontCache.SearchPath.Add('/usr/share/fonts/');
393   gTTFontCache.SearchPath.Add('/usr/share/wine/fonts/');
394   gTTFontCache.SearchPath.Add('/usr/local/lib/X11/fonts/');
395   gTTFontCache.SearchPath.Add(GetUserDir + '.fonts/');
396   {$ENDIF}
397 
398 end;
399 begin
400   if gTTFontCache.Count = 0 then
401   begin
402     {$IF (FPC_FULLVERSION >= 30101)}
403     gTTFontCache.BuildFontCacheIgnoresErrors:=true;
404     {$ENDIF}
405     {$IFDEF WINDOWS}
406     CreateFontDirList;
407     {$ELSE}
408     gTTFontCache.ReadStandardFonts;
409     {$ENDIF}
410     gTTFontCache.BuildFontCache;
411   end;
412 end;
413 
414 procedure TlrPdfExportFilter.DoMemoView(View: TfrMemoView);
415 var
416   S: String;
417 begin
418   DrawRectView(View);
419 
420   S:=TfrMemoView(View).URLInfo;
421   if LazStartsText('HTTP://', S) or LazStartsText('HTTPS://', S) then
422     WriteURL(View.Left, View.Top, View.Width, View.Height, TfrMemoView(View).URLInfo);
423 
424   //prepare font
425   FCurFont:=FFontItems.AddItem(View.Font.Name, View.Font.Style);
426   if Assigned(FCurFont) then
427   begin
428     FCurFont.FontSize:=View.Font.Size;
429     FCurFont.FontColor:=View.Font.Color;
430     FCurFont.Activate;
431   end;
432 end;
433 
434 procedure TlrPdfExportFilter.DoImageView(View: TfrPictureView);
435 var
436   IDX: Integer;
437 
LoadImagenull438 function LoadImage:boolean;
439 var
440   S: TMemoryStream;
441 begin
442   Result:=false;
443   S:=TMemoryStream.Create;
444   try
445     View.Picture.SaveToStream(S);
446     S.Position:=0;
447     if View.Picture.Graphic is TJPegImage then
448       IDX := FPDFDocument.Images.AddFromStream(S, TFPReaderJPEG, False)
449     else
450     if View.Picture.Graphic is TPortableNetworkGraphic then
451       IDX := FPDFDocument.Images.AddFromStream(S, TFPReaderPNG, False)
452     else
453       IDX := FPDFDocument.Images.AddFromStream(S, TFPReaderBMP, False);
454     Result:=true;
455   finally
456     S.Free;
457   end;
458 end;
459 
460 var
461   fX, fY, fW, fH: TPDFFloat;
462   X, Y, W, H, L: Double;
463   R: Extended;
464 begin
465   DrawRectView(View);
466 
467   if not ((View.Picture.Graphic = nil) or View.Picture.Graphic.Empty) then
468   begin
469     if not LoadImage then exit;
470 
471     if View.Stretched then
472     begin
473       X:=View.Left;
474       Y:=View.Top;
475       W:=View.Width;
476       H:=View.Height;
477 
478       if View.KeepAspect then
479       begin
480         R:=FPDFDocument.Images[IDX].Width / FPDFDocument.Images[IDX].Height;
481         if (W / H) < R then
482         begin
483           L := H;
484           H := W/R;
485           if View.Centered then
486             Y := Y + (L - H) / 2;
487         end
488         else
489         begin
490           L := W;
491           W := H * R;
492           if View.Centered then
493             X := X + (L - W) / 2;
494         end;
495       end;
496     end
497     else
498     if View.Centered then
499     begin
500       if FPDFDocument.Images[IDX].Width < View.dx then
501       begin
502         X:=View.X + (View.dx - FPDFDocument.Images[IDX].Width) div 2;
503         W:=FPDFDocument.Images[IDX].Width;
504       end
505       else
506       begin
507         X:=View.x;
508         W:=View.dx;
509       end;
510 
511         if FPDFDocument.Images[IDX].Height < View.dy then
512         begin
513           Y:=View.y + (View.dy - FPDFDocument.Images[IDX].Height) div 2;
514           H:=FPDFDocument.Images[IDX].Height;
515         end
516         else
517         begin
518           Y:=View.y;
519           H:=View.dy;
520         end;
521     end
522     else
523     begin
524       X:=View.X;
525       Y:=View.Y;
526       W:=FPDFDocument.Images[IDX].Width;
527       H:=FPDFDocument.Images[IDX].Height;
528     end;
529 
530     fX:=ConvetUnits(X);
531     fY:=ConvetUnits(Y);
532     fW:=ConvetUnits(W);
533     fH:=ConvetUnits(H);
534 
535     FCurPage.DrawImage(fX, fY + fH, fW, fH, IDX);  // left-bottom coordinate of image
536   end;
537 end;
538 
539 procedure TlrPdfExportFilter.DoLineView(View: TfrLineView);
540 begin
541   DrawRect(View.Left, View.Top, View.Width, View.Height, View.FrameColor, clNone, View.Frames, View.FrameWidth);
542 end;
543 
544 procedure TlrPdfExportFilter.DoCheckBoxView(View: TfrCheckBoxView);
545 var
546   C: Boolean;
547   GX, GY: Integer;
548 begin
549   DrawRectView(View);
550   C:=View.Checked;
551   if View.Memo.Count > 0 then
552     C:=View.Memo[0] = '1';
553 
554   View.CalcGaps;
555   GX:= TfrHackView(View).InternalGapX * 2; //View.GapX + View.FrameWidth + 2;
556   GY:= TfrHackView(View).InternalGapY * 2; //View.GapY + View.FrameWidth + 2;
557 
558   if C then
559   begin
560     DrawLine(
561       View.X + GX,
562       View.Y + GY,
563       View.X + View.DX - GX * 2,
564       View.Y + View.DY - GY * 2,
565       View.FrameColor,
566       ConvetUnits1(3)
567     );
568 
569     DrawLine(
570       View.X + View.DX - GX * 2,
571       View.Y + GY,
572       View.X + GX * 2,
573       View.Y + View.DY - GY * 2,
574       View.FrameColor,
575       ConvetUnits1(3)
576     );
577   end;
578 end;
579 
580 procedure TlrPdfExportFilter.DoShapeView(View: TfrShapeView);
581 begin
582  case View.ShapeType of
583     frstRectangle:
584       DrawRect(View.X, View.Y, View.DX, View.DY, View.FrameColor, View.FillColor, [frbLeft, frbTop, frbRight, frbBottom], View.FrameWidth);
585     frstRoundRect:
586       DrawLRObjectInternal(View);
587     frstEllipse:
588       DrawEllipse(View.X, View.Y, View.DX, View.DY, View.FrameColor, View.FillColor, View.Frames, View.FrameWidth);
589     frstTriangle:
590       DrawLRObjectInternal(View);
591     frstDiagonal1:
592       DrawLine( View.X, View.Y, View.X + View.DX, View.Y + View.DY, View.FrameColor,View.FrameWidth);
593     frstDiagonal2:
594       DrawLine( View.X + View.DX, View.Y, View.X, View.Y + View.DY, View.FrameColor,View.FrameWidth);
595  end;
596 end;
597 
598 procedure TlrPdfExportFilter.DoBarCodeView(View: TfrCustomBarCodeView);
599 var
600   FBmp: TLazreportBitmap;
601   X, Y: Integer;
602 begin
603   DrawRectView(View);
604   FBmp:=View.GenerateBitmap;
605   try
606     DrawImage(View.X, View.Y, FBmp.Width, FBmp.Height, FBmp);
607   finally
608     FBmp.Free;
609   end;
610 end;
611 
612 procedure TlrPdfExportFilter.DoRoundRectView(View: TfrRoundRectView);
613 begin
614 //  DrawRectView(View);
615   DrawLRObjectInternal(View);
616 end;
617 
618 procedure TlrPdfExportFilter.WriteTextRectJustify(AExportFont: TExportFontItem;
619   X, Y, W, H: TPDFFloat; const Text: string; Trimmed: boolean);
620 var
621   S: String;
622   Arr: TArrUTF8Item;
623   AvailWidth, PxSpc, RxSpc, Extra: TPDFFloat;
624   WordCount, SpcCount, SpcSize, Cini, CEnd, i: Integer;
625   SpaceWidth, FTH: Single;
626 begin
627   //Calc text height
628   FTH:=AExportFont.TextHeight('Wg');
629 
630   X:=ConvetUnits(X);
631   Y:=ConvetUnits(Y);
632   W:=ConvetUnits(W);
633   H:=ConvetUnits(H);
634 
635   AvailWidth:=W;
636   // count words
637   Arr := UTF8CountWords(Text, WordCount, SpcCount, SpcSize);
638   // handle trimmed text
639   S := Text;
640   if (SpcCount>0) then
641   begin
642     Cini := 0;
643     CEnd := Length(Arr)-1;
644     if Trimmed then
645     begin
646       s := UTF8Trim(Text, [u8tKeepStart]);
647       if Arr[CEnd].Space then
648       begin
649         Dec(CEnd);
650         Dec(SpcCount);
651       end;
652     end;
653     AvailWidth := AvailWidth - AExportFont.TextWidth(S);
654   end;
655 
656   // check if long way is needed
657   if (SpcCount>0) and (AvailWidth>0) then
658   begin
659 
660     SpaceWidth := AExportFont.TextWidth(' ');
661     PxSpc := AvailWidth / SpcCount;
662     RxSpc := AvailWidth - PxSpc * SpcCount;
663     if PxSPC=0 then
664     begin
665       PxSPC := 1;
666       RxSpc := 0;
667     end;
668 
669     for i:=CIni to CEnd do
670       if Arr[i].Space then
671       begin
672         X := X + Arr[i].Count * SpaceWidth;
673         if AvailWidth>0 then
674         begin
675           Extra := PxSpc;
676           if RxSpc>0 then
677           begin
678             Extra:=Extra + ConvetUnits1(1);
679             RxSpc:=RxSpc - ConvetUnits1(1);
680           end;
681           X := X + Extra;
682           AvailWidth:=AvailWidth - Extra;
683         end;
684       end
685       else
686       begin
687         s := Copy(Text, Arr[i].Index, Arr[i].Count);
688         FCurPage.WriteText(X, Y + FTH, S);
689         X := X + AExportFont.TextWidth(S)
690       end;
691 
692   end
693   else
694     FCurPage.WriteText(X, Y + FTH, S);
695 
696   SetLength(Arr, 0);
697 end;
698 
699 procedure TlrPdfExportFilter.WriteTextRect(AExportFont: TExportFontItem; X, Y,
700   W: TPDFFloat; AText: string; AHAlign: TAlignment; angle: TPDFFloat);
701 var
702   FTW, FTH: Single;
703   X1: TPDFFloat;
704   Y1, fX, fY, fW: TPDFFloat;
705 begin
706   fX := ConvetUnits(X);
707   fY := ConvetUnits(Y);
708   fW := ConvetUnits(W);
709 
710   //Calc text width
711   FTW:=AExportFont.TextWidth(AText);
712   //Calc text height
713   FTH:=AExportFont.TextHeight(AText);
714 
715    case AHAlign of
716     taLeftJustify:
717       begin
718         Y1:=fY + FTH;
719         X1:=fX;
720       end;
721     taRightJustify:
722       begin
723         Y1:=fY + FTH;
724         X1:=fX + fW - FTW;
725         if X1 < fX then
726           X1:=fX;
727       end;
728     taCenter:
729       begin
730         Y1:=fY + FTH;
731         X1:=fX + fW / 2 - FTW / 2;
732         if X1 < fX then
733           X1:=fX;
734       end;
735   end;
736   FCurPage.WriteText(X1, Y1, AText, angle);
737 end;
738 
739 procedure TlrPdfExportFilter.DrawRect(X, Y, W, H: TPDFFloat; ABorderColor,
740   AFillColor: TColor; AFrames: TfrFrameBorders; ABorderWidth: TPDFFloat);
741 var
742   fX, fY, fW, fH: Extended;
743 begin
744   if (AFillColor = clNone) and (ABorderColor = clNone) then exit;
745 
746   if ABorderColor <> clNone then
747     FCurPage.SetColor(ColorToPdfColor(ABorderColor), true);
748 
749   if (AFillColor <> clNone) then
750     FCurPage.SetColor(ColorToPdfColor(AFillColor), false);
751 
752   fW:= ConvetUnits(W);
753   fH:= ConvetUnits(H);
754   fX:= ConvetUnits(X);
755   fY:= ConvetUnits(Y);
756   ABorderWidth:=ConvetUnits(ABorderWidth);
757 
758   if AFrames = [frbLeft, frbTop, frbRight, frbBottom] then
759     FCurPage.DrawRect(fX, fY + fH, fW, fH, ABorderWidth, (AFillColor <> clNone), (ABorderColor <> clNone))
760   else
761   begin
762     if frbLeft in AFrames then
763       FCurPage.DrawLine(fX, fY, fX, fY + fH, ABorderWidth);
764 
765     if frbTop in AFrames then
766       FCurPage.DrawLine(fX, fY, fX + fW, fY, ABorderWidth);
767 
768     if frbRight in AFrames then
769       FCurPage.DrawLine(fX + fW, fY, fX + fW, fY + fH, ABorderWidth);
770 
771     if frbBottom in AFrames then
772       FCurPage.DrawLine(fX, fY + fH, fX + fW, fY + fH, ABorderWidth);
773   end;
774 end;
775 
776 procedure TlrPdfExportFilter.DrawRectView(AView: TfrView);
777 begin
778   if AView.Frames <> [] then
779     DrawRect(AView.Left, AView.Top, AView.Width, AView.Height, AView.FrameColor, AView.FillColor, AView.Frames, AView.FrameWidth);
780 end;
781 
782 procedure TlrPdfExportFilter.WriteURL(X, Y, W, H: TPDFFloat; AUrlText: string);
783 begin
784   {$IF (FPC_FULLVERSION >= 30101)}
785   X := ConvetUnits(X);
786   Y := ConvetUnits(Y);
787   W := ConvetUnits(W);
788   H := ConvetUnits(H);
789   FCurPage.AddExternalLink(X, Y + H, W, H, AUrlText, false);
790   {$ENDIF}
791 end;
792 
793 procedure TlrPdfExportFilter.DrawLine(X1, Y1, X2, Y2: TPDFFloat;
794   ABorderColor: TColor; ABorderWidth: TPDFFloat);
795 begin
796   if (ABorderColor = clNone) then exit;
797 
798   if ABorderColor <> clNone then
799     FCurPage.SetColor(ColorToPdfColor(ABorderColor), true);
800 
801 
802   FCurPage.DrawLine(
803     ConvetUnits(X1),
804     ConvetUnits(Y1),
805     ConvetUnits(X2),
806     ConvetUnits(Y2),
807     ConvetUnits(ABorderWidth));
808 end;
809 
810 procedure TlrPdfExportFilter.DrawEllipse(X, Y, W, H: TPDFFloat; ABorderColor,
811   AFillColor: TColor; AFrames: TfrFrameBorders; ABorderWidth: TPDFFloat);
812 var
813   fX, fY, fW, fH: Extended;
814 begin
815   if (AFillColor = clNone) and (ABorderColor = clNone) then exit;
816 
817   if ABorderColor <> clNone then
818     FCurPage.SetColor(ColorToPdfColor(ABorderColor), true);
819 
820   if (AFillColor <> clNone) then
821     FCurPage.SetColor(ColorToPdfColor(AFillColor), false);
822 
823   fW:= ConvetUnits(W);
824   fH:= ConvetUnits(H);
825   fX:= ConvetUnits(X);
826   fY:= ConvetUnits(Y);
827   ABorderWidth:=ConvetUnits(ABorderWidth);
828 
829   FCurPage.DrawEllipse(fX, fY + fH, fW, fH, ABorderWidth, (AFillColor <> clNone), (ABorderColor <> clNone))
830 end;
831 
832 procedure TlrPdfExportFilter.DrawImage(X, Y, W, H: integer; ABmp: TLazreportBitmap);
833 var
834   X1, Y1, W1, H1: TPDFFloat;
835   S: TMemoryStream;
836   IDX: Integer;
837 begin
838   begin
839     S:=TMemoryStream.Create;
840     try
841       ABmp.SaveToStream(S);
842       S.Position:=0;
843       IDX := FPDFDocument.Images.AddFromStream(S, TFPReaderBMP, False);
844       X1:=ConvetUnits(X);
845       Y1:=ConvetUnits(Y);
846       W1 := ConvetUnits(W); // FPDFDocument.Images[IDX].Width);
847       H1 := ConvetUnits(H); // FPDFDocument.Images[IDX].Height);
848       FCurPage.DrawImage(X1, Y1 + H1, W1, H1, IDX);  // left-bottom coordinate of image
849     finally
850       S.Free;
851     end;
852   end;
853 end;
854 
855 procedure TlrPdfExportFilter.DrawLRObjectInternal(View: TfrView);
856 var
857   FBmp: TLazReportBitmap;
858   X, Y: Integer;
859 begin
860   X:=View.X;
861   Y:=View.Y;
862   FBmp:=TLazReportBitmap.Create;
863   try
864     FBmp.Width:=View.DX + 1;
865     FBmp.Height:=View.DY + 1;
866     FBmp.Canvas.Brush.Color := clWhite;
867     FBmp.Canvas.Brush.style := bsSolid;
868     FBmp.Canvas.FillRect(0, 0, FBmp.Width, FBmp.Height);
869     View.X:=0;
870     View.Y:=0;
871     View.Draw(FBmp.Canvas);
872     DrawImage(X, Y, FBmp.Width, FBmp.Height, FBmp);
873   finally
874     FBmp.Free;
875   end;
876 end;
877 
878 constructor TlrPdfExportFilter.Create(AStream: TStream);
879 begin
880   inherited Create(AStream);
881   FPDFDocument:=TPDFDocument.Create(nil);
882   FFontItems:=TExportFonts.Create(Self);
883   InitFonts;
884 end;
885 
886 destructor TlrPdfExportFilter.Destroy;
887 begin
888   FreeAndNil(FFontItems);
889   FreeAndNil(FPDFDocument);
890   inherited Destroy;
891 end;
892 
893 procedure TlrPdfExportFilter.OnBeginDoc;
894 begin
895   inherited OnBeginDoc;
896   FCurPageNo:=-1;
897   FPDFDocument.Infos.Title := Application.Title;
898 {  FPDFDocument.Infos.Author := FAuthorPDF;
899   FPDFDocument.Infos.Producer := FProducerPDF;}
900   FPDFDocument.Infos.ApplicationName := ApplicationName;
901   FPDFDocument.Infos.CreationDate := Now;
902 
903   FPDFDocument.Options:=FPDFDocument.Options + [poPageOriginAtTop, poUseRawJPEG, poSubsetFont];
904   FPDFDocument.DefaultOrientation := ppoPortrait;
905 
906   FPDFDocument.StartDocument;
907   FCurSection := FPDFDocument.Sections.AddSection;
908 
909   SetupFonts;
910 end;
911 
912 procedure TlrPdfExportFilter.OnEndDoc;
913 begin
914   inherited OnEndDoc;
915   FPDFDocument.SaveToStream(Stream);
916 end;
917 
918 procedure TlrPdfExportFilter.OnBeginPage;
919 var
920   lrPg: PfrPageInfo;
921 begin
922   inherited OnBeginPage;
923   Inc(FCurPageNo);
924   FCurPage := FPDFDocument.Pages.AddPage;
925   FCurPage.UnitOfMeasure := uomMillimeters; //normal work only whis mm ??
926   FCurSection.AddPage(FCurPage);
927 
928   //setup page size
929   lrPg:=CurReport.EMFPages[FCurPageNo];
930   case lrPg^.pgSize of
931     9:FCurPage.PaperType := ptA4;
932     11:FCurPage.PaperType := ptA5;
933     1,2:FCurPage.PaperType := ptLetter;
934     5:FCurPage.PaperType := ptLegal;
935     7:FCurPage.PaperType := ptExecutive;
936     //:FCurPage.PaperType := ptComm10;
937     37:FCurPage.PaperType := ptMonarch;
938     27:FCurPage.PaperType := ptDL;
939     28:FCurPage.PaperType := ptC5;
940     34:FCurPage.PaperType := ptB5;
941   else
942     FCurPage.PaperType := ptA4;
943   end;
944 
945   if lrPg^.pgOr in [poPortrait, poReversePortrait] then
946     FCurPage.Orientation:=ppoPortrait
947   else //poReverseLandscape, poLandscape,
948     FCurPage.Orientation:=ppoLandscape;
949 end;
950 
951 procedure TlrPdfExportFilter.OnEndPage;
952 begin
953   inherited OnEndPage;
954 end;
955 
956 procedure TlrPdfExportFilter.OnData(x, y: Integer; View: TfrView);
957 begin
958   InternalGapX:=2 + View.GapX;
959   InternalGapY:=2 + View.GapY;
960 
961   if (View is TfrRoundRectView) then
962     DoRoundRectView(TfrRoundRectView(View))
963   else
964   if (View is TfrMemoView) then
965     DoMemoView(TfrMemoView(View))
966   else
967   if (View is TfrPictureView) then
968     DoImageView(TfrPictureView(View))
969   else
970   if (View is TfrLineView) then
971     DoLineView(TfrLineView(View))
972   else
973   if (View is TfrCheckBoxView) then
974     DoCheckBoxView(TfrCheckBoxView(View))
975   else
976   if (View is TfrShapeView) then
977     DoShapeView(TfrShapeView(View))
978   else
979   if (View is TfrCustomBarCodeView) then
980     DoBarCodeView(TfrCustomBarCodeView(View))
981 
982     ;
983 end;
984 
985 procedure TlrPdfExportFilter.OnText(x, y: Integer; const Text: String;
986   View: TfrView);
987 var
988   W: Double;
989 begin
990   if (View is TfrMemoView) and Assigned(FCurFont) then
991   begin
992     if TfrMemoView(View).FirstLine then
993       W:=TfrMemoView(View).Width - TfrMemoView(View).ParagraphGap - InternalGapX * 2
994     else
995       W:=TfrMemoView(View).Width - InternalGapX * 2;
996 
997     if TfrMemoView(View).Justify and not TfrMemoView(View).LastLine then
998       WriteTextRectJustify(FCurFont, X + InternalGapX, Y, W, View.dy, Text, true)
999     else
1000       WriteTextRect(FCurFont, X + InternalGapX, Y, W, Text, TfrMemoView(View).Alignment, TfrMemoView(View).Angle);
1001   end;
1002 end;
1003 
1004 procedure TlrPdfExportFilter.OnExported(x, y: Integer; View: TfrView);
1005 begin
1006 end;
1007 
1008 initialization
1009   frRegisterExportFilter(TlrPdfExportFilter, 'PDF file (*.pdf)', '*.pdf');
1010 end.
1011 
1012