1 /// Reporting unit
2 // - this unit is a part of the freeware Synopse framework,
3 // licensed under a MPL/GPL/LGPL tri-license; version 1.18
4 unit mORMotReport;
5 
6 (*
7     This file is part of Synopse framework.
8 
9     Synopse framework. Copyright (C) 2015 Arnaud Bouchez
10       Synopse Informatique - http://synopse.info
11 
12   *** BEGIN LICENSE BLOCK *****
13   Version: MPL 1.1/GPL 2.0/LGPL 2.1
14 
15   The contents of this file are subject to the Mozilla Public License Version
16   1.1 (the "License"); you may not use this file except in compliance with
17   the License. You may obtain a copy of the License at
18   http://www.mozilla.org/MPL
19 
20   Software distributed under the License is distributed on an "AS IS" basis,
21   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
22   for the specific language governing rights and limitations under the License.
23 
24   The Original Code is Synopse framework.
25 
26   The Initial Developer of the Original Code is Angus Johnson.
27 
28   Portions created by the Initial Developer are Copyright (C) 2003
29   the Initial Developer. All Rights Reserved.
30   Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2015
31   Arnaud Bouchez. All Rights Reserved.
32 
33   Contributor(s):
34   - Celery
35   - Leo
36   - Mike Lamusse (mogulza)
37 
38   Alternatively, the contents of this file may be used under the terms of
39   either the GNU General Public License Version 2 or later (the "GPL"), or
40   the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
41   in which case the provisions of the GPL or the LGPL are applicable instead
42   of those above. If you wish to allow use of your version of this file only
43   under the terms of either the GPL or the LGPL, and not to allow others to
44   use your version of this file under the terms of the MPL, indicate your
45   decision by deleting the provisions above and replace them with the notice
46   and other provisions required by the GPL or the LGPL. If you do not delete
47   the provisions above, a recipient may use your version of this file under
48   the terms of any one of the MPL, the GPL or the LGPL.
49 
50   ***** END LICENSE BLOCK *****
51 
52 
53     Initial Notes and Copyright:
54    ******************************
55 
56  Component Name:  TPages
57  Module:          Pages
58  Description:     Report writer and previewer
59  Version:         1.6
60  Date:            25-MAY-2004 (initial version)
61  Target:          Win32, Delphi 3 - Delphi 7
62  Author:          Angus Johnson, http://www.angusj.com
63  Copyright        � 2003 Angus Johnson
64 
65  Notes:
66    * TGDIPages is designed as a simple lightweight report writer. Reports are
67      created in code, they are not banded, nor are they directly linked to
68      TDatasets. If you're looking for a dataset aware report writer then
69      TGDIPages is not for you. TGDIPages is a visual component based on a
70      TScrollbox, though it isn't necessary to view reports prior to printing.
71 
72    * Main features include:
73      + Text can be output either wrapped between page margins, in columns
74        or at specified offsets.
75      + Multiple alignment options -
76          > left, right and justified in non-columned text
77          > left, right and currency in columned text
78      + Tabs to Assigned tabstops
79      + Multi-line page headers, footers and column headers
80      + Multiple fonts can be used.
81      + Angled text output
82      + Single, line & half, and double line spacing
83      + Methods for printing bitmaps, metafiles, lines, boxes and arrows
84      + Page numbering can be redefined
85      + Text output 'groups' prevent blocks of text spanning across pages
86      + Designed around a TScrollbox descendant preview window with:
87        mouse click zoom control; keyboard handling of lineup, linedown,
88        pageup and pagedown srolling; mouse wheel scrolling.
89 
90    * In order to get the best print quality, TGDIPages uses the selected
91      printer driver's resolution to prepare reports.
92      If a report will be printed to a different printer (eg by using a
93      PrintDialog), it's preferable to change to that printer object BEFORE
94      preparing the report. Otherwise, the report will be stretch down to the
95      printer canvas resulting in a slight degradation in print quality.
96 
97 
98   Enhanced for the freeware Synopse framework:
99  **********************************************
100 
101   - Windows XP, Vista and Seven compatibility adding
102   - fix printing metafiles and page groups on some printer (with bad drivers)
103   - optionnaly use antialiaised drawing (via SynGdiPlus unit)
104   - popup menu creation, with zoom, print or copy features (and custom entries)
105   - direct PDF export (if a PDF printer is installed, or via SynPdf unit)
106   - direct page export to clipboard as text
107   - optional Black and White / Duplex mode (with out TPrinterNew custom class)
108   - new useful methods for easy text adding (especially column definition)
109   - new fast double buffering drawing
110   - full Unicode text process (even before Delphi 2009)
111   - speed up and various bug fixes to work with Delphi 5 up to XE3
112 
113   Modifications � 2009-2015 Arnaud Bouchez
114 
115   Version 1.4 - February 8, 2010
116   - whole Synopse SQLite3 database framework released under the GNU Lesser
117     General Public License version 3, instead of generic "Public Domain"
118 
119   Version 1.6
120   - new version, using our SynGdiPlus unit: if the GDI+ is available,
121     it will use it to render the page using its AntiAliased engine;
122     under Windows 98 or 2000, no antialiasing will occur, but the program
123     will still run (since our SynGdiPlus unit use dynamic linking of the
124     gdiplus.dll library);
125     if only GDI+ 1.0 is available (i.e. with a Windows XP without any Office
126     2003/2007 installed) a pure Delphi version of GDI+ drawing is used, which
127     should not be able to convert 100% of page content, but should work on
128     most cases
129 
130   Version 1.8
131   - some fixes for compilation under Delphi 2009/2010
132 
133   Version 1.9
134   - new AppendRichEdit method to draw RichEdit content
135   - new WordWrapLeftCols property used to optionaly word wrap caLeft columns
136     into multiple lines, i.e. if the text is wider than the column width, its
137     content is wrapped to the next line (set to false by default) - this
138     also will handle #13/#10 in column text as a "go to next line" command
139 
140   Version 1.9.2
141   - fix font color issue in header and footers
142   - safety additional code to avoid any division per 0 exception
143 
144   Version 1.11
145   - fixed issue in TGDIPages.AppendRichEdit - see user feedback from
146     http://synopse.info/forum/viewtopic.php?pid=671#p671
147   - added Author, Subject and Keywords optional parameters to TGDIPages.ExportPDF
148 
149   Version 1.12
150   - fixed one issue (in SynGdiPlus) for displaying bitmaps in anti-aliased mode
151     and displaying underlined or stroken out text - new ForceInternalAntiAliased
152     method (true by default) using SynGdiPlus instead of GDI+ 1.1 native conversion
153   - OnStringToUnicodeEvent is now called for all text, whatever the alignment is
154   - new property BiDiMode, for formatting the text in right to left order
155   - added new DrawBMP overloaded method to add some bitmap as a (centered)
156     paragraph, with some optional legend - bitmaps are now cached and reused
157     in the exported PDF, if the same one is drawn multiple time in the document
158   - added new AddBookMark, AddOutline and AddLink methods (working also with
159     the PDF export using SynPdf) :)
160   - live navigation via links in the preview screen, and via the new 'Bookmarks'
161     popup menu entry
162   - additional ExportPDF* properties used during PDF export
163   - introducing the new TRenderPages class, for high-quality document rendering
164     (used e.g. within SynProject for document preview and PDF generation,
165     with basic understanding of the rtf format)
166 
167   Version 1.15
168   - fixed an endless loop in TGDIPages.DrawTextAcrossCols when wrapping text
169   - fixed an issue in TGDIPages.DrawTextAcrossCols when test is exported to pdf
170     (wrong clipping region set)
171   - if TGDIPages.WordWrapLeftCols=TRUE, won't wrap column headers
172 
173   Version 1.16
174   - includes new TSynAnsiConvert classes for handling Ansi charsets
175   - some minor fixes (e.g. preview landscape or keys for popup menu)
176   - fix issue in TGDIPages.AppendRichEdit() when called on a blank page
177   - enhanced the print preview screen with a left-sided button bar
178   - new TGdiPages.RenderGraphic method (accepting both TBitmap and TMetaFile)
179 
180   Version 1.17
181   - now whole text process is UNICODE-ready, even on pre-Delphi-2009 versions
182   - now implements font fall-back in internal Anti-Aliaised drawing,
183     if the new ForceInternalAntiAliasedFontFallBack property is set to TRUE
184 
185   Version 1.18
186   - renamed SQLite3Pages.pas to mORMotReport.pas
187   - TGdiPages now handles several page layouts per report - see new overloaded
188     TGDIPages.NewPageLayout() methods and also Orientation property which now
189     allows several page orientations per report - feature request [204b698b3d]
190   - now internal page content (TMetaFile) is compressed using our SynLZ
191     algorithm: we were able to generate reports with more than 20,000 pages!
192   - added optional EndOfPagePositions parameter to TGDIPages.AppendRichEdit()
193   - speed up and memory resource decrease for pdf export of huge reports
194   - fixed issue about disabled Zoom menu entry if no Outline is defined
195   - fixed unexpected exception with TGDIPages.DrawText() and huge string
196   - proper function TGDIPages.GetLineHeight() computation - from kln feedback
197   - added ExportPDFBackground and ExportPDFGeneratePDF15File properties
198   - added ExportPDFEncryptionLevel/User/OwnerPassword/Permissions properties to
199     optionally export report as 40 bit or 128 bit encrypted pdf
200   - added setter method for ZoomStatus property (during preview) - [dd656b470b]
201   - added TGDIPages.ExportPDFStream() method - to be used e.g. on servers
202   - fixed [cfdc644038] about truncated parenthesis in pdf export for caCurrency
203   - fixed [e7ffb69131] about TGDIPages.DrawGraphic() when the TGraphic is Empty
204   - allow preview as a blank colored component at design time (thanks to Celery)
205   - added VisibleButtons optional parameter to TGDIPages.ShowPreviewForm method
206     as requested by [4d64a52675]
207   - added withNewLine optional parameter to DrawText*() methods so that you
208     may be able to append some text without creating a new paragraph - from a
209     proposal patch by Mike Lamusse (mogulza): thanks for sharing!
210 
211 *)
212 
213 interface
214 
215 {.$define MOUSE_CLICK_PERFORM_ZOOM} // old not user-friendly behavior
216 {.$define RENDERPAGES} // TRenderBox and TRenderPages are not yet finished
217 
218 {$define GDIPLUSDRAW}
219 // optionaly (if ForceNoAntiAliased=false) use GDI+ to draw for antialiasing:
220 // slower but smoother (need the GDI+ library, best with version 1.1)
221 
222 {.$define USEPDFPRINTER}
223 // do not use the Synopse PDF engine, in Delphi code, but a PDF virtual printer
224 
225 {.$define PRINTERNEW}
226 // if our custom Printer.pas unit is installed, use TPrinterNew class instead
227 // of TPrinter to allow Black&White and Duplex printing
228 // -> disabled by default, should be enabled globaly from the Project Options
229 
230 {$ifndef ENHANCEDRTL}
231   {$undef PRINTERNEW}
232   // Black&White and Duplex printing are only available with our Enhanced RTL
233 {$endif}
234 
235 {$I Synopse.inc} // define HASINLINE USETYPEINFO CPU32 CPU64 OWNNORMTOUPPER
236 
237 uses
238   SynCommons, SynLZ,
239 {$ifndef USEPDFPRINTER}
240   SynPdf,
241 {$endif}
242   Windows, Messages, SysUtils, Classes, Contnrs,
243 {$ifdef GDIPLUSDRAW}
244   SynGdiPlus,
245 {$endif}
246   Graphics, Controls, Dialogs, Forms, StdCtrls,
247   ExtCtrls, WinSpool, Printers, Menus, ShellAPI, RichEdit;
248 
249 const
250   MAXCOLS = 20;
251   MAXTABS = 20;
252 
253   {{ this constant can be used to be replaced by the page number in
254    the middle of any text }
255   PAGENUMBER = '<<pagenumber>>';
256 
257 type
258 
259   /// text paragraph alignment
260   TTextAlign = (taLeft,taRight,taCenter,taJustified);
261 
262   /// text column alignment
263   TColAlign = (caLeft,caRight,caCenter, caCurrency);
264 
265   /// text line spacing
266   TLineSpacing = (lsSingle, lsOneAndHalf, lsDouble);
267 
268   /// available zoom mode
269   // - zsPercent is used with a zoom percentage (e.g. 100% or 50%)
270   // - zsPageFit fits the page to the report
271   // - zsPageWidth zooms the page to fit the report width on screen
272   TZoomStatus = (zsPercent, zsPageFit, zsPageWidth);
273 
274   /// Event triggered when a new page is added
275   TNewPageEvent = procedure(Sender: TObject; PageNumber: integer) of object;
276 
277   /// Event triggered when the Zoom was changed
278   TZoomChangedEvent = procedure(Sender: TObject;
279     Zoom: integer; ZoomStatus: TZoomStatus) of object;
280 
281   /// Event triggered to allow custom unicode character display on the screen
282   // - called for all text, whatever the alignment is
283   // - Text content can be modified by this event handler to customize
284   // some characters (e.g. '>=' can be converted to the one Unicode glyph)
onstnull285   TOnStringToUnicodeEvent = function(const Text: SynUnicode): SynUnicode of object;
286 
287   /// available known paper size for NewPageLayout() method
288   TGdiPagePaperSize = (
289     psA4, psA5, psA3, psLetter, psLegal);
290 
291   TGDIPages = class;
292 
293   /// a report layout state, as used by SaveLayout/RestoreSavedLayout methods
294   TSavedState = record
295     FontName: string;
296     FontColor: integer;
297     Flags: integer;
298     LeftMargin: integer;
299     RightMargin: integer;
300     BiDiMode: TBiDiMode;
301   end;
302 
303   /// internal format of the header or footer text
304   THeaderFooter = class
305   public
306     Text: SynUnicode;
307     State: TSavedState;
308     /// initialize the header or footer parameters with current report state
309     constructor Create(Report: TGDIPages; doubleline: boolean;
310       const aText: SynUnicode=''; IsText: boolean=false);
311   end;
312 
313   /// internal format of a text column
314   TColRec = record
315     ColLeft, ColRight: integer;
316     ColAlign: TColAlign;
317     ColBold: boolean;
318   end;
319 
320   TPopupMenuClass = class of TPopupMenu;
321 
322   /// hack the TPaintBox to allow custom background erase
323   TPagePaintBox = class(TPaintBox)
324   private
325     procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
326   end;
327 
328   /// internal structure used to store bookmarks or links
329   TGDIPagereference = class
330   public
331     /// the associated page number (starting at 1)
332     Page: Integer;
333     /// graphical coordinates of the hot zone
334     // - for bookmarks, Top is the Y position
335     // - for links, the TRect will describe the hot region
336     // - for Outline, Top is the Y position and Bottom the outline tree level
337     Rect: TRect;
338     /// coordinates on screen of the hot zone
339     Preview: TRect;
340     /// initialize the structure with the current page
341     constructor Create(PageNumber: integer; Left, Top, Right, Bottom: integer);
342     /// compute the coordinates on screen into Preview
343     procedure ToPreview(Pages: TGDIPages);
344   end;
345 
346   /// contains one page
347   TGDIPageContent = object
348     /// SynLZ-compressed content of the page
349     MetaFileCompressed: RawByteString;
350     /// text equivalent of the page
351     Text: string;
352     /// the physical page size
353     SizePx: TPoint;
354     /// margin of the page
355     MarginPx: TRect;
356     /// non printable offset of the page
357     OffsetPx: TPoint;
358   end;
359 
360   /// used to store all pages of the report
361   TGDIPageContentDynArray = array of TGDIPageContent;
362 
363   /// the available menu items
364   TGdiPagePreviewButton = (
365     rNone, rNextPage, rPreviousPage, rGotoPage, rZoom, rBookmarks,
366     rPageAsText, rPrint, rExportPDF, rClose);
367 
368   /// set of menu items
369   TGdiPagePreviewButtons = set of TGdiPagePreviewButton;
370 
371   /// Report class for generating documents from code
372   // - data is drawn in memory, they displayed or printed as desired
373   // - allow preview and printing, and direct pdf export
374   // - handle bookmark, outlines and links inside the document
375   // - page coordinates are in mm's
376   TGDIPages = class(TScrollBox)
377   protected
378     fPreviewSurface: TPagePaintbox;
379     fCanvas: TMetafileCanvas;
380     fCanvasText: string;
381     fBeforeGroupText: string;
382     fGroupPage: TMetafile;
383     fPages: TGDIPageContentDynArray;
384     fHeaderLines: TObjectList;
385     fFooterLines: TObjectList;
386     fColumns: array of TColRec;
387     fColumnHeaderList: array of record
388       headers: TSynUnicodeDynArray;
389       flags: integer;
390     end;
391 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
392     fZoomTimer: TTimer;
393 {$endif}
394     fPtrHdl: THandle;
395 
396     fTabCount: integer;
397     fCurrentPrinter: string;
398     fOrientation: TPrinterOrientation;
399     fDefaultLineWidth: integer;        //drawing line width (boxes etc)
400     fVirtualPageNum: integer;
401     fCurrPreviewPage: integer;
402     fZoomIn: boolean;
403     fLineHeight: integer;              //Text line height
404     fLineSpacing: TLineSpacing;
405     fCurrentYPos: integer;
406     fCurrentTextTop, fCurrentTextPage: integer;
407     fHeaderHeight: integer;
408     fHangIndent: integer;
409     fAlign: TTextAlign;
410     fBiDiMode: TBiDiMode;
411     fPageMarginsPx: TRect;
412     fHasPrinterInstalled: boolean;
413 {$ifdef USEPDFPRINTER}
414     fHasPDFPrinterInstalled: boolean;
415     fPDFPrinterIndex: integer;
416 {$else}
417     fForceJPEGCompression: Integer;
418     fExportPDFApplication: string;
419     fExportPDFAuthor: string;
420     fExportPDFSubject: string;
421     fExportPDFKeywords: string;
422     fExportPDFEmbeddedTTF: boolean;
423     fExportPDFA1: boolean;
424     fExportPDFBackground: TGraphic;
425     {$ifndef NO_USE_UNISCRIBE}
426     fExportPDFUseUniscribe: boolean;
427     {$endif}
428     fExportPDFUseFontFallBack: boolean;
429     fExportPDFFontFallBackName: string;
430     fExportPDFEncryptionLevel: TPdfEncryptionLevel;
431     fExportPDFEncryptionUserPassword: string;
432     fExportPDFEncryptionOwnerPassword: string;
433     fExportPDFEncryptionPermissions: TPdfEncryptionPermissions;
434     fExportPDFGeneratePDF15File: boolean;
435 {$endif}
436     fPrinterPxPerInch: TPoint;
437     fPhysicalSizePx: TPoint;           //size of page in printer pixels
438     fPhysicalOffsetPx: TPoint;         //size of non-printing margins in pixels
439     fCustomPxPerInch: TPoint;
440     fCustomPageSize: TPoint;
441     fCustomNonPrintableOffset: TPoint;
442     fCustomPageMargins: TRect;
443     fZoom: integer;
444     fZoomStatus: TZoomStatus;
445     fNegsToParenthesesInCurrCols: boolean;
446     fWordWrapLeftCols: boolean;
447     fUseOutlines: boolean;
448     fForceScreenResolution: boolean;
449     fHeaderDone: boolean;
450     fFooterHeight: integer;
451     fFooterGap: integer;
452     fInHeaderOrFooter: boolean;
453     fColumnHeaderPrinted: boolean;
454     fColumnHeaderPrintedAtLeastOnce: boolean;
455     fDrawTextAcrossColsDrawingHeader: boolean;
456 
457     fColumnHeaderInGroup: boolean;
458     fColumnsUsedInGroup: boolean;
459     fGroupVerticalSpace: integer;
460     fGroupVerticalPos: integer;
461 
462     fZoomChangedEvent: TZoomChangedEvent;
463     fPreviewPageChangedEvent: TNotifyEvent;
464     fStartNewPage: TNewPageEvent;
465     fStartPageHeader: TNotifyEvent;
466     fEndPageHeader: TNotifyEvent;
467     fStartPageFooter: TNotifyEvent;
468     fEndPageFooter: TNotifyEvent;
469     fStartColumnHeader: TNotifyEvent;
470     fEndColumnHeader: TNotifyEvent;
471 
472     fSavedCount: integer;
473     fSaved: array of TSavedState;
474 
475     fTab: array of integer;
476     fColumnsWithBottomGrayLine: boolean;
477     fColumnsRowLineHeight: integer;
478     fOnDocumentProducedEvent: TNotifyEvent;
479     PageRightButton, PageLeftButton: TPoint;
480     fPagesToFooterText: string; // not SynUnicode, since calls format()
481     fPagesToFooterAt: TPoint;
482     fPagesToFooterState: TSavedState;
483     fMetaFileForPage: TMetaFile;
484     fCurrentMetaFile: TMetaFile;
485 
486     procedure GetPrinterParams;
487     procedure SetAnyCustomPagePx;
488     function  GetPaperSize: TSize;
489     procedure FlushPageContent;
490     function  PrinterPxToScreenPxX(PrinterPx: integer): integer;
491     function  PrinterPxToScreenPxY(PrinterPx: integer): integer;
492     procedure ResizeAndCenterPaintbox;
493     function GetMetaFileForPage(PageIndex: integer): TMetaFile;
494     procedure SetMetaFileForPage(PageIndex: integer; MetaFile: TMetaFile);
495 
496     function  GetOrientation: TPrinterOrientation;
497     procedure SetOrientation(orientation: TPrinterOrientation);
498     procedure SetTextAlign(Value: TTextAlign);
499     procedure SetPage(NewPreviewPage: integer);
500     function  GetPageCount: integer;
501     function  GetLineHeight: integer;
502     function  GetLineHeightMm: integer;
503     procedure CheckYPos;               //ie: if not vertical room force new page
504     function  GetYPos: integer;
505     procedure SetYPos(YPos: integer);
506     procedure NewPageInternal; virtual;
507     function  CreateMetaFile(aWidth, aHeight: integer): TMetaFile;
508     function  CreateMetafileCanvas(Page: TMetafile): TMetafileCanvas;
509     procedure UpdateMetafileCanvasFont(aCanvas: TMetafileCanvas);
510     function  TextFormatsToFlags: integer;
511     procedure SetFontWithFlags(flags: integer);
512     function  GetPageMargins: TRect;
513     procedure SetPageMargins(Rect: TRect);
514 
515     procedure DoHeader;
516     procedure DoFooter;
517     procedure DoHeaderFooterInternal(Lines: TObjectList);
518     procedure CalcFooterGap;
519 
520     function  GetColumnCount: integer;
521     function  GetColumnRec(col: integer): TColRec;
522     procedure PrintColumnHeaders;
523 
524     procedure SetZoom(zoom: integer);
525     procedure SetZoomStatus(aZoomStatus: TZoomStatus);
526     procedure ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
527     procedure ZoomTimer(Sender: TObject);
528 
529     procedure LineInternal(start,finish: integer; DoubleLine: boolean);
530     procedure PrintFormattedLine(s: SynUnicode; flags: integer;
531       const aBookmark: string=''; const aLink: string=''; withNewLine: boolean=true);
532     procedure LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean=true);
533     procedure RightOrCenterWrap(const s: SynUnicode);
534     procedure GetTextLimitsPx(var LeftOffset, RightOffset: integer);
535     procedure HandleTabsAndPrint(const leftstring: SynUnicode;
536       var rightstring: SynUnicode; leftOffset, rightOffset: integer);
537     procedure PreviewPaint(Sender: TObject);
538     procedure PreviewMouseDown(Sender: TObject; Button: TMouseButton;
539       Shift: TShiftState; X, Y: Integer);
540     procedure PreviewMouseUp(Sender: TObject; Button: TMouseButton;
541       Shift: TShiftState; X, Y: Integer);
542     procedure PreviewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
543     function GetLeftMargin: integer;
544     procedure SetLeftMargin(const Value: integer);
545     function GetRightMarginPos: integer;
546     function GetSavedState: TSavedState;
547     procedure SetSavedState(const SavedState: TSavedState);
548     /// can be used internaly (for instance by fPagesToFooterState)
549     property SavedState: TSavedState read GetSavedState write SetSavedState;
550   protected
551     fMousePos: TPoint;
552 {$ifndef MOUSE_CLICK_PERFORM_ZOOM}
553     fButtonDown, fButtonDownScroll: TPoint;
554 {$endif}
555     /// Strings[] are the bookmark names, and Objects[] are TGDIPagereference
556     // to get the Y position
557     fBookmarks: TStringList;
558     /// Strings[] are the bookmark names, and Objects[] are TGDIPagereference to
559     // get the hot region
560     fLinks: TStringList;
561     fLinksCurrent: integer;
562     /// Strings[] are the outline titles, and Objects[] are TGDIPagereference
563     // to get the Y position of the destination
564     fOutline: TStringList;
565     fInternalUnicodeString: SynUnicode;
566     fForcedLeftOffset : integer;
567     PreviewForm: TForm;
568     PreviewButtons: array of TButton;
569     PreviewPageCountLabel: TLabel;
570     procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
571     procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
572     procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
573     procedure KeyDown(var Key: Word; Shift: TShiftState); override;
574     procedure CreateWnd; override;
575     procedure Resize; override;
576     procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
577     procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
578     {$IFNDEF VER100}
579     function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
580       MousePos: TPoint): Boolean; override; //no mousewheel support in Delphi 3
581     {$ENDIF}
582     procedure PopupMenuPopup(Sender: TObject);
583     procedure CheckHeaderDone; virtual;
584     // warning: PW buffer is overwritten at the next method call
585     procedure InternalUnicodeString(const s: SynUnicode;
586       var PW: PWideChar; var PWLen: integer; size: PSize);
587   public
588     /// Event triggered when the ReportPopupMenu is displayed
589     // - default handling (i.e. leave this field nil) is to add Page naviguation
590     // - you can override this method for adding items to the ReportPopupMenu
591     OnPopupMenuPopup: TNotifyEvent;
592     /// Event triggered when a ReportPopupMenu item is selected
593     // - default handling (i.e. leave this field nil) is for Page navigation
594     // - you can override this method for handling additionnal items to the menu
595     // - the Tag component of the custom TMenuItem should be 0 or greater than
596     // Report pages count: use 1000 as a start for custom TMenuItem.Tag values
597     OnPopupMenuClick: TNotifyEvent;
598     /// user can customize this class to create an advanced popup menu instance
599     PopupMenuClass: TPopupMenuClass;
600     /// the title of the report
601     // - used for the preview caption form
602     // - used for the printing document name
603     Caption: string;
604     /// if true, the PrintPages() method will use a temporary bitmap for printing
605     // - some printer device drivers have problems with printing metafiles
606     // which contains other metafiles; should have been fixed
607     // - not useful, since slows the printing a lot and makes huge memory usage
608     ForcePrintAsBitmap: boolean;
609     /// if true the preview will not use GDI+ library to draw anti-aliaised graphics
610     // - this may be slow on old computers, so caller can disable it on demand
611     ForceNoAntiAliased: boolean;
612     /// if true, drawing will NOT to use native GDI+ 1.1 conversion
613     // - we found out that GDI+ 1.1 was not as good as our internal conversion
614     // function written in Delphi, e.g. for underlined fonts
615     // - so this property is set to true by default for proper display on screen
616     // - will only be used if ForceNoAntiAliased is false, of course
617     ForceInternalAntiAliased: boolean;
618     /// if true, internal text drawing will use a font-fallback mechanism
619     // for characters not existing within the current font (just as with GDI)
620     // - is disabled by default, but could be set to TRUE to force enabling
621     // TGDIPlusFull.ForceUseDrawString property
622     ForceInternalAntiAliasedFontFallBack: boolean;
623 
624 {$ifdef PRINTERNEW}
625     // the PrintPages() will use this parameter to force black and white, or
626     // color mode, whatever the global printer setting is
627     ForcePrintColorMode: (printColorDefault, printBW, printColor);
628     // the PrintPages() will use this parameter to force duplex mode,
629     // whatever the global printer setting is
630     ForcePrintDuplexMode: (printDuplexDefault, printSimplex, printDuplex);
631 {$endif}
632     /// if true, the headers are copied only once to the text
633     ForceCopyTextAsWholeContent: boolean;
634     /// customize text conversion before drawing
635     // - Text content can be modified by this event handler to customize
636     // some characters (e.g. '>=' can be converted to its Unicode glyph)
637     OnStringToUnicode: TOnStringToUnicodeEvent;
638     /// set group page fill method
639     // - if set to true, the groups will be forced to be placed on the same page
640     // (this was the original default "Pages" component behavior, but this
641     // is not usual in page composition, so is disabled by default in TGDIPages)
642     // - if set to false, the groups will force a page feed if there is not
643     // enough place for 20 lines on the current page (default behavior)
644     GroupsMustBeOnSamePage: boolean;
645     /// the bitmap used to draw the page
646     PreviewSurfaceBitmap: TBitmap;
647 
648     /// creates the reporting component
649     constructor Create(AOwner: TComponent); override;
650     /// finalize the component, releasing all used memory
651     destructor Destroy; override;
652     /// customized invalidate
653     procedure Invalidate; override;
654 
655     /// Begin a Report document
656     // - Every report must start with BeginDoc and end with EndDoc
657     // - note that Printers.SetPrinter() should be set BEFORE calling BeginDoc,
658     // otherwise you may have a "canvas does not allow drawing" error
659     procedure BeginDoc;
660     /// Clear the current Report document
661     procedure Clear; virtual;
662     /// draw some text as a paragraph, with the current alignment
663     // - this method does all word-wrapping and formating if necessary
664     // - this method handle multiple paragraphs inside s (separated by newlines -
665     // i.e. #13)
666     // - by default, will write a paragraph, unless withNewLine is set to FALSE,
667     // so that the next DrawText() will continue drawing at the current position
668     procedure DrawText(const s: string; withNewLine: boolean=true);
669       {$ifdef HASINLINE}inline;{$endif}
670     /// draw some UTF-8 text as a paragraph, with the current alignment
671     // - this method does all word-wrapping and formating if necessary
672     // - this method handle multiple paragraphs inside s (separated by newlines -
673     // i.e. #13)
674     // - by default, will write a paragraph, unless withNewLine is set to FALSE,
675     // so that the next DrawText() will continue drawing at the current position
676     procedure DrawTextU(const s: RawUTF8; withNewLine: boolean=true);
677       {$ifdef HASINLINE}inline;{$endif}
678     /// draw some Unicode text as a paragraph, with the current alignment
679     // - this method does all word-wrapping and formating if necessary
680     // - this method handle multiple paragraphs inside s (separated by newlines -
681     // i.e. #13)
682     // - by default, will write a paragraph, unless withNewLine is set to FALSE,
683     // so that the next DrawText() will continue drawing at the current position
684     procedure DrawTextW(const s: SynUnicode; withNewLine: boolean=true);
685     /// draw some text as a paragraph, with the current alignment
686     // - this method use format() like parameterss
687     procedure DrawTextFmt(const s: string; const Args: array of const;
688       withNewLine: boolean=true);
689     /// get the formating flags associated to a Title
690     function TitleFlags: integer;
691     /// draw some text as a paragraph title
692     // - the outline level can be specified, if UseOutline property is enabled
693     // - if aBookmark is set, a bookmark is created at this position
694     // - if aLink is set, a link to the specified bookmark name (in aLink) is made
695     procedure DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false; OutlineLevel: Integer=0;
696       const aBookmark: string=''; const aLink: string='');
697     /// draw one line of text, with the current alignment
698     procedure DrawTextAt(s: SynUnicode; XPos: integer; const aLink: string='';
699       CheckPageNumber: boolean=false);
700     /// draw one line of text, with a specified Angle and X Position
701     procedure DrawAngledTextAt(const s: SynUnicode; XPos, Angle: integer);
702     /// draw a square box at the given coordinates
703     procedure DrawBox(left,top,right,bottom: integer);
704     /// draw a filled square box at the given coordinates
705     procedure DrawBoxFilled(left,top,right,bottom: integer; Color: TColor);
706     /// Stretch draws a bitmap image at the specified page coordinates in mm's
707     procedure DrawBMP(rec: TRect; bmp: TBitmap); overload;
708     /// add the bitmap at the specified X position
709     // - if there is not enough place to draw the bitmap, go to next page
710     // - then the current Y position is updated
711     // - bLeft (in mm) is calculated in reference to the LeftMargin position
712     // - if bLeft is maxInt, the bitmap is centered to the page width
713     // - bitmap is stretched (keeping aspect ratio) for the resulting width to
714     // match the bWidth parameter (in mm)
715     procedure DrawBMP(bmp: TBitmap; bLeft, bWidth: integer; const Legend: string=''); overload;
716     /// Stretch draws a metafile image at the specified page coordinates in mm's
717     procedure DrawMeta(rec: TRect; meta: TMetafile);
718     /// add the graphic (bitmap or metafile) at the specified X position
719     // - handle only TBitmap and TMetafile kind of TGraphic
720     // - if there is not enough place to draw the bitmap, go to next page
721     // - then the current Y position is updated
722     // - bLeft (in mm) is calculated in reference to the LeftMargin position
723     // - if bLeft is maxInt, the bitmap is centered to the page width
724     // - bitmap is stretched (keeping aspect ratio) for the resulting width to
725     // match the bWidth parameter (in mm)
726     procedure DrawGraphic(graph: TGraphic; bLeft, bWidth: integer; const Legend: SynUnicode='');
727     /// draw an Arrow
728     procedure DrawArrow(Point1, Point2: TPoint; HeadSize: integer; SolidHead: boolean);
729     /// draw a Line, either simple or double, between the left & right margins
730     procedure DrawLine(doubleline: boolean=false);
731     /// draw a Dashed Line between the left & right margins
732     procedure DrawDashedLine;
733     /// append a Rich Edit content to the current report
734     // - note that if you want the TRichEdit component to handle more than 64 KB
735     // of RTF content, you have to set its MaxLength property as expected (this
736     // is a limitation of the VCL, not of this method)
737     // - you can specify optionally a pointer to a TIntegerDynArray variable,
738     // which will be filled with the position of each page last char: it may
739     // be handy e.g. to add some cross-reference table about the rendered content
740     procedure AppendRichEdit(RichEditHandle: HWnd; EndOfPagePositions: PIntegerDynArray=nil);
741     /// jump some line space between paragraphs
742     // - Increments the current Y Position the equivalent of a single line
743     // relative to the current font height and line spacing
744     procedure NewLine;
745     /// jump some half line space between paragraphs
746     // - Increments the current Y Position the equivalent of an half single line
747     // relative to the current font height and line spacing
748     procedure NewHalfLine;
749     /// jump some line space between paragraphs
750     // - Increments the current Y Position the equivalent of 'count' lines
751     // relative to the current font height and line spacing
752     procedure NewLines(count: integer);
753     /// save the current font and alignment
754     procedure SaveLayout; virtual;
755     /// restore last saved font and alignment
756     procedure RestoreSavedLayout; virtual;
757     /// jump to next page, i.e. force a page break
758     procedure NewPage(ForceEndGroup: boolean=false);
759     /// jump to next page, but only if some content is pending
760     procedure NewPageIfAnyContent;
761     /// change the page layout for the upcoming page
762     // - will then force a page break by a call to NewPage(true) method
763     // - can change the default margin if margin*>=0
764     // - can change the default non-printable printer margin if nonPrintable*>=0
765     procedure NewPageLayout(sizeWidthMM, sizeHeightMM: integer;
766       nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1); overload;
767     /// change the page layout for the upcoming page
768     // - will then force a page break by a call to NewPage(true) method
769     // - can change the default margin if margin*>=0
770     // - can change the default non-printable printer margin if nonPrintable*>=0
771     procedure NewPageLayout(paperSize: TGdiPagePaperSize;
772       orientation: TPrinterOrientation=poPortrait;
773       nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1); overload;
774     /// begin a Group: stops the contents from being split across pages
775     // - BeginGroup-EndGroup text blocks can't be nested
776     procedure BeginGroup;
777     /// end a previously defined Group
778     // - BeginGroup-EndGroup text blocks can't be nested
779     procedure EndGroup;
780     /// End the Report document
781     // - Every report must start with BeginDoc and end with EndDoc
782     procedure EndDoc;
783     /// Print the selected pages to the default printer of Printer unit
784     // - if PrintFrom=0 and PrintTo=0, then all pages are printed
785     // - if PrintFrom=-1 or PrintTo=-1, then a printer dialog is displayed
786     function PrintPages(PrintFrom, PrintTo: integer): boolean;
787     /// export the current report as PDF file
788 {$ifdef USEPDFPRINTER}
789     // - uses an external 'PDF' printer
790 {$else}
791     // - uses internal PDF code, from Synopse PDF engine (handle bookmarks,
792     // outline and twin bitmaps) - in this case, a file name can be set
793 {$endif}
794     function ExportPDF(aPdfFileName: TFileName; ShowErrorOnScreen: boolean;
795       LaunchAfter: boolean=true): boolean;
796 {$ifndef USEPDFPRINTER}
797     /// export the current report as PDF in a specified stream
798     // - uses internal PDF code, from Synopse PDF engine (handle bookmarks,
799     // outline and twin bitmaps) - in this case, a file name can be set
800     function ExportPDFStream(aDest: TStream): boolean;
801 {$endif}
802     /// show a form with the preview, allowing the user to browse pages and
803     // print the report
804     // - you can customize the buttons and popup menu actions displayed on
805     // the screen - by default, all buttons are visible
806     procedure ShowPreviewForm(VisibleButtons: TGdiPagePreviewButtons =
807       [rNextPage..High(TGdiPagePreviewButton)]);
808 
809     /// set the Tabs stops on every line
810     // - if one value is provided, it will set the Tabs as every multiple of it
811     // - if more than one value are provided, they will be the exact Tabs positions
812     procedure SetTabStops(const tabs: array of integer);
813     /// returns true if there is enough space in the current Report for Count lines
814     // - Used to check if there's sufficient vertical space remaining on the page
815     // for the specified number of lines based on the current Y position
HasSpaceForLinesnull816     function  HasSpaceForLines(Count: integer): boolean;
817     /// returns true if there is enough space in the current Report for a
818     // vertical size, specified in mm
HasSpaceFornull819     function  HasSpaceFor(mm: integer): boolean;
820 
821     /// Clear all already predefined Headers
822     procedure ClearHeaders;
823     /// Adds either a single line or a double line (drawn between the left &
824     // right page margins) to the page header
825     procedure AddLineToHeader(doubleline: boolean);
826     /// Adds text using to current font and alignment to the page header
827     procedure AddTextToHeader(const s: SynUnicode);
828     /// Adds text to the page header at the specified horizontal position and
829     // using to current font.
830     // - No Line feed will be triggered: this method doesn't increment the YPos,
831     // so can be used to add multiple text on the same line
832     // - if XPos=-1, will put the text at the current right margin
833     procedure AddTextToHeaderAt(const s: SynUnicode; XPos: integer);
834 
835     /// Clear all already predefined Footers
836     procedure ClearFooters;
837     /// Adds either a single line or a double line (drawn between the left &
838     // right page margins) to the page footer
839     procedure AddLineToFooter(doubleline: boolean);
840     /// Adds text using to current font and alignment to the page footer
841     procedure AddTextToFooter(const s: SynUnicode);
842     /// Adds text to the page footer at the specified horizontal position and
843     // using to current font. No Line feed will be triggered.
844     // - if XPos=-1, will put the text at the current right margin
845     procedure AddTextToFooterAt(const s: SynUnicode; XPos: integer);
846     /// Will add the current 'Page n/n' text at the specified position
847     // - PageText must be of format 'Page %d/%d', in the desired language
848     // - if XPos=-1, will put the text at the current right margin
849     // - if the vertical position does not fit your need, you could set
850     // YPosMultiplier to a value which will be multipled by fFooterHeight to
851     // compute the YPos
852     procedure AddPagesToFooterAt(const PageText: string; XPos: integer;
853       YPosMultiplier: integer=1);
854 
855     /// register a column, with proper alignment
856     procedure AddColumn(left, right: integer; align: TColAlign; bold: boolean);
857     /// register same alignement columns, with percentage of page column width
858     // - sum of all percent width should be 100, but can be of any value
859     // - negative widths are converted into absolute values, but
860     // corresponding alignment is set to right
861     // - if a column need to be right aligned or currency aligned,
862     // use SetColumnAlign() method below
863     // - individual column may be printed in bold with SetColumnBold() method
864     procedure AddColumns(const PercentWidth: array of integer; align: TColAlign=caLeft);
865     /// register some column headers, with the current font formating
866     // - Column headers will appear just above the first text output in
867     // columns on each page
868     // - you can call this method several times in order to have diverse
869     // font formats across the column headers
870     procedure AddColumnHeaders(const headers: array of SynUnicode;
871       WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
872       RowLineHeight: integer=0; flags: integer=0);
873     /// register some column headers, with the current font formating
874     // - Column headers will appear just above the first text output in
875     // columns on each page
876     // - call this method once with all columns text as CSV
877     procedure AddColumnHeadersFromCSV(var CSV: PWideChar;
878       WithBottomGrayLine: boolean; BoldFont: boolean=false; RowLineHeight: integer=0);
879     /// draw some text, split across every columns
880     // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
881     // row is printed on white with this background color (e.g. to highlight errors)
882     procedure DrawTextAcrossCols(const StringArray: array of SynUnicode;
883       BackgroundColor: TColor=clNone); overload;
884     /// draw some text, split across every columns
885     // - you can specify an optional bookmark name to be used to link a column
886     // content via a AddLink() call
887     // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
888     // row is printed on white with this background color (e.g. to highlight errors)
889     procedure DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
890       BackgroundColor: TColor=clNone); overload;
891     /// draw some text, split across every columns
892     // - this method expect the text to be separated by commas
893     // - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
894     // row is printed on white with this background color (e.g. to highlight errors)
895     procedure DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
896     /// draw (double if specified) lines at the bottom of all currency columns
897     procedure DrawLinesInCurrencyCols(doublelines: boolean);
898 
899     /// retrieve the current Column count
900     property ColumnCount: integer read GetColumnCount;
901     /// retrieve the attributes of a specified column
902     function GetColumnInfo(index: integer): TColRec;
903     /// individually set column alignment
904     // - useful after habing used AddColumns([]) method e.g.
905     procedure SetColumnAlign(index: integer; align: TColAlign);
906     /// individually set column bold state
907     // - useful after habing used AddColumns([]) method e.g.
908     procedure SetColumnBold(index: integer);
909     /// erase all columns and the associated headers
910     procedure ClearColumns;
911     /// clear the Headers associated to the Columns
912     procedure ClearColumnHeaders;
913     /// ColumnHeadersNeeded will force column headers to be drawn again just
914     // prior to printing the next row of columned text
915     // - Usually column headers are drawn once per page just above the first
916     // column. ColumnHeadersNeeded is useful where columns of text have been
917     // separated by a number of lines of non-columned text
918     procedure ColumnHeadersNeeded;
919 
920     /// create a bookmark entry at the current position of the current page
921     // - return false if this bookmark name was already existing, true on success
922     // - if aYPosition is not 0, the current Y position will be used
923     function AddBookMark(const aBookmarkName: string; aYPosition: integer=0): Boolean; virtual;
924     /// go to the specified bookmark
925     // - returns true if the bookmark name was existing and reached
926     function GotoBookmark(const aBookmarkName: string): Boolean; virtual;
927     /// create an outline entry at the current position of the current page
928     // - if aYPosition is not 0, the current Y position will be used
929     procedure AddOutline(const aTitle: string; aLevel: Integer;
930       aYPosition: integer=0; aPageNumber: integer=0); virtual;
931     /// create a link entry at the specified coordinates of the current page
932     // - coordinates are specified in mm
933     // - the bookmark name is not checked by this method: a bookmark can be
934     // linked before being marked in the document
935     procedure AddLink(const aBookmarkName: string; aRect: TRect;
936       aPageNumber: integer=0); virtual;
937 
938     /// convert a rect of mm into pixel canvas units
939     function MmToPrinter(const R: TRect): TRect;
940     /// convert a rect of pixel canvas units into mm
941     function PrinterToMM(const R: TRect): TRect;
942     /// convert a mm X position into pixel canvas units
943     function MmToPrinterPxX(mm: integer): integer;
944     /// convert a mm Y position into pixel canvas units
945     function MmToPrinterPxY(mm: integer): integer;
946     /// convert a pixel canvas X position into mm
947     function PrinterPxToMmX(px: integer): integer;
948     /// convert a pixel canvas Y position into mm
949     function PrinterPxToMmY(px: integer): integer;
950     /// return the width of the specified text, in mm
951     function TextWidth(const Text: SynUnicode): integer;
952     /// the current Text Alignment, during text adding
953     property TextAlign: TTextAlign read fAlign write SetTextAlign;
954     /// specifies the reading order (bidirectional mode) of the box
955     // - only bdLeftToRight and bdRightToLeft are handled
956     // - this will be used by DrawText[At], DrawTitle, AddTextToHeader/Footer[At],
957     // DrawTextAcrossCols, SaveLayout/RestoreSavedLayout methods
958     property BiDiMode: TBiDiMode read fBiDiMode write fBiDiMode;
959     /// create a meta file and its associated canvas for displaying a picture
960     // - you must release manually both Objects after usage
961     function CreatePictureMetaFile(Width, Height: integer;
962       out MetaCanvas: TCanvas): TMetaFile;
963     /// Distance (in mm's) from the top of the page to the top of the current group
964     // - returns CurrentYPos if no group is in use
CurrentGroupPosStartnull965     function CurrentGroupPosStart: integer;
966     /// go to the specified Y position on a given page
967     // - used e.g. by GotoBookmark() method
968     procedure GotoPosition(aPage: integer; aYPos: integer);
969     /// access to all pages content
970     // - numerotation begin with Pages[0] for page 1
971     // - the Pages[] property should be rarely needed
972     property Pages: TGDIPageContentDynArray read fPages;
973     /// add an item to the popup menu
974     // - used mostly internaly to add page browsing
975     // - default OnClick event is to go to page set by the Tag property
NewPopupMenuItemnull976     function NewPopupMenuItem(const aCaption: string; Tag: integer=0;
977       SubMenu: TMenuItem=nil; OnClick: TNotifyEvent=nil; ImageIndex: integer=-1): TMenuItem;
978     /// this is the main popup menu item click event
979     procedure PopupMenuItemClick(Sender: TObject);
980     /// can be used to draw directly using GDI commands
981     // - The Canvas property should be rarely needed
982     property Canvas: TMetaFileCanvas read fCanvas;
983     /// Distance (in mm's) from the top of the page to the top of the next line
984     property CurrentYPos: integer read GetYPos write SetYPos;
985     /// get current line height (mm)
986     property LineHeight: integer read GetLineHeightMm;
987     /// the name of the current selected printer
988     // - note that Printers.SetPrinter() should be set BEFORE calling BeginDoc,
989     // otherwise you may have a "canvas does not allow drawing" error
990     property PrinterName: string read fCurrentPrinter;
991     /// the index of the previewed page
992     // - please note that the first page is 1 (not 0)
993     property Page: integer read fCurrPreviewPage write SetPage;
994     /// total number of pages
995     property PageCount: integer read GetPageCount;
996     /// Size of each margin relative to its corresponding edge in mm's
997     property PageMargins: TRect read GetPageMargins write SetPageMargins;
998     /// Size of the left margin relative to its corresponding edge in mm's
999     property LeftMargin: integer read GetLeftMargin write SetLeftMargin;
1000     /// Position of the right margin, in mm
1001     property RightMarginPos: integer read GetRightMarginPos;
1002     /// get the current selected paper size, in mm's
1003     property PaperSize: TSize read GetPaperSize;
1004     /// number of pixel per inch, for X and Y directions
1005     property PrinterPxPerInch: TPoint read fPrinterPxPerInch;
1006 {$ifdef USEPDFPRINTER}
1007     /// true if any printer appears to be a PDF printer
1008     property HasPDFPrinterInstalled: boolean read fHasPDFPrinterInstalled;
1009 {$else}
1010     /// this property can force saving all bitmaps as JPEG in exported PDF
1011     // - by default, this property is set to 0 by the constructor of this class,
1012     // meaning that the JPEG compression is not forced, and the engine will use
1013     // the native resolution of the bitmap - in this case, the resulting
1014     // PDF file content will be bigger in size (e.g. use this for printing)
1015     // - 60 is the prefered way e.g. for publishing PDF over the internet
1016     // - 80/90 is a good ration if you want to have a nice PDF to see on screen
1017     // - of course, this doesn't affect vectorial (i.e. emf) pictures
1018     property ExportPDFForceJPEGCompression: integer read fForceJPEGCompression write fForceJPEGCompression;
1019     /// optional application name used during Export to PDF
1020     // - if not set, global Application.Title will be used
1021     property ExportPDFApplication: string read fExportPDFApplication write fExportPDFApplication;
1022     /// optional Author name used during Export to PDF
1023     property ExportPDFAuthor: string read fExportPDFAuthor write fExportPDFAuthor;
1024     /// optional Subject text used during Export to PDF
1025     property ExportPDFSubject: string read fExportPDFSubject write fExportPDFSubject;
1026     /// optional Keywords name used during Export to PDF
1027     property ExportPDFKeywords: string read fExportPDFKeywords write fExportPDFKeywords;
1028     /// if set to TRUE, the used True Type fonts will be embedded to the exported PDF
1029     // - not set by default, to save disk space and produce tiny PDF
1030     property ExportPDFEmbeddedTTF: boolean read fExportPDFEmbeddedTTF write fExportPDFEmbeddedTTF;
1031     /// if set to TRUE, the exported PDF is made compatible with PDF/A-1 requirements
1032     property ExportPDFA1: Boolean read fExportPDFA1 write fExportPDFA1;
1033     /// an optional background image, to be exported on every pdf page
1034     // - note that no private copy of the TGraphic instance is made: the caller
1035     // has to manage it, and free it after the pdf is generated
1036     property ExportPDFBackground: TGraphic read fExportPDFBackground write fExportPDFBackground;
1037     {$ifndef NO_USE_UNISCRIBE}
1038     /// set if the exporting PDF engine must use the Windows Uniscribe API to
1039     // render Ordering and/or Shaping of the text
1040     // - useful for Hebrew, Arabic and some Asiatic languages handling
1041     // - set to FALSE by default, for faster content generation
1042     property ExportPDFUseUniscribe: boolean read fExportPDFUseUniscribe write fExportPDFUseUniscribe;
1043     {$endif}
1044     /// used to define if the exported PDF document will handle "font fallback" for
1045     // characters not existing in the current font: it will avoid rendering
1046     // block/square symbols instead of the correct characters (e.g. for Chinese text)
1047     // - will use the font specified by FontFallBackName property to add any
1048     // Unicode glyph not existing in the currently selected font
1049     // - default value is TRUE
1050     property ExportPDFUseFontFallBack: boolean read fExportPDFUseFontFallBack
1051       write fExportPDFUseFontFallBack;
1052     /// set the font name to be used for missing characters in exported PDF document
1053     // - used only if UseFontFallBack is TRUE
1054     // - default value is 'Arial Unicode MS', if existing
1055     property ExportPDFFontFallBackName: string read fExportPDFFontFallBackName
1056       write fExportPDFFontFallBackName;
1057     /// set encryption level to be used in exporting PDF document
1058     property ExportPDFEncryptionLevel: TPdfEncryptionLevel
1059       read fExportPDFEncryptionLevel write fExportPDFEncryptionLevel;
1060     /// set encryption user password to be used in exporting PDF document
1061     // - leave it to '' unless you want the user to be asked for this password
1062     // at document opening
1063     // - ExportPDFEncryptionLevel = elRC4_40/elRC4_128 expects only ASCII-7 chars
1064     property ExportPDFEncryptionUserPassword: string
1065       read fExportPDFEncryptionUserPassword write fExportPDFEncryptionUserPassword;
1066     /// set encryption owner password to be used in exporting PDF document
1067     // - it is mandatory to set it to a non void value - by default, is set to
1068     // 'SynopsePDFEngine' by should be overridden for security
1069     // - ExportPDFEncryptionLevel = elRC4_40/elRC4_128 expects only ASCII-7 chars
1070     property ExportPDFEncryptionOwnerPassword: string
1071       read fExportPDFEncryptionOwnerPassword write fExportPDFEncryptionOwnerPassword;
1072     /// set encryption Permissions to be used in exporting PDF document
1073     // - can be either one of the PDF_PERMISSION_ALL / PDF_PERMISSION_NOMODIF /
1074     // PDF_PERSMISSION_NOPRINT / PDF_PERMISSION_NOCOPY /
1075     // PDF_PERMISSION_NOCOPYNORPRINT set of options
1076     // - default value is PDF_PERMISSION_ALL (i.e. no restriction)
1077     property ExportPDFEncryptionPermissions: TPdfEncryptionPermissions
1078       read fExportPDFEncryptionPermissions write fExportPDFEncryptionPermissions;
1079     /// set to TRUE to export in PDF 1.5 format, which may produce smaller files
1080     property ExportPDFGeneratePDF15File: Boolean
1081       read fExportPDFGeneratePDF15File write fExportPDFGeneratePDF15File;
1082 {$endif}
1083     /// the current page number, during text adding
1084     // - Page is used during preview, after text adding
1085     property VirtualPageNum: integer read fVirtualPageNum write fVirtualPageNum;
1086     /// true if any header as been drawn, that is if something is to be printed
1087     property HeaderDone: boolean read fHeaderDone;
1088 {    /// used to set if columns must be delimited at their bottom with a gray line
1089     property ColumnsWithBottomGrayLine: boolean read fColumnsWithBottomGrayLine
1090       write fColumnsWithBottomGrayLine; }
1091   published
1092     /// accounting standard layout for caCurrency columns:
1093     // - convert all negative sign into parentheses
1094     // - using parentheses instead of negative numbers is used in financial
1095     // statement reporting (see e.g. http://en.wikipedia.org/wiki/Income_statement)
1096     // - align numbers on digits, not parentheses
1097     property NegsToParenthesesInCurrCols: boolean
1098       read fNegsToParenthesesInCurrCols write fNegsToParenthesesInCurrCols;
1099     /// word wrap (caLeft) left-aligned columns into multiple lines
1100     // - if the text is wider than the column width, its content
1101     // is wrapped to the next line
1102     // - if the text contains some #13/#10 characters, it will be splitted into
1103     // individual lines
1104     // - this is disabled by default
1105     property WordWrapLeftCols: boolean read fWordWrapLeftCols write fWordWrapLeftCols;
1106     /// if set, any DrawTitle() call will create an Outline entry
1107     // - used e.g. for PDF generation
1108     // - this is enabled by default
1109     property UseOutlines: boolean read fUseOutlines write fUseOutlines;
1110     /// left justification hang indentation
1111     property HangIndent: integer read fHangIndent write fHangIndent;
1112     /// Line spacing: can be lsSingle, lsOneAndHalf or lsDouble
1113     property LineSpacing: TLineSpacing read fLineSpacing write fLineSpacing;
1114     /// the paper orientation
1115     property Orientation: TPrinterOrientation read GetOrientation write SetOrientation;
1116     /// the current Zoom value, according to the zoom status
1117     // - you can use PAGE_WIDTH and PAGE_FIT constants to force the corresponding
1118     // zooming mode (similar to ZoomStatus property setter)
1119     // - set this property will work only when the report is already shown
1120     // in preview mode, not before ShowPreviewForm method call
1121     property Zoom: integer read fZoom write SetZoom;
1122     /// the current Zoom procedure, i.e. zsPercent, zsPageFit or zsPageWidth
1123     // - set this property will define the Zoom at PAGE_WIDTH or PAGE_FIT
1124     // special constant, if needed
1125     // - set this property will work only when the report is already shown
1126     // in preview mode, not before ShowPreviewForm method call
1127     property ZoomStatus: TZoomStatus read fZoomStatus write SetZoomStatus;
1128     /// if set to true, we reduce the precision for better screen display
1129     property ForceScreenResolution: boolean
1130       read fForceScreenResolution write fForceScreenResolution;
1131 
1132     /// Event triggered when each new page is created
1133     property OnNewPage: TNewPageEvent
1134       read fStartNewPage write fStartNewPage;
1135     /// Event triggered when each new header is about to be drawn
1136     property OnStartPageHeader: TNotifyEvent
1137       read fStartPageHeader write fStartPageHeader;
1138     /// Event triggered when each header was drawn
1139     property OnEndPageHeader: TNotifyEvent
1140       read fEndPageHeader write fEndPageHeader;
1141     /// Event triggered when each new footer is about to be drawn
1142     property OnStartPageFooter: TNotifyEvent
1143       read fStartPageFooter write fStartPageFooter;
1144     /// Event triggered when each footer was drawn
1145     property OnEndPageFooter: TNotifyEvent
1146       read fEndPageFooter write fEndPageFooter;
1147     /// Event triggered when each new column is about to be drawn
1148     property OnStartColumnHeader: TNotifyEvent
1149       read fStartColumnHeader write fStartColumnHeader;
1150     /// Event triggered when each column was drawn
1151     property OnEndColumnHeader: TNotifyEvent
1152       read fEndColumnHeader write fEndColumnHeader;
1153 
1154     /// Event triggered whenever the report document generation is done
1155     // - i.e. when the EndDoc method has just been called
1156     property OnDocumentProduced: TNotifyEvent
1157       read fOnDocumentProducedEvent write fOnDocumentProducedEvent;
1158     /// Event triggered whenever the current preview page is changed
1159     property OnPreviewPageChanged: TNotifyEvent
1160       read fPreviewPageChangedEvent write fPreviewPageChangedEvent;
1161     /// Event triggered whenever the preview page is zoomed in or out
1162     property OnZoomChanged: TZoomChangedEvent
1163       read fZoomChangedEvent write fZoomChangedEvent;
1164   end;
1165 
1166 {$ifdef RENDERPAGES}
1167   TRenderPages = class;
1168 
1169   /// a TRenderPages additional layout state
1170   // - used by the overridden SaveLayout/RestoreSavedLayout methods
1171   TSavedStateRender = record
1172     FirstLineIndent: Integer;
1173     Before: Integer;
1174     After: Integer;
1175     RightIndent: Integer;
1176     LeftIndent: Integer;
1177   end;
1178 
1179   PRenderBoxWord = ^TRenderBoxWord;
1180 
1181   /// the internal "Word" box structure used by TRenderBox
1182   TRenderBoxWord = packed record
1183     /// offset in the fText[] array
1184     TextOffset: integer;
1185     /// PWideChar count starting from fText[TextOffset]
1186     TextLength: integer;
1187     /// size on the canvas
1188     Size: TSize;
1189     /// used to retrieve associated font attributes
1190     FontIndex: integer;
1191     /// space width from current font attribute
1192     FontSpaceWidth: integer;
1193     /// number of spaces at the right side of this "Word" box
1194     SpaceAfterCount: integer;
1195     /// associated link bookmark name
1196     // - from fLinksBookMarkName[LinkNumber-1], no link set for 0
1197     LinkNumber: integer;
1198   end;
1199 
1200   PRenderBoxLayout = ^TRenderBoxLayout;
1201 
1202   /// the internal "drawing" box structure used by TRenderBox
1203   // - TRenderBox.InternalRender populate fLayout[] with this structures,
1204   // ready to be drawn to the document Canvas
1205   TRenderBoxLayout = packed record
1206     /// pointer of the words in the fText[] array
1207     Text: PWideChar;
1208     /// number of PWideChar starting at Text^
1209     Length: integer;
1210     /// layout box X coordinate
1211     Left: integer;
1212     /// layout box Y coordinate
1213     Top: integer;
1214     /// layout box width (in pixels)
1215     Width: integer;
1216     /// layout box height (in pixels) - that is, the line height
1217     Height: integer;
1218     /// corresponding rendered line index (starting at 0)
1219     LineIndex: integer;
1220     /// used to retrieve associated font attributes and links e.g.
1221     LastBox: PRenderBoxWord;
1222     /// length of extra space, in pixels - as used by SetTextJustification()
1223     BreakExtra: integer;
1224     /// count of space characters in line of text - as used by SetTextJustification()
1225     BreakCount: integer;
1226   end;
1227 
1228   /// used to render a "box" of text
1229   // - will handle word adding, and formatting for a given width
1230   // - is used by TRenderPage for a whole paragraph, or a column inside a table
1231   TRenderBox = class
1232   protected
1233     fBiDiMode: TBiDiMode;
1234     fWidth: integer;
1235     fHeight: integer;
1236     /// an internal buffer containing the Unicode text of this box
1237     fText: array of WideChar;
1238     fTextLen: integer;
1239     /// word markers of the current text
1240     fBox: array of TRenderBoxWord;
1241     fBoxCount: integer;
1242     /// InternalRender will fill this ready to be rendered layout array
1243     fLayout: array of TRenderBoxLayout;
1244     fLayoutCount: integer;
1245     fOwner: TRenderPages;
1246     fOwnerFont: TFont;
1247     /// associated links: none set for 0, otherwise fLinksBookMarkName[number-1]
1248     fLinksBookMarkNameCurrent: integer;
1249     fLinksBookMarkName: array of string;
1250     /// populate fLayout[] from fBox[] and calculate fHeight
1251     procedure InternalRender;
1252     function GetHeight: integer;
1253     procedure Clear;
1254   public
1255     /// initialize the rendering "box"
1256     constructor Create(Owner: TRenderPages);
1257     /// add some text at the current position
1258     // - the text is converted to Unicode before adding (calling
1259     // Owner.OnStringToUnicode if was defined)
1260     // - the current Owner Font settings are used for the rendering
1261     // - warning: this method won't handle control chars (like #13 or #10), but
1262     // will replace them with a space: it's about the caller to
1263     procedure AddText(const s: string); overload;
1264     /// add some text at the current position
1265     // - the current Owner Font settings are used for the rendering
1266     // - warning: this method won't handle control chars (like #13 or #10), but
1267     // will replace them with a space: it's about the caller to
1268     procedure AddText(PW: PWideChar; PWLen: integer); overload;
1269     /// format the already inserted text into the TRenderPages owner
1270     // - this TRenderBox text content will be cleared at the end of this method
1271     // - you don't have to call it usualy: use Owner.RdrParagraph instead
1272     // - by default, will render top aligned to the X=Left/Y=Top pixels position
1273     // - for vertical alignment, specify an height in ForcedHeightBottomCentered
1274     // then will be centered if ForcedAtBottom=false, or bottom aligned if true
1275     // - if CurrentPageOnly is true, will only flush the content which will fit on
1276     // the current page - the fLayout[] array will contain remaining boxes;
1277     // - if CurrentPageOnly is false, this will flush all content to multiple pages
1278     procedure Flush(Left, Top: Integer; CurrentPageOnly: boolean;
1279       ForcedHeightBottomCentered: Integer; ForcedAtBottom: boolean);
1280     /// render the text paragraph, but go to the next line
1281     // - similar to the <br /> HTML tag or the \line RTF command
1282     procedure NewLine;
1283     /// mark that an hyperlink must begin at the current position
1284     // - use e.g. RdrAddText method to add some text for the link
1285     // - will cancel any previous LinkBegin with no LinkEnd: i.e. no nested
1286     // links are handled yet (how would want it anyway, in the HTML world?)
1287     procedure LinkBegin(const aBookmarkName: string);
1288     /// mark that an hyperlink must begin at the current position
1289     // - use e.g. RdrAddText method to add some text for the link
1290     // - return false on error (e.g. no hyperlink previously opened via LinkBegin)
LinkEndnull1291     function LinkEnd: boolean;
1292     /// reset font (character) formatting properties to a default value
1293     // - default value have been set by RdrSetCurrentStateAsDefault
1294     // - if no previous call to RdrSetCurrentStateAsDefault has been made,
1295     // the font is reset to a 12 point, with no bold/italic/underline attributes
1296     // - similar to the \plain RTF command
1297     procedure Plain; {$ifdef HASINLINE}inline;{$endif}
1298     /// reset paragraph formatting properties to a default value
1299     // - similar to the \pard RTF command
1300     procedure Pard; {$ifdef HASINLINE}inline;{$endif}
1301     /// reset both paragraph and font formatting properties to a default value
1302     // - similar to the \pard\plain RTF command
1303     procedure PardPlain; {$ifdef HASINLINE}inline;{$endif}
1304     /// shortcut to the owner TRenderPages
1305     property Owner: TRenderPages read fOwner;
1306     /// shortcut to the owner TRenderPages.Font
1307     property Font: TFont read fOwnerFont;
1308     /// specifies the reading order (bidirectional mode) of the box
1309     // - only bdLeftToRight and bdRightToLeft are handled
1310     property BiDiMode: TBiDiMode read FBiDiMode write FBiDiMode;
1311     /// current width of the "box", in pixels
1312     // - must be set before any call to InternalRender
1313     property Width: integer read fWidth write fWidth;
1314     /// current resulting height of the "box", in pixels
1315     // - will be calculated from current text if necessary
1316     property Height: integer read GetHeight;
1317   end;
1318 
1319   /// Report class specified in high-quality document rendering
1320   // - this class add some methods for creating a document at the character
1321   // level (whereas standard TGDIPages allows reporting at paragraph level)
1322   // - can be used e.g. to render some RTF-like content
1323   // - column handling is much more sophisticated than AddColumn*() methods
1324   // - uses the Windows Uniscribe API to handle right-to-left scripting and
1325   // process complex scripts (like Arabic)
1326   // - uses internaly some TeX-like algorithms like widows and orphans, and
1327   // an optional external hyphenation engine (like our hyphen unit)
1328   TRenderPages = class(TGDIPages)
1329   protected
1330     fParagraphFirstLineIndent: Integer;
1331     fParagraphBefore: Integer;
1332     fParagraphAfter: Integer;
1333     fParagraphRightIndent: Integer;
1334     fParagraphLeftIndent: Integer;
1335     fSavedRender: array of TSavedStateRender;
1336     fDefaultState: TSavedState;
1337     fDefaultStateRender: TSavedStateRender;
1338     fRdr: TRenderBox;
1339     fRdrCol: TObjectList;
1340     /// an array of TFont, used as cache
1341     fFontCache: TObjectList;
1342     fFontCacheSpace: array of TSize;
1343     procedure RdrPard;
1344     procedure RdrPardPlain;
1345     procedure RdrPlain;
GetCurrentFontCacheIndexnull1346     function GetCurrentFontCacheIndex: integer;
GetCurrentFontCacheIndexAndSelectnull1347     function GetCurrentFontCacheIndexAndSelect: integer;
GetSavedRendernull1348     function GetSavedRender: TSavedStateRender;
1349     procedure SetSavedRender(const State: TSavedStateRender);
1350     /// will close any pending paragraph (\page makes an implicit \par)
1351     procedure NewPageInternal; override;
1352   public
1353     /// will set the current Font and Paragraph properties to be used as default
1354     // - will be used by RdrPlain and RdrPard methods
1355     procedure RdrSetCurrentStateAsDefault;
1356     /// render the text paragraph, and begin a new one
1357     // - write the paragraph text as specified by all previous calls to the
1358     // Rdr TRenderBox methods, and begin a new paragraph, using a cleaned
1359     // TRenderBox instance
1360     // - will use the current TextAlign property value, and the current value
1361     // of all Paragraph* properties of this class
1362     // - similar to the </p> HTML tag or the \par RTF command
1363     procedure RdrParagraph;
1364     /// create a new table at the current position
1365     // - return false on error (e.g. a table was opened but not yet ended)
RdrTableBeginnull1366     function RdrTableBegin(const PercentWidth: array of integer): Boolean;
1367     /// get a particular column
1368     // - return the 'box' handling the layout of the column: use its
1369     // AddText/NewLine/Link*/Plain/Pard methods  methods to add some formatted text
RdrTableColumnnull1370     function RdrTableColumn(aColumnIndex: Integer): TRenderBox; {$ifdef HASINLINE}inline;{$endif}
1371     /// end a previously opened table
1372     // - will draw all columns to the documents
1373     // - return false on error (e.g. a table was not opened)
RdrTableEndnull1374     function RdrTableEnd: Boolean;
1375     /// the main paragraph 'box' of the document
1376     // - its AddText/NewLine/Link*/Plain/Pard methods  methods to add some
1377     // formatted text
1378     // - the paragraph will be flushed to the main document with the RdrParagraph
1379     // method will be called
1380     property Rdr: TRenderBox read fRdr;
1381   public { some overridden methods }
1382     /// creates the reporting component
1383     constructor Create(AOwner: TComponent); override;
1384     /// finalize the component, releasing all used memory and associated TRenderBox
1385     destructor Destroy; override;
1386     /// Clear the current Report document
1387     procedure Clear; override;
1388     /// save the current font and alignment
1389     // - similar to a { character in some RTF content
1390     // - this version will save also Paragraph* properties values
1391     procedure SaveLayout; override;
1392     /// restore last saved font and alignment
1393     // - similar to a } character in some RTF content
1394     // - this version will restore also Paragraph* properties values
1395     procedure RestoreSavedLayout; override;
1396   public
1397     /// current paragraph "space before" spacing (in mm, the default is 0)
1398     property ParagraphBefore: Integer read fParagraphBefore write fParagraphBefore;
1399     /// current paragraph "space after" spacing (in mm, the default is 0)
1400     property ParagraphAfter: Integer read fParagraphAfter write fParagraphAfter;
1401     /// current paragraph first-line indent (in mm, the default is 0)
1402     property ParagraphFirstLineIndent: Integer
1403       read fParagraphFirstLineIndent write fParagraphFirstLineIndent;
1404     /// current paragraph left indent (in mm, the default is 0)
1405     property ParagraphLeftIndent: Integer
1406       read fParagraphLeftIndent write fParagraphLeftIndent;
1407     /// current paragraph right indent (in mm, the default is 0)
1408     property ParagraphRightIndent: Integer
1409       read fParagraphRightIndent write fParagraphRightIndent;
1410   end;
1411 {$endif RENDERPAGES}
1412 
1413 resourcestring
1414   sPDFFile = 'Acrobat File';
1415   sPageN = 'Page %d / %d';
1416   /// used to create the popup menu of the report
1417   // - should match TGdiPagePreviewButton order
1418   sReportPopupMenu1 = '&Next page,&Previous page,&Go to Page...,&Zoom...,'+
1419     '&Bookmarks,Copy Page as &Text,P&rint,PDF &Export,&Close,Page fit,Page width';
1420   /// used to create the pages browsing menu of the report
1421   sReportPopupMenu2 = 'Pages %d to %d,Page %d';
1422 
1423 const
1424   /// minimum gray border with around preview page
1425   GRAY_MARGIN = 10;
1426 
1427   /// TGdiPages.Zoom property value for "Page width" layout during preview
1428   PAGE_WIDTH = -1;
1429   /// TGdiPages.Zoom property value for "Page fit" layout during preview
1430   PAGE_FIT   = -2;
1431 
1432   //TEXT FORMAT FLAGS...
1433   FORMAT_DEFAULT    = $0;
1434   //fontsize bits 0-7  .'. max = 255
1435   FORMAT_SIZE_MASK  = $FF;
1436   //alignment bits 8-9
1437   FORMAT_ALIGN_MASK = $300;
1438   FORMAT_LEFT       = $0;
1439   FORMAT_RIGHT      = $100;
1440   FORMAT_CENTER     = $200;
1441   FORMAT_JUSTIFIED  = $300;
1442   //fontstyle bits 10-12
1443   FORMAT_BOLD       = $400;
1444   FORMAT_UNDERLINE  = $800;
1445   FORMAT_ITALIC     = $1000;
1446   //undefined bit 13
1447   FORMAT_UNDEFINED  = $2000;
1448   //line flags bits 14-15
1449   FORMAT_SINGLELINE = $8000;
1450   FORMAT_DOUBLELINE = $4000;
1451   FORMAT_LINES      = $C000;
1452   //DrawTextAt XPos 16-30 bits  (max value = ~64000)
1453   FORMAT_XPOS_MASK  = $FFFF0000;
1454 
1455   PAPERSIZE_A4_WIDTH = 210;
1456   PAPERSIZE_A4_HEIGHT = 297;
1457 
1458 
1459 procedure SetCurrentPrinterAsDefault;
1460 function CurrentPrinterName: string;
1461 function CurrentPrinterPaperSize: string;
1462 procedure UseDefaultPrinter;
1463 
1464 procedure Register;
1465 
1466 
1467 implementation
1468 
1469 uses
1470   {$ifdef ISDELPHIXE3}System.UITypes,{$endif}
1471   Types, Clipbrd, Consts;
1472 
1473 // Miscellaneous functions ...
1474 
1475 function TextExtent(Canvas: TCanvas; const Text: SynUnicode; Len: integer=0): TSize;
1476 begin
1477   Result.cX := 0;
1478   Result.cY := 0;
1479   if Len=0 then
1480     Len := length(Text);
1481   GetTextExtentPoint32W(Canvas.Handle, pointer(Text), Len, Result);
1482 end;
1483 
1484 function TextWidthC(Canvas: TCanvas; const Text: SynUnicode): Integer;
1485 begin
1486   Result := TextExtent(Canvas,Text).cX;
1487 end;
1488 
1489 procedure TextOut(Canvas: TCanvas; X,Y: integer; Text: PWideChar; Len: integer); overload;
1490 begin
1491   ExtTextOutW(Canvas.Handle,X,Y,Canvas.TextFlags,nil,Text,Len,nil);
1492 end;
1493 
1494 procedure TextOut(Canvas: TCanvas; X,Y: integer; const Text: SynUnicode); overload;
1495 begin
1496   ExtTextOutW(Canvas.Handle,X,Y,Canvas.TextFlags,nil,pointer(Text),Length(Text),nil);
1497 end;
1498 
1499 procedure Register;
1500 begin
1501   RegisterComponents('Samples', [TGDIPages]);
1502 end;
1503 
1504 function ConvertNegsToParentheses(const ValStr: SynUnicode): SynUnicode;
1505 begin
1506   result := ValStr;
1507   if (result = '') or (result[1] <> '-') then
1508     exit;
1509   result[1] := '(';
1510   result := result+')';
1511 end;
1512 
1513 function PrinterDriverExists: boolean;
1514 var Flags, Count, NumInfo: dword;
1515     Level: Byte;
1516 begin
1517   // avoid using fPrinter.printers.Count as this will raise an
1518   // exception if no printer driver is installed...
1519   Count := 0;
1520   try
1521     if Win32Platform = VER_PLATFORM_WIN32_NT then begin
1522       Flags := PRINTER_ENUM_CONNECTIONS or PRINTER_ENUM_LOCAL;
1523       Level := 4;
1524     end else begin
1525       Flags := PRINTER_ENUM_LOCAL;
1526       Level := 5;
1527     end;
1528     EnumPrinters(Flags, nil, Level, nil, 0, Count, NumInfo);
1529   except
1530   end;
1531   result := (count > 0);
1532 end;
1533 
1534 function RightTrim(const S: SynUnicode): SynUnicode;
1535 var i: integer;
1536 begin
1537   i := Length(s);
1538   while (i > 0) and (ord(S[i])<=32) do dec(i);
1539   SetString(result,PWideChar(pointer(S)),i);
1540 end;
1541 
1542 function LowerCaseU(const S: SynUnicode): SynUnicode;
1543 var i: integer;
1544 begin
1545   SetString(result,PWideChar(pointer(S)),length(S));
1546   for i := 0 to length(S)-1 do
1547     if PWordArray(result)[i] in [ord('A')..ord('Z')] then
1548       dec(PWordArray(result)[i],32);
1549 end;
1550 
1551 function Max(a,b: integer): integer;
1552 begin
1553   if a > b then
1554     result := a else
1555     result := b;
1556 end;
1557 
1558 function Min(a,b: integer): integer;
1559 begin
1560   if a < b then
1561     result := a else
1562     result := b;
1563 end;
1564 
1565 procedure UseDefaultPrinter;
1566 begin
1567   Printers.Printer.PrinterIndex := -1;
1568 end;
1569 
1570 function GetDefaultPrinterName: string;
1571 var Device : array[byte] of char;
1572     p,p2: PChar;
1573 begin
1574   GetProfileString('windows', 'device', '', Device, 255);
1575   p2 := Device;
1576   while p2^ = ' ' do inc(p2);
1577   p := p2;
1578   while not (ord(p2^) in [0,ord(',')]) do inc(p2);
1579   SetLength(result, p2 - p);
1580   if p2 > p then
1581    move(p^, pointer(result)^, p2 - p);
1582 end;
1583 
1584 function GetDriverForPrinter(Device: PChar; Driver: PChar): boolean;
1585 var
1586   PrintHandle: THandle;
1587   DriverInfo2: PDriverInfo2;
1588   cnt: dword;
1589   DriverPath: string;
1590 begin
1591   result := false;
1592   if not OpenPrinter(Device,PrintHandle, nil) then exit;
1593   try
1594     getmem(DriverInfo2,1024);
1595     try
1596       if GetPrinterDriver(PrintHandle, nil, 2, DriverInfo2, 1024, cnt) then
1597       begin
1598         DriverPath :=
1599           changefileext(extractfilename(DriverInfo2.pDriverPath),'');
1600         strpcopy(Driver, DriverPath);
1601         result := true;
1602       end;
1603     finally
1604       freemem(DriverInfo2);
1605     end;
1606   finally
1607     ClosePrinter(PrintHandle);
1608   end;
1609 end;
1610 
1611 procedure SetCurrentPrinterAsDefault;
1612 var Device : array[byte] of char;
1613   Driver : array[byte] of char;
1614   Port  : array[byte] of char;
1615   DefaultPrinter: string;
1616   hDeviceMode: THandle;
1617 begin
1618   DefaultPrinter := GetDefaultPrinterName;
1619   Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
1620   if DefaultPrinter = Device then exit;
1621   if (Driver[0] = #0) then
1622     if not GetDriverForPrinter(Device, Driver) then exit;  //oops !
1623   DefaultPrinter := format('%s,%s,%s',[Device, Driver, Port]);
1624   WriteProfileString( 'windows', 'device', pointer(DefaultPrinter) );
1625   Device := 'windows';
1626   SendMessage( HWND_BROADCAST, WM_WININICHANGE, 0, integer( @Device ));
1627 end;
1628 
1629 function CurrentPrinterName: string;
1630 var Device : array[byte] of char;
1631     Driver : array[byte] of char;
1632     Port  : array[byte] of char;
1633     hDeviceMode: THandle;
1634 begin
1635   Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
1636   result := trim(Device);
1637 end;
1638 
1639 
1640 function CurrentPrinterPaperSize: string;
1641 var PtrHdl: THandle;
1642     PtrPPI: TPoint;
1643     size: TSize;
1644 begin
1645   try
1646     PtrHdl := Printer.Handle;
1647     PtrPPI.x := GetDeviceCaps(PtrHdl, LOGPIXELSX);
1648     PtrPPI.y := GetDeviceCaps(PtrHdl, LOGPIXELSY);
1649     size.cx := MulDiv(GetDeviceCaps(PtrHdl, PHYSICALWIDTH), 254,PtrPPI.x *10);
1650     size.cy := MulDiv(GetDeviceCaps(PtrHdl, PHYSICALHEIGHT), 254,PtrPPI.y *10);
1651   except
1652   end;
1653   with size do
1654   begin
1655     if cx > cy then
1656     begin
1657       //landscape ...
1658       case cy of
1659         148: if (cx = 210) then result := 'A5 (210 x 148mm)';
1660         210: if (cx = 297) then result := 'A4 (297 x 210mm)';
1661         216: if (cx = 279) then result := 'Letter (11 x 8�")'
1662              else if (cx = 356) then result := 'Legal (14 x 8�")';
1663         297: if (cx = 420) then result := 'A3 (420 x 297mm)';
1664       end;
1665     end else
1666     begin
1667       //portrait ...
1668       case cx of
1669         148: if (cy = 210) then result := 'A5 (148 x 210mm)';
1670         210: if (cy = 297) then result := 'A4 (210 x 297mm)';
1671         216: if (cy = 279) then result := 'Letter (8� x 11")'
1672              else if (cy = 356) then result := 'Legal (8� x 14")';
1673         297: if (cy = 420) then result := 'A3 (297 x 420mm)';
1674       end;
1675     end;
1676     if result = '' then result := format('Custom (%d x %dmm)',[cx, cy]);
1677   end;
1678 end;
1679 
1680 
1681 // This declaration modifies Delphi's declaration of GetTextExtentExPoint
1682 // so that the variable to receive partial string extents (p6) is ignored ...
GetTextExtentExPointNoPartialsWnull1683 function GetTextExtentExPointNoPartialsW(DC: HDC; p2: PChar; p3, p4: Integer;
1684   var p5: Integer; const p6: integer; var p7: TSize): BOOL; stdcall;
1685     external gdi32 name 'GetTextExtentExPointW';
1686 
1687 // TrimLine: Splits off from LS any characters beyond the allowed width
1688 // breaking at the end of a word if possible. Leftover chars -> RS.
1689 procedure TrimLine(Canvas: TCanvas; var ls: SynUnicode; out rs: SynUnicode;
1690                            LineWidthInPxls: integer);
1691 var i,len,NumCharWhichFit: integer;
1692     dummy: TSize;
Fitsnull1693   function Fits: boolean;
1694   begin
1695     result := GetTextExtentExPointNoPartialsW(Canvas.Handle,
1696       pointer(ls),len,LineWidthInPxls,NumCharWhichFit,0,dummy);
1697   end;
1698 begin
1699   len := length(ls);
1700   if len = 0 then
1701     exit;
1702 
1703   // get the number of characters which will fit within LineWidth...
1704   if len>1024 then
1705     len := 1024; // speed up the API call: we expect only one line of text
1706   if not Fits then // fix API error (too big text) by rough binary approximation
1707     repeat
1708       len := len shr 1;
1709     until (len=0) or Fits;
1710 
1711   if NumCharWhichFit = length(ls) then
1712     exit; // if everything fits then stop here
1713 
1714   // find the end of the last whole word which will fit...
1715   i := NumCharWhichFit;
1716   while (NumCharWhichFit > 0) and (ls[NumCharWhichFit] > ' ') do
1717     dec(NumCharWhichFit);
1718   if (NumCharWhichFit = 0) then NumCharWhichFit := i;
1719 
1720   i := NumCharWhichFit+1;
1721   // ignore trailing blanks in LS...
1722   while (ls[NumCharWhichFit] <= ' ') do dec(NumCharWhichFit);
1723   // ignore beginning blanks in RS...
1724   len := length(ls); // may have been reduced if len>1024 or on API error
1725   while (i < len) and (ls[i] <= ' ') do inc(i);
1726   rs := copy(ls,i,len);
1727   ls := copy(ls,1,NumCharWhichFit);        //nb: assign ls AFTER rs here
1728 end;
1729 
1730 
1731 procedure PrintBitmap(Canvas: TCanvas; DestRect: TRect; Bitmap: TBitmap);
1732 var BitmapHeader:  pBitmapInfo;
1733     BitmapImage :  POINTER;
1734     HeaderSize  :  dword;
1735     ImageSize   :  dword;
1736 begin
1737   // we expect the bitmap to be stored as DIB in the TMetaFile content
1738   GetDIBSizes(Bitmap.Handle,HeaderSize,ImageSize);
1739   GetMem(BitmapHeader,HeaderSize);
1740   GetMem(BitmapImage,ImageSize);
1741   try
1742     GetDIB(Bitmap.Handle, Bitmap.Palette, BitmapHeader^, BitmapImage^);
1743     // will create a EMR_STRETCHDIBITS record, ready for SynPdf and SynGdiPlus
1744     StretchDIBits(Canvas.Handle,
1745                   DestRect.Left, DestRect.Top,     // Destination Origin
1746                   DestRect.Right  - DestRect.Left, // Destination Width
1747                   DestRect.Bottom - DestRect.Top,  // Destination Height
1748                   0,0,                             // Source Origin
1749                   Bitmap.Width, Bitmap.Height,     // Source Width & Height
1750                   BitmapImage,
1751                   TBitmapInfo(BitmapHeader^),
1752                   DIB_RGB_COLORS,
1753                   SRCCOPY);
1754   finally
1755     FreeMem(BitmapHeader);
1756     FreeMem(BitmapImage)
1757   end;
1758 end;
1759 
1760 
isnull1761 // This DrawArrow() function is based on code downloaded from
1762 // http://www.efg2.com/Lab/Library/Delphi/Graphics/Algorithms.htm
1763 // (The original author is unknown)
1764 procedure DrawArrowInternal(Canvas: TCanvas;
1765   FromPoint, ToPoint: TPoint; HeadSize: integer; SolidArrowHead: boolean);
1766 var
1767   xbase           :  integer;
1768   xLineDelta      :  integer;
1769   xLineUnitDelta  :  Double;
1770   xNormalDelta    :  integer;
1771   xNormalUnitDelta:  Double;
1772   ybase           :  integer;
1773   yLineDelta      :  integer;
1774   yLineUnitDelta  :  Double;
1775   yNormalDelta    :  integer;
1776   yNormalUnitDelta:  Double;
1777   SavedBrushColor :  TColor;
1778 begin
1779   with FromPoint do Canvas.MoveTo(x,y);
1780   with ToPoint do Canvas.LineTo(x,y);
1781 
1782   xLineDelta := ToPoint.X - FromPoint.X;
1783   yLineDelta := ToPoint.Y - FromPoint.Y;
1784 
1785   xLineUnitDelta := xLineDelta / SQRT( SQR(xLineDelta) + SQR(yLineDelta) );
1786   yLineUnitDelta := yLineDelta / SQRt( SQR(xLineDelta) + SQR(yLineDelta) );
1787 
1788   // (xBase,yBase) is where arrow line is perpendicular to base of triangle
1789   xBase := ToPoint.X - ROUND(HeadSize * xLineUnitDelta);
1790   yBase := ToPoint.Y - ROUND(HeadSize * yLineUnitDelta);
1791 
1792   xNormalDelta :=  yLineDelta;
1793   yNormalDelta := -xLineDelta;
1794   xNormalUnitDelta := xNormalDelta / SQRT( SQR(xNormalDelta) + SQR(yNormalDelta) );
1795   yNormalUnitDelta := yNormalDelta / SQRt( SQR(xNormalDelta) + SQR(yNormalDelta) );
1796 
1797   SavedBrushColor := Canvas.Brush.Color;
1798   if SolidArrowHead then
1799     Canvas.Brush.Color := Canvas.Pen.Color;
1800   Canvas.Polygon([ToPoint,
1801     Point(xBase + ROUND(HeadSize*xNormalUnitDelta),
1802       yBase + ROUND(HeadSize*yNormalUnitDelta)),
1803     Point(xBase - ROUND(HeadSize*xNormalUnitDelta),
1804       yBase - ROUND(HeadSize*yNormalUnitDelta)) ]);
1805   Canvas.Brush.Color := SavedBrushColor;
1806 end;
1807 
1808 
1809 
1810 { TPagePaintBox }
1811 
1812 procedure TPagePaintBox.WMEraseBkgnd(var Message: TWmEraseBkgnd);
1813 begin
1814   Message.Result := 1; // no erasing is necessary after this method call
1815 end;
1816 
1817 
1818 { TGDIPages }
1819 
1820 procedure TGDIPages.SetAnyCustomPagePx;
1821 begin
1822   if Int64(fCustomPageSize)<>-1 then
1823     fPhysicalSizePx := fCustomPageSize;
1824   if Int64(fCustomNonPrintableOffset)<>-1 then
1825     fPhysicalOffsetPx := fCustomNonPrintableOffset;
1826   if Int64(fCustomPageMargins.TopLeft)<>-1 then
1827     fPageMarginsPx := fCustomPageMargins;
1828   if Int64(fCustomPxPerInch)<>-1 then
1829     fPrinterPxPerInch := fCustomPxPerInch;
1830   fDefaultLineWidth := (fPrinterPxPerInch.y*25) div 2540;
1831 end;
1832 
1833 procedure TGDIPages.GetPrinterParams;
1834 var i: integer;
1835 begin
1836   if Self=nil then exit;
1837   if not fForceScreenResolution and fHasPrinterInstalled then
1838     try
1839       fCurrentPrinter := CurrentPrinterName;
1840       if (Printer.orientation <> fOrientation) then
1841         Printer.orientation := fOrientation;
1842       fPtrHdl := Printer.Handle;
1843       fPrinterPxPerInch.x := GetDeviceCaps(fPtrHdl, LOGPIXELSX);
1844       fPrinterPxPerInch.y := GetDeviceCaps(fPtrHdl, LOGPIXELSY);
1845       fPhysicalSizePx.x := GetDeviceCaps(fPtrHdl, PHYSICALWIDTH);
1846       fPhysicalOffsetPx.x := GetDeviceCaps(fPtrHdl,PHYSICALOFFSETX);
1847       fPhysicalSizePx.y := GetDeviceCaps(fPtrHdl, PHYSICALHEIGHT);
1848       fPhysicalOffsetPx.y := GetDeviceCaps(fPtrHdl,PHYSICALOFFSETY);
1849       fDefaultLineWidth := (fPrinterPxPerInch.y*25) div 2540; // 0.25 mm
1850       exit; // if a printer was found then that's all that's needed
1851     except
1852       fHasPrinterInstalled := false;
1853     end;
1854 
1855   // ForceScreenResolution or no Printer: use screen resolution
1856   if fHasPrinterInstalled then begin
1857     if (Printer.orientation <> fOrientation) then
1858       Printer.orientation := fOrientation;
1859     fPtrHdl := printer.Handle;
1860     fPhysicalSizePx.X := round(GetDeviceCaps(fPtrHdl, PHYSICALWIDTH) *
1861         screen.pixelsperinch / GetDeviceCaps(fPtrHdl, LOGPIXELSX));
1862     fPhysicalSizePx.Y := round(GetDeviceCaps(fPtrHdl, PHYSICALHEIGHT) *
1863         screen.pixelsperinch / GetDeviceCaps(fPtrHdl, LOGPIXELSY));
1864   end else begin
1865     // if no printer drivers installed use the screen as device context and
1866     // assume A4 page size...
1867     fPtrHdl := 0; //GetDC(0);
1868     fPhysicalSizePx.X := MulDiv(PAPERSIZE_A4_WIDTH*10,screen.pixelsperinch,254);
1869     fPhysicalSizePx.Y := MulDiv(PAPERSIZE_A4_HEIGHT*10,screen.pixelsperinch,254);
1870   end;
1871   //assume 6mm non-printing offsets...
1872   fPhysicalOffsetPx.X := MulDiv(60,screen.pixelsperinch,254);
1873   fPhysicalOffsetPx.Y := MulDiv(60,screen.pixelsperinch,254);
1874   fPrinterPxPerInch.X := screen.pixelsperinch;
1875   fPrinterPxPerInch.Y := screen.pixelsperinch;
1876   //fDefaultLineWidth ==> 0.3 mm
1877   fDefaultLineWidth := (fPrinterPxPerInch.y*3) div 254;
1878   if not fHasPrinterInstalled and (fOrientation = poLandscape) then begin
1879     // no Printer.Orientation -> swap width & height if Landscape page layout
1880     i := fPhysicalSizePx.x;
1881     fPhysicalSizePx.x := fPhysicalSizePx.y;
1882     fPhysicalSizePx.y := i;
1883   end;
1884 end;
1885 
1886 procedure TGDIPages.SetMetaFileForPage(PageIndex: integer; MetaFile: TMetaFile);
1887 var stream: TRawByteStringStream;
1888 begin
1889   if cardinal(PageIndex)>=cardinal(length(fPages)) then
1890     exit;
1891   stream := TRawByteStringStream.Create;
1892   try
1893     MetaFile.SaveToStream(stream);
1894     fPages[PageIndex].MetaFileCompressed := stream.DataString;
1895     CompressSynLZ(fPages[PageIndex].MetaFileCompressed,true);
1896   finally
1897     stream.Free;
1898   end;
1899 end;
1900 
GetMetaFileForPagenull1901 function TGDIPages.GetMetaFileForPage(PageIndex: integer): TMetaFile;
1902 var tmp: RawByteString;
1903     stream: TStream;
1904 begin
1905   if fMetaFileForPage=nil then
1906     fMetaFileForPage := TMetafile.Create else
1907     fMetaFileForPage.Clear;
1908   result := fMetaFileForPage;
1909   if cardinal(PageIndex)>=cardinal(length(fPages)) then
1910     exit;
1911   tmp := fPages[PageIndex].MetaFileCompressed;
1912   CompressSynLZ(tmp,false);
1913   stream := TRawByteStringStream.Create(tmp);
1914   try
1915     fMetaFileForPage.LoadFromStream(stream);
1916   finally
1917     stream.Free;
1918   end;
1919 end;
1920 
PrinterPxToScreenPxXnull1921 function TGDIPages.PrinterPxToScreenPxX(PrinterPx: integer): integer;
1922 begin
1923   if (Self=nil) or (fPrinterPxPerInch.x=0) then
1924     result := 0 else
1925     result := (PrinterPx*screen.pixelsperinch*fZoom) div (fPrinterPxPerInch.x*100);
1926 end;
1927 
TGDIPages.PrinterPxToScreenPxYnull1928 function TGDIPages.PrinterPxToScreenPxY(PrinterPx: integer): integer;
1929 begin
1930   if (Self=nil) or (fPrinterPxPerInch.y=0) then
1931     result := 0 else
1932     result := (PrinterPx*screen.pixelsperinch*fZoom) div (fPrinterPxPerInch.y*100);
1933 end;
1934 
TGDIPages.MmToPrinterPxXnull1935 function TGDIPages.MmToPrinterPxX(mm: integer): integer;
1936 begin
1937   if Self=nil then
1938     result := 0 else
1939     result := ((mm*10) * fPrinterPxPerInch.x) div 254;
1940 end;
1941 
TGDIPages.MmToPrinterPxYnull1942 function TGDIPages.MmToPrinterPxY(mm: integer): integer;
1943 begin
1944   if Self=nil then
1945     result := 0 else
1946     result := ((mm*10) * fPrinterPxPerInch.y) div 254;
1947 end;
1948 
PrinterPxToMmXnull1949 function TGDIPages.PrinterPxToMmX(px: integer): integer;
1950 begin
1951   if (Self=nil) or (fPrinterPxPerInch.x=0) then
1952     result := 0 else
1953     result := (px*254) div (fPrinterPxPerInch.x*10);
1954 end;
1955 
TGDIPages.PrinterPxToMmYnull1956 function TGDIPages.PrinterPxToMmY(px: integer): integer;
1957 begin
1958   if (Self=nil) or (fPrinterPxPerInch.y=0) then
1959     result := 0 else
1960     result := (px*254) div (fPrinterPxPerInch.y*10);
1961 end;
1962 
1963 procedure TGDIPages.ResizeAndCenterPaintbox;
1964 var l,t, i: integer;
1965     siz: TPoint;
1966 begin
1967   if cardinal(page-1)<cardinal(length(fPages)) then
1968     siz := fPages[page-1].SizePx else
1969     siz := fPhysicalSizePx;
1970   // center the paintbox according to the new size
1971   with fPreviewSurface do begin
1972     siz.X := PrinterPxToScreenPxX(siz.X)+GRAY_MARGIN*2;
1973     siz.Y := PrinterPxToScreenPxY(siz.Y)+GRAY_MARGIN*2;
1974     l := Max((Self.ClientWidth - siz.X) div 2,0) - HorzScrollbar.Position;
1975     t := Max((Self.ClientHeight - siz.Y) div 2,0) - VertScrollbar.Position;
1976     SetBounds(l,t,siz.X,siz.Y);
1977   end;
1978   // resize any hot link
1979   for i := 0 to fLinks.Count-1 do
1980     TGDIPagereference(fLinks.Objects[i]).ToPreview(Self);
1981 end;
1982 
TGDIPages.GetOrientationnull1983 function TGDIPages.GetOrientation: TPrinterOrientation;
1984 begin
1985   if (Self=nil) or (fPhysicalSizePx.x > fPhysicalSizePx.y) then
1986     result := poLandscape else
1987     result := poPortrait;
1988 end;
1989 
1990 procedure TGDIPages.SetTextAlign(Value: TTextAlign);
1991 begin
1992   if Self<>nil then
1993     fAlign := Value;
1994 end;
1995 
1996 procedure TGDIPages.SetOrientation(orientation: TPrinterOrientation);
1997 begin
1998   if (Self<>nil) and (fOrientation<>orientation) then begin
1999     fOrientation := orientation;
2000     if fPages<>nil then begin
2001       // changed orientation after start writing -> customize with inversed size
2002      fCustomPageSize.X := fPhysicalSizePx.Y;
2003      fCustomPageSize.Y := fPhysicalSizePx.X;
2004      fCustomPxPerInch.X := fPrinterPxPerInch.Y;
2005      fCustomPxPerInch.Y := fPrinterPxPerInch.X;
2006      fCustomNonPrintableOffset.X := fPhysicalOffsetPx.Y;
2007      fCustomNonPrintableOffset.Y := fPhysicalOffsetPx.X;
2008     end;
2009   end;
2010 end;
2011 
2012 procedure TGDIPages.NewPageLayout(sizeWidthMM, sizeHeightMM: integer;
2013   nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1);
2014 begin
2015   if Self=nil then
2016     exit;
2017   fCustomPageSize.X := MmToPrinterPxX(sizeWidthMM);
2018   fCustomPageSize.Y := MmToPrinterPxY(sizeHeightMM);
2019   if (nonPrintableWidthMM>=0) and (nonPrintableHeightMM>=0) then begin
2020     fCustomNonPrintableOffset.X := MmToPrinterPxX(nonPrintableWidthMM);
2021     fCustomNonPrintableOffset.Y := MmToPrinterPxY(nonPrintableHeightMM);
2022   end;
2023   if fPages<>nil then // force new page if already some content
2024     NewPage(true);
2025 end;
2026 
2027 procedure TGDIPages.NewPageLayout(paperSize: TGdiPagePaperSize;
2028   orientation: TPrinterOrientation=poPortrait;
2029   nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1);
2030 var Siz,NonPrint: TPoint;
2031 const // psA4, psA5, psA3, psLetter, psLegal
2032   SIZES: array[TGdiPagePaperSize] of TPoint = (
2033    (x:210;y:297),(x:148;y:210),(x:297;y:420),(x:216;y:279),(x:216;y:356));
2034 begin
2035   if orientation=poPortrait then begin
2036     Siz := SIZES[paperSize];
2037     NonPrint.X := nonPrintableWidthMM;
2038     NonPrint.Y := nonPrintableHeightMM;
2039   end else begin
2040     Siz.X := SIZES[paperSize].Y;
2041     Siz.Y := SIZES[paperSize].X;
2042     NonPrint.X := nonPrintableHeightMM;
2043     NonPrint.Y := nonPrintableWidthMM;
2044   end;
2045   NewPageLayout(Siz.X,Siz.Y,NonPrint.X,NonPrint.Y);
2046 end;
2047 
2048 procedure TGDIPages.SetPage(NewPreviewPage: integer);
2049 begin
2050   if Self=nil then exit;
2051   if NewPreviewPage > length(fPages) then
2052     NewPreviewPage := length(fPages) else
2053   if NewPreviewPage < 1 then
2054     NewPreviewPage := 1;
2055   if (Pages = nil) or (fCurrPreviewPage = NewPreviewPage) then
2056     exit;
2057   fCurrPreviewPage := NewPreviewPage;
2058   fLinksCurrent := -1;
2059   FreeAndNil(PreviewSurfaceBitmap); // force double buffering Bitmap recreate
2060   ResizeAndCenterPaintbox; // if page size changed
2061   PreviewPaint(Self);
2062   if Assigned(fPreviewPageChangedEvent) then
2063     fPreviewPageChangedEvent(Self);
2064   if PreviewForm<>nil then begin
2065     PreviewPageCountLabel.Caption := format(sPageN,[Page,PageCount]);
2066     PreviewButtons[ord(rNextPage)-1].Enabled := Page<PageCount;
2067     PreviewButtons[ord(rPreviousPage)-1].Enabled := Page>1;
2068   end;
2069 end;
2070 
TGDIPages.GetPageCountnull2071 function TGDIPages.GetPageCount: integer;
2072 begin
2073   if Self=nil then
2074     result := 0 else
2075     result := length(fPages);
2076 end;
2077 
GetLineHeightnull2078 function TGDIPages.GetLineHeight: integer;
2079 var tm: TTextMetric;
2080     DC: HDC;
2081 begin
2082   if Self=nil then begin
2083     result := 0;
2084     exit;
2085   end;
2086   if fLineHeight = 0 then begin
2087     if not Assigned(fCanvas) then begin
2088       // if no current fCanvas: use the Screen resolution (very fast)
2089       DC := GetDC(0);
2090       GetTextMetrics(DC,tm);
2091       ReleaseDC(0,DC);
2092     end else
2093       GetTextMetrics(fCanvas.Handle,tm);
2094     fLineHeight := ((tm.tmHeight+tm.tmExternalLeading)*9)shr 3;
2095   end;
2096   if fInHeaderOrFooter then
2097     result := fLineHeight else
2098     case fLineSpacing of
2099       lsSingle:     result := fLineHeight;
2100       lsOneAndHalf: result := (fLineHeight*3) shr 1;
2101       else          result := fLineHeight*2;
2102     end;
2103 end;
2104 
TGDIPages.GetLineHeightMmnull2105 function TGDIPages.GetLineHeightMm: integer;
2106 begin
2107   if Self=nil then
2108     result := 0 else
2109     result := PrinterPxToMmY(GetLineHeight);
2110 end;
2111 
2112 procedure TGDIPages.CheckHeaderDone;
2113 begin
2114   if not fHeaderDone then
2115     DoHeader;
2116 end;
2117 
2118 procedure TGDIPages.CheckYPos;
2119 begin
2120   if Self=nil then exit;
2121   if fInHeaderOrFooter then exit;
2122   CheckHeaderDone;
2123   if not HasSpaceForLines(1) then begin
2124     NewPageInternal;
2125     // nb: header is done inside a group, so we must check for it
2126     CheckHeaderDone;
2127   end;
2128 end;
2129 
GetYPosnull2130 function TGDIPages.GetYPos: integer;
2131 begin
2132   if (Self=nil) or (fPrinterPxPerInch.y=0) then
2133     result := 0 else
2134     result := (fCurrentYPos*254) div (fPrinterPxPerInch.y*10);
2135 end;
2136 
2137 procedure TGDIPages.SetYPos(YPos: integer);
2138 begin
2139   if Self=nil then exit;
2140   if fCurrentYPos >= fPhysicalSizePx.y then
2141     NewPageInternal;
2142   fCurrentYPos := MmToPrinterPxY(YPos);
2143 end;
2144 
GetSavedStatenull2145 function TGDIPages.GetSavedState: TSavedState;
2146 begin
2147   with result do begin
2148     Flags := TextFormatsToFlags;
2149     FontName := Font.Name;
2150     FontColor := Font.Color;
2151     LeftMargin := fPageMarginsPx.Left;
2152     RightMargin := fPageMarginsPx.Right;
2153     BiDiMode := fBiDiMode;
2154   end;
2155 end;
2156 
2157 procedure TGDIPages.SetSavedState(const SavedState: TSavedState);
2158 begin
2159   with SavedState do begin
2160     SetFontWithFlags(Flags);
2161     Font.Name := FontName;
2162     Font.Color := FontColor;
2163     fPageMarginsPx.Left := LeftMargin;
2164     fPageMarginsPx.Right := RightMargin;
2165     fBiDiMode := BiDiMode;
2166   end;
2167 end;
2168 
2169 procedure TGDIPages.SaveLayout;
2170 begin
2171   if Self=nil then exit; // avoid GPF
2172   if fSavedCount>=length(fSaved) then
2173     SetLength(fSaved,fSavedCount+20);
2174   fSaved[fSavedCount] := SavedState;
2175   inc(fSavedCount);
2176 end;
2177 
2178 procedure TGDIPages.RestoreSavedLayout;
2179 begin
2180   if Self=nil then exit; // avoid GPF
2181   if fSavedCount<=0 then
2182     exit;
2183   dec(fSavedCount);
2184   SavedState := fSaved[fSavedCount];
2185 end;
2186 
TGDIPages.CreateMetaFilenull2187 function TGDIPages.CreateMetaFile(aWidth, aHeight: integer): TMetaFile;
2188 begin
2189   result := TMetafile.Create;
2190   if Self=nil then exit;
2191   result.Enhanced := true;
2192   result.Width := aWidth;
2193   result.Height := aHeight;
2194 end;
2195 
2196 procedure TGDIPages.FlushPageContent;
2197 var n: integer;
2198 begin
2199   n := length(fPages);
2200   if n>0 then begin
2201     with fPages[n-1] do begin
2202       Text := fCanvasText;
2203       SizePx := fPhysicalSizePx;
2204       MarginPx := fPageMarginsPx;
2205       OffsetPx := fPhysicalOffsetPx;
2206     end;
2207     if fCurrentMetaFile<>nil then begin
2208       SetMetaFileForPage(n-1,fCurrentMetaFile);
2209       FreeAndNil(fCurrentMetaFile);
2210     end;
2211   end;
2212 end;
2213 
2214 procedure TGDIPages.NewPageInternal;
2215 var n: integer;
2216     UsedGroupSpace: integer;
2217     InGroup: boolean;
2218     GroupText: string;
2219 begin
2220   if Self=nil then exit;
2221   UsedGroupSpace := 0; //stops a warning
2222   InGroup := Assigned(fGroupPage);
2223   if InGroup then begin // close the Group Canvas
2224     UsedGroupSpace := fCurrentYPos;
2225     FreeAndNil(fCanvas); // now recreate/redraw a fresh fCanvas for DoFooter
2226     fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
2227     fCanvas.Draw(0,0,fCurrentMetaFile); // re-draw last page
2228     GroupText := fCanvasText;
2229     fCanvasText := fBeforeGroupText;
2230   end;
2231   DoFooter;
2232   //create a new metafile and its canvas ...
2233   if Assigned(fCanvas) then
2234     FreeAndNil(fCanvas);
2235   FlushPageContent;
2236   SetAnyCustomPagePx;
2237   //NewPage.MMWidth := (fPhysicalSizePx.x*2540) div fPrinterPxPerInch.x;
2238   //NewPage.MMHeight := (fPhysicalSizePx.y*2540) div fPrinterPxPerInch.y;
2239   n := Length(fPages)+1;
2240   SetLength(fPages,n);
2241   fCurrentMetaFile := CreateMetaFile(fPhysicalSizePx.x,fPhysicalSizePx.y);
2242   fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
2243   fCanvasText := '';
2244   inc(fVirtualPageNum);
2245   fCurrentYPos := fPageMarginsPx.top;
2246   if Assigned(fStartNewPage) then
2247     fStartNewPage(Self,n);
2248   fHeaderDone := false;
2249   fColumnHeaderPrinted := false; // when next col. started add header
2250   if InGroup then begin // draw the group at the begining of new page + EndGroup
2251     DoHeader;
2252     if fColumnsUsedInGroup then begin
2253       //The next line is a workaround to stop an endless loop. CheckYPos (called
2254       //via PrintColumnHeaders) thinks we're still drawing on fGroupPage as it's
2255       //still Assigned so can flag "out of room" and try to create another page.
2256       fGroupVerticalSpace := fPhysicalSizePx.y;
2257       if not fColumnHeaderInGroup then
2258         PrintColumnHeaders else
2259         fColumnHeaderPrinted := true;
2260     end;
2261     fCanvas.Draw(0,fCurrentYPos,fGroupPage);
2262     FreeAndNil(fGroupPage); // idem as EndGroup
2263     inc(fCurrentYPos,UsedGroupSpace);
2264     fCanvasText := fCanvasText+GroupText;
2265   end;
2266 end;
2267 
CreateMetafileCanvasnull2268 function TGDIPages.CreateMetafileCanvas(Page: TMetafile): TMetafileCanvas;
2269 begin
2270   result := TMetafileCanvas.Create(Page,fPtrHdl);
2271   if Self=nil then exit;
2272   UpdateMetafileCanvasFont(result);
2273   result.Pen.Width := fPrinterPxPerInch.y div screen.PixelsPerInch;
2274 end;
2275 
2276 procedure TGDIPages.UpdateMetafileCanvasFont(aCanvas: TMetafileCanvas);
2277 begin
2278   // next 2 lines are a printer bug workaround - 23Mar2000
2279   aCanvas.Font.Size := Font.Size+1;
2280   aCanvas.Font.PixelsPerInch := fPrinterPxPerInch.y;
2281   aCanvas.Font := Font;
2282 end;
2283 
TextFormatsToFlagsnull2284 function TGDIPages.TextFormatsToFlags: integer;
2285 begin
2286   result := min(max(font.size,4),FORMAT_SIZE_MASK); { size between 4 and 255 }
2287   case fAlign of
2288     taRight:     result := result or FORMAT_RIGHT;
2289     taCenter:    result := result or FORMAT_CENTER;
2290     taJustified: result := result or FORMAT_JUSTIFIED;
2291   end;
2292   if fsBold in font.style then
2293     result := result or FORMAT_BOLD;
2294   if fsUnderline in font.style then
2295     result := result or FORMAT_UNDERLINE;
2296   if fsItalic in font.style then
2297     result := result or FORMAT_ITALIC;
2298 end;
2299 
2300 procedure TGDIPages.SetFontWithFlags(flags: integer);
2301 var fontstyle: TFontStyles;
2302 begin
2303   if flags and FORMAT_SIZE_MASK<>Font.Size then
2304     Font.size := flags and FORMAT_SIZE_MASK;
2305   if (flags and FORMAT_BOLD) <> 0 then
2306     fontstyle := [fsBold] else
2307     fontstyle := [];
2308   if (flags and FORMAT_UNDERLINE) <> 0 then
2309     include(fontstyle,fsUnderline);
2310   if (flags and FORMAT_ITALIC) <> 0 then
2311     include(fontstyle,fsItalic);
2312   if Font.Style<>fontstyle then
2313     Font.Style := fontstyle;
2314   case flags and FORMAT_ALIGN_MASK of
2315     FORMAT_RIGHT:     falign := taRight;
2316     FORMAT_CENTER:    falign := taCenter;
2317     FORMAT_JUSTIFIED: falign := taJustified;
2318     else              falign := taLeft;
2319   end;
2320 end;
2321 
TGDIPages.HasSpaceForLinesnull2322 function TGDIPages.HasSpaceForLines(Count: integer): boolean;
2323 begin
2324   if Self=nil then
2325     result := false else // avoid GPF
2326   if Assigned(fGroupPage) then
2327     result := fCurrentYPos + GetLineHeight*Count < fGroupVerticalSpace else
2328     result := fCurrentYPos + GetLineHeight*Count <
2329       fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight;
2330 end;
2331 
HasSpaceFornull2332 function TGDIPages.HasSpaceFor(mm: integer): boolean;
2333 begin
2334   if Self=nil then
2335     result := false else begin // avoid GPF
2336     mm := fCurrentYPos + MmToPrinterPxY(mm);
2337     if Assigned(fGroupPage) then
2338       result := mm < fGroupVerticalSpace else
2339       result := mm < fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight;
2340   end;
2341 end;
2342 
2343 procedure TGDIPages.DoHeader;
2344 begin
2345   fHeaderDone := true;
2346   if (fHeaderLines.Count = 0) then exit;
2347   SaveLayout;
2348   if Assigned(fStartPageHeader) then
2349     fStartPageHeader(Self);
2350   Font.Color := clBlack;
2351   DoHeaderFooterInternal(fHeaderLines);
2352   if Assigned(fEndPageHeader) then
2353     fEndPageHeader(Self);
2354   GetLineHeight;
2355   inc(fCurrentYPos,fLineHeight shr 2); // add a small header gap
2356   fHeaderHeight := fCurrentYPos-fPageMarginsPx.Top;
2357   RestoreSavedLayout;
2358 end;
2359 
2360 procedure TGDIPages.DoFooter;
2361 begin
2362   if (fFooterLines.Count = 0) then exit;
2363   SaveLayout;
2364   fCurrentYPos :=
2365     fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight + fFooterGap;
2366   if Assigned(fStartPageFooter) then
2367     fStartPageFooter(Self);
2368   DoHeaderFooterInternal(fFooterLines);
2369   if Assigned(fEndPageFooter) then
2370     fEndPageFooter(Self);
2371   RestoreSavedLayout;
2372 end;
2373 
2374 procedure TGDIPages.DoHeaderFooterInternal(Lines: TObjectList);
2375 var i: integer;
2376 begin
2377   SaveLayout;
2378   fInHeaderOrFooter := true;
2379   try
2380     for i := 0 to Lines.Count -1 do
2381       with THeaderFooter(Lines[i]) do
2382       begin
2383         SavedState := State;
2384         PrintFormattedLine(Text, State.Flags);
2385       end;
2386   finally
2387     fInHeaderOrFooter := false;
2388     RestoreSavedLayout;
2389   end;
2390 end;
2391 
2392 procedure TGDIPages.CalcFooterGap;
2393 begin
2394   GetLineHeight;
2395   // make sure there's a gap of at least 1/4 of a lineheight
2396   // between the page body and the footer ...
2397   fFooterGap := fLineHeight shr 2;
2398   fFooterHeight := fFooterGap;
2399 end;
2400 
GetColumnRecnull2401 function TGDIPages.GetColumnRec(col: integer): TColRec;
2402 begin
2403   result.ColLeft := 0;
2404   result.ColRight := 0;
2405   if Cardinal(col)<Cardinal(length(fColumns)) then
2406     result := fColumns[col];
2407 end;
2408 
2409 procedure TGDIPages.PrintColumnHeaders;
2410 var
2411   i,SavedFontSize,FontCol: integer;
2412   SavedFontStyle: TFontStyles;
2413   SavedAlign: TTextAlign;
2414   SavedWordWrapLeftCols: boolean;
2415 
2416 begin
2417   if (fColumnHeaderList = nil) or (fColumns=nil) then exit;
2418   CheckYPos;
2419 
2420   fColumnHeaderPrinted := true;   //stops an endless loop
2421   SavedFontSize := Font.size;
2422   SavedFontStyle := font.style;
2423   SavedAlign := fAlign;
2424   SavedWordWrapLeftCols := WordWrapLeftCols;
2425   WordWrapLeftCols := false;
2426 
2427   if Assigned(fStartColumnHeader) then
2428     fStartColumnHeader(Self);
2429   FontCol := fCanvas.Font.Color;
2430   for i := 0 to High(fColumnHeaderList) do begin
2431     SetFontWithFlags(fColumnHeaderList[i].flags);
2432     fCanvas.Font.Color := clBlack;
2433     fDrawTextAcrossColsDrawingHeader := true;
2434     DrawTextAcrossCols(fColumnHeaderList[i].headers,[],clNone);
2435     fDrawTextAcrossColsDrawingHeader := false;
2436   end;
2437   fCanvas.Font.Color := FontCol;
2438   if Assigned(fEndColumnHeader) then
2439     fEndColumnHeader(Self);
2440   // add a small space below the column headers
2441   // inc(fCurrentYPos,fLineHeight shr 2);
2442 
2443   Font.Size := SavedFontSize;
2444   Font.Style := SavedFontStyle;
2445   fAlign := SavedAlign;
2446   WordWrapLeftCols := SavedWordWrapLeftCols;
2447   if Assigned(fGroupPage) then
2448     fColumnHeaderInGroup := true;
2449   fColumnHeaderPrintedAtLeastOnce :=
2450     ForceCopyTextAsWholeContent; // don't reproduce headers every page
2451 end;
2452 
2453 procedure TGDIPages.SetZoomStatus(aZoomStatus: TZoomStatus);
2454 var zoom: integer;
2455 begin
2456   if (self=nil) or (aZoomStatus=fZoomStatus) then
2457     exit;
2458   case aZoomStatus of
2459     zsPageFit:   zoom := PAGE_FIT;
2460     zsPageWidth: zoom := PAGE_WIDTH;
2461     else         zoom := fZoom;
2462   end;
2463   SetZoom(zoom);
2464 end;
2465 
2466 procedure TGDIPages.SetZoom(Zoom: integer);
2467 var scrollIncrement, zoomW, zoomH: integer;
2468     siz: TPoint;
2469 begin
2470   if (Self=nil) or (zoom < PAGE_FIT) or (zoom in [0..9]) or (zoom > 200) then
2471     exit;
2472   fLinksCurrent := -1;
2473   FreeAndNil(PreviewSurfaceBitmap);
2474 
2475   if (not handleallocated) or (fZoom=Zoom) or
2476      (cardinal(page-1)>=cardinal(length(fPages))) then
2477     exit;
2478 
2479   // calculate the new fZoom ...
2480   siz := fPages[page-1].SizePx;
2481   if (siz.x=0) or (siz.y=0) then  // in case of potential div per 0 -> do it later
2482     exit else
2483   if zoom = PAGE_FIT then begin
2484     ZoomW := trunc((clientWidth-GRAY_MARGIN*2)*fPrinterPxPerInch.x*
2485                100/siz.x/screen.pixelsperinch);
2486     ZoomH := trunc((clientHeight-GRAY_MARGIN*2)*fPrinterPxPerInch.y*
2487                100/siz.y/screen.pixelsperinch);
2488     //choose the smallest of width% and height% to fit on page (but min 10%)
2489     fZoom := Max(Min(ZoomW,ZoomH),10);
2490   end else
2491   if zoom = PAGE_WIDTH then
2492     fZoom := trunc((clientWidth-GRAY_MARGIN*2)*fPrinterPxPerInch.x*
2493                100/siz.x/screen.pixelsperinch) else
2494     fZoom := Zoom;
2495 
2496   // ZoomStatus required when resizing...
2497   if zoom = PAGE_FIT then
2498     fZoomStatus := zsPageFit else
2499   if zoom = PAGE_WIDTH then
2500     fZoomStatus := zsPageWidth else
2501     fZoomStatus := zsPercent;
2502 
2503   scrollIncrement := PrinterPxToScreenPxY(GetLineHeight);
2504   HorzScrollbar.Increment := scrollIncrement;
2505   VertScrollbar.Increment := scrollIncrement;
2506 
2507   // resize and center preview surface...
2508   ResizeAndCenterPaintbox;
2509 
2510   if Assigned(fZoomChangedEvent) then
2511     fZoomChangedEvent(Self, fZoom, fZoomStatus);
2512 end;
2513 
2514 const
2515   ZOOMSTEP = 20;
2516 
2517 procedure TGDIPages.ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
2518 var
2519   OldZoom: integer;
2520   pt, siz: TPoint;
2521 begin
2522   if (Self=nil) or (fPhysicalSizePx.x=0) or (fPhysicalSizePx.y=0) then
2523     Exit;
2524   if page>0 then
2525     siz := fPages[page-1].SizePx else
2526     siz := fPhysicalSizePx;
2527   OldZoom := fZoom;
2528   sendmessage(handle,WM_SETREDRAW,0,0);
2529   try
2530     if ZoomIn then begin
2531 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
2532       if fZoom >= 200 then
2533         fZoomTimer.enabled := false else      //(maximum 200%)
2534 {$else}if fZoom < 200 then {$endif}
2535         Zoom := ((fZoom + ZOOMSTEP) div ZOOMSTEP)*ZOOMSTEP;                //to nearest ZOOMSTEP%
2536     end else begin
2537       if (fZoom > 20) then
2538         Zoom := ((fZoom - ZOOMSTEP) div ZOOMSTEP)*ZOOMSTEP else //(minimum 20%)
2539 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
2540         fZoomTimer.enabled := false;
2541 {$endif}
2542     end;
2543     if fZoom = OldZoom then
2544       exit;
2545     // work out click pos relative to page (as x & y percentages)
2546     pt.x := ((X-fPreviewSurface.left-GRAY_MARGIN)*100) div PrinterPxToScreenPxX(siz.x);
2547     pt.x := min(max(pt.x,0),100);
2548     pt.y := ((Y-fPreviewSurface.top-GRAY_MARGIN)*100) div PrinterPxToScreenPxY(siz.y);
2549     pt.y := min(max(pt.y,0),100);
2550     // finally, adjust scrollbar positions based on click pos ...
2551     with HorzScrollbar do position := (pt.x*(range-clientwidth)) div 100;
2552     with VertScrollbar do position := (pt.y*(range-clientheight)) div 100;
2553   finally
2554     SendMessage(handle,WM_SETREDRAW,1,0);
2555   end;
2556   Invalidate;
2557 end;
2558 
2559 procedure TGDIPages.ZoomTimer(Sender: TObject);
2560 var
2561   CursorPos: TPoint;
2562 begin
2563   GetCursorPos(CursorPos);
2564   CursorPos := ScreenToClient(CursorPos);
2565   ZoomTimerInternal(CursorPos.x,CursorPos.y, fZoomIn);
2566 end;
2567 
2568 procedure TGDIPages.LineInternal(start,finish: integer; DoubleLine: boolean);
2569 var
2570   Y: integer;
2571 begin
2572   if (Self<>nil) and (fCanvas<>nil) then
2573   with fCanvas do begin
2574     Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size,8);
2575     if fsBold in Self.Font.style then Pen.Width := Pen.Width +1;
2576     if DoubleLine then begin
2577       Y := fCurrentYPos + (GetLineHeight shr 1) - (Pen.Width);
2578       MoveTo(start,Y);
2579       LineTo(finish,Y);
2580       MoveTo(start,Y + Pen.Width*2);
2581       LineTo(finish,Y + Pen.Width*2);
2582     end else begin
2583       Y := fCurrentYPos + (GetLineHeight shr 1) - (Pen.Width shr 1);
2584       MoveTo(start,Y);
2585       LineTo(finish,Y);
2586     end;
2587   end;
2588 end;
2589 
2590 procedure TGDIPages.PrintFormattedLine(s: SynUnicode; flags: integer;
2591   const aBookmark: string; const aLink: string; withNewLine: boolean);
2592 var i, xpos: integer;
2593     leftOffset, rightOffset: integer;
2594 begin
2595   s := RightTrim(s);
2596   i := pos(PAGENUMBER,LowerCaseU(s));
2597   if i > 0 then begin
2598     delete(s,i,14);
2599     insert(UTF8ToSynUnicode(Int32ToUtf8(fVirtualPageNum)),s,i);
2600   end;
2601   if flags <> FORMAT_DEFAULT then
2602     SetFontWithFlags(flags);
2603   CheckYPos;
2604   fCurrentTextTop := fCurrentYPos;
2605   fCurrentTextPage := PageCount;
2606   GetTextLimitsPx(leftOffset,rightOffset);
2607   if flags and (FORMAT_SINGLELINE or FORMAT_DOUBLELINE)<>0 then begin
2608     LineInternal(leftOffset,rightOffset,flags and FORMAT_DOUBLELINE=FORMAT_DOUBLELINE);
2609     NewLine;
2610   end else
2611   if s = '' then begin
2612     if withNewLine then
2613       NewLine;
2614   end else
2615   if (flags and FORMAT_XPOS_MASK <> 0) then begin
2616     xpos := ((flags and FORMAT_XPOS_MASK) shr 16)-2;
2617     if xpos<0 then
2618       xpos := RightMarginPos else
2619       inc(xpos);
2620     DrawTextAt(s,xpos);
2621   end else
2622   if (falign in  [taLeft,taJustified]) then
2623     LeftOrJustifiedWrap(s,withNewLine) else
2624     RightOrCenterWrap(s);
2625   if aBookmark<>'' then
2626     AddBookMark(aBookmark,fCurrentTextTop);
2627   if aLink<>'' then
2628     AddLink(aLink,Rect(PrinterPxToMmX(leftOffset),PrinterPxToMmY(fCurrentTextTop),
2629       PrinterPxToMmX(rightOffset),PrinterPxToMmY(fCurrentTextTop+fLineHeight)),
2630       fCurrentTextPage);
2631     // first line of written text is added
2632 end;
2633 
2634 procedure TGDIPages.LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean);
2635 var indent, leftOffset, rightOffset, LineWidth: integer;
2636     leftstring, rightstring: SynUnicode;
2637     firstLoop: boolean;
2638 begin
2639   leftstring := s;
2640   Indent := MmToPrinterPxX(fHangIndent);
2641   firstLoop := true;
2642   repeat
2643     CheckYPos;
2644     GetTextLimitsPx(leftOffset,rightOffset);
2645     LineWidth := rightOffset-leftOffset;
2646 
2647     // offset leftOffset if hang-indenting...
2648     if Indent<>0 then
2649       if firstLoop then begin
2650         firstLoop := false;
2651         if (Indent < 0) then begin
2652           inc(leftOffset,-Indent);
2653           dec(LineWidth,-Indent);
2654         end;
2655       end else
2656       if (Indent > 0) and (Indent < LineWidth) then begin
2657         inc(leftOffset,Indent);
2658         dec(LineWidth,Indent);
2659       end;
2660 
2661     // dump overrun into rightstring...
2662     TrimLine(fCanvas,leftstring,rightstring,LineWidth);
2663 
2664     // HandleTabsAndPrint: prints leftstring after adjusting for tabs and
2665     // prepending any further text overrun into rightstring ...
2666     HandleTabsAndPrint(leftstring, rightstring, leftOffset, rightOffset);
2667     if length(rightstring)=0 then
2668       break;
2669     leftstring := rightstring;
2670     NewLine;
2671   until false;
2672   if withNewLine then
2673     NewLine;
2674 end;
2675 
2676 procedure TGDIPages.RightOrCenterWrap(const s: SynUnicode);
2677 var i,leftOffset,rightOffset, LineWidth: integer;
2678     leftstring,rightstring: SynUnicode;
2679     offset: integer;
2680 begin
2681   leftstring := s;
2682   // remove tabs and replace by spaces
2683   i := pos(#9,leftstring);
2684   while i > 0 do begin
2685     delete(leftstring,i,1);
2686     insert('    ',leftstring,i);
2687     i := pos(#9,leftstring);
2688   end;
2689   // write text
2690   SetBkMode(fCanvas.Handle,TRANSPARENT);
2691   repeat
2692     GetTextLimitsPx(leftOffset,rightOffset);
2693     LineWidth := rightOffset-leftOffset;
2694     TrimLine(fCanvas,leftstring,rightstring,LineWidth);
2695     case falign of
2696       taRight:  Offset := rightOffset-TextWidthC(fCanvas,leftstring)-1;
2697       taCenter: Offset := leftOffset+
2698         (rightOffset-leftOffset-TextWidthC(fCanvas,leftstring))div 2;
2699       else Offset := 0; // should never happen - ?? add assert
2700     end;
2701     CheckYPos;
2702     TextOut(fCanvas,Offset,fCurrentYPos,leftstring);
2703     if length(rightstring) = 0 then break;
2704     leftstring := rightstring;
2705     NewLine;
2706   until false;
2707   NewLine;
2708 end;
2709 
2710 procedure TGDIPages.GetTextLimitsPx(var LeftOffset, RightOffset: integer);
2711 begin
2712   // Offsets (in Printer pixels) based on current page margins
2713   LeftOffset := fPageMarginsPx.left;
2714   if fForcedLeftOffset <> -1 then
2715     leftOffset := fForcedLeftOffset;
2716   RightOffset := fPhysicalSizePx.x-fPageMarginsPx.right;
2717   if RightOffset < LeftOffset then
2718     raise Exception.Create('GetTextLimitsPx: wrong margins');
2719 end;
2720 
2721 procedure TGDIPages.HandleTabsAndPrint(const leftstring: SynUnicode;
2722   var rightstring: SynUnicode; leftOffset, rightOffset: integer);
2723 const
2724     // if a tabstop is very close to the right margin, it may spoil justifying...
2725     MIN_CHAR_WIDTH_PX = 5;
2726 var i, spacecount, linewidth, tabPos, tabIndex, PWLen: integer;
2727     ls, rs: SynUnicode;
2728     size: TSize;
2729     PW: PWideChar;
2730 begin
2731   // handles tabs one at a time and prints text into the available space...
2732   // (unfortunately there's no equivalent GetTextExtentExPoint() for tabbed text
2733   // and using GetTabbedTextExtent() and TabbedDrawText() instead would appear
2734   // to be undesirable as there's no efficient way to determine the number of
2735   // chars that will fit within the specified space)
2736   ls := leftstring;
2737   linewidth := rightOffset - leftOffset;
2738   tabPos := pos(#9,ls);
2739   SetBkMode(fCanvas.Handle,TRANSPARENT);
2740   while tabPos > 0 do begin // and still room to print
2741     // split line at the tab ...
2742     if rs <> '' then
2743         rs := copy(ls,tabPos+1,length(ls)) + ' '+ rs else
2744         rs := copy(ls,tabPos+1,length(ls));
2745     // add a trailing space so next the tabstop is at least one space away ...
2746     ls := copy(ls,1,tabPos-1)+' ';
2747     // get offset of next tabstop ...
2748     size := TextExtent(fCanvas,ls,tabPos);
2749     i := leftOffset + size.cx; //minimum pos for next tabstop
2750     tabIndex := 0;
2751     while tabIndex < MAXTABS do
2752       if fTab[tabIndex] > i then
2753         break else
2754         inc(tabIndex);
2755     if (tabIndex = MAXTABS) or
2756       (fTab[tabIndex] >= rightOffset - MIN_CHAR_WIDTH_PX) then begin
2757       // no tabstop found to align 'rs' to, so ...
2758       // rather than left aligning 'ls', remove its appended space and
2759       // break out ready to print it ? align left&right justified.
2760       SetLength(ls,length(ls)-1);
2761       break;
2762     end;
2763     // tabstop found so DrawText 'ls' simply left aligned ...
2764     TextOut(fCanvas,leftOffset,fCurrentYPos,ls);
2765     leftOffset := fTab[tabIndex];
2766     linewidth := rightOffset - leftOffset;
2767     ls := rs;
2768     TrimLine(fCanvas,ls,rs,linewidth);
2769     tabPos := pos(#9,ls);
2770   end;
2771   if rs <> '' then
2772     rightstring := rs + ' '+ rightstring;
2773 
2774   // OK, no TABS now in ls...
2775   InternalUnicodeString(ls,PW,PWLen,@size);
2776   // print ls into (remaining) linewidth at (leftOffset, fCurrentYPos)
2777   if (falign = taLeft) or (rightstring = '') then begin // left aligned
2778     if BiDiMode=bdRightToLeft then
2779       leftOffset := rightOffset-size.cx;
2780     TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);
2781     fForcedLeftOffset := leftOffset+size.cx;
2782     // don't care about line width: it should be always equal or smaller,
2783     // and we are left aligned
2784   end else begin // justified
2785     spacecount := 0;
2786     for i := 1 to length(ls) do
2787       if ls[i] = ' ' then
2788         inc(spacecount);
2789     if spacecount>0 then
2790       SetTextJustification(fCanvas.Handle, linewidth - size.cx, spacecount);
2791     TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);
2792     SetTextJustification(fCanvas.Handle,0,0);
2793   end;
2794 end;
2795 
2796 procedure TGDIPages.PreviewPaint(Sender: TObject);
2797 var R: TRect;
2798     P1,P2: TPoint;
2799 begin
2800   if csDesigning in ComponentState then begin // no preview at design time
2801     R := fPreviewSurface.ClientRect;
2802     fPreviewSurface.Canvas.Brush.Color := Color;
2803     fPreviewSurface.Canvas.FillRect(R);
2804     exit;
2805   end;
2806   if not Visible then begin
2807     FreeAndNil(PreviewSurfaceBitmap);
2808     exit;
2809   end;
2810   if PreviewSurfaceBitmap<>nil then
2811     fPreviewSurface.Canvas.Draw(0,0,PreviewSurfaceBitmap) else
2812   with fPreviewSurface do begin
2813     // paint the page white with a dark gray line around it
2814     R := ClientRect;
2815     PreviewSurfaceBitmap := TBitmap.Create;
2816     PreviewSurfaceBitmap.Width := R.Right;
2817     PreviewSurfaceBitmap.Height := R.Bottom;
2818     with PreviewSurfaceBitmap.Canvas do begin
2819       Brush.Color := Color; // background color
2820       FillRect(R);
2821       InflateRect(R,-GRAY_MARGIN,-GRAY_MARGIN);
2822       Brush.Color := clWhite;
2823       Pen.Width := 1;
2824       Pen.Color := clGray;
2825       Rectangle(R);
2826       Refresh;
2827     end;
2828     // draw the metafile on the page
2829     if (fPages<>nil) and (cardinal(Page-1)<=cardinal(High(fPages))) and
2830        (fPages[Page-1].MetaFileCompressed<>'') then begin
2831 {$ifdef GDIPLUSDRAW} // anti aliased drawing:
2832       if not ForceNoAntiAliased then
2833         DrawEmfGdip(PreviewSurfaceBitmap.Canvas.Handle,
2834           GetMetaFileForPage(Page-1),R,ForceInternalAntiAliased,
2835           ForceInternalAntiAliasedFontFallBack) else
2836 {$endif} begin // fast direct GDI painting, with no antialiaising:
2837         // note: we must use a temporary TMetaFile, otherwise the Pages[] content
2838         // is changed (screen dpi is changed but not reset in nested emf) and the
2839         // resulting report is incorrect on most printers, due to a driver bug :(
2840         PreviewSurfaceBitmap.Canvas.StretchDraw(R,GetMetaFileForPage(Page-1));
2841       end;
2842       PreviewSurfaceBitmap.Canvas.Refresh;
2843     end;
2844     // draw the change page grey "arrow" buttons
2845     if Page>1 then begin
2846       P1.X := R.Left+10;
2847       P2.X := R.Left+1;
2848       PageLeftButton.X := P2.X;
2849       P1.Y := R.Top+11;
2850       P2.Y := P1.Y;
2851       PageLeftButton.Y := P1.Y-10;
2852       DrawArrowInternal(PreviewSurfaceBitmap.Canvas,P1,P2,10,true);
2853     end else
2854       PageLeftButton.X := 0;
2855     if Page<PageCount then begin
2856       P1.X := R.Right-10;
2857       PageRightButton.X := P1.X;
2858       P2.X := R.Right-1;
2859       P1.Y := R.Top+11;
2860       P2.Y := P1.Y;
2861       PageRightButton.Y := P1.Y-10;
2862       DrawArrowInternal(PreviewSurfaceBitmap.Canvas,P1,P2,10,true);
2863     end else
2864       PageRightButton.X := 0;
2865     //draw the page shadows
2866     R.Top := GRAY_MARGIN+3;
2867     R.Left := ClientWidth-GRAY_MARGIN;
2868     R.Bottom := ClientHeight-GRAY_MARGIN+3;
2869     R.Right := R.Left+3;
2870     PreviewSurfaceBitmap.Canvas.brush.color := clGray;
2871     PreviewSurfaceBitmap.Canvas.FillRect(R);
2872     R.Top := ClientHeight-GRAY_MARGIN;
2873     R.Left := GRAY_MARGIN+3;
2874     R.Bottom := R.Top+3;
2875     R.Right := ClientWidth-GRAY_MARGIN+3;
2876     PreviewSurfaceBitmap.Canvas.brush.color := clGray;
2877     PreviewSurfaceBitmap.Canvas.FillRect(R);
2878     Canvas.Draw(0,0,PreviewSurfaceBitmap)
2879   end;
2880   if fLinksCurrent>=0 then
2881     fPreviewSurface.Canvas.DrawFocusRect(
2882       TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
2883 end;
2884 
2885 procedure TGDIPages.PreviewMouseDown(Sender: TObject; Button: TMouseButton;
2886   Shift: TShiftState; X, Y: Integer);
2887 var i: integer;
2888 begin
2889   if Button=mbRight then begin
2890     if PopupMenu<>nil then begin
2891       with fPreviewSurface.ClientToScreen(Point(X,Y)) do
2892         PopupMenu.Popup(X,Y);
2893       exit;
2894     end;
2895   end else
2896   if Button=mbLeft then begin
2897     if fLinksCurrent>=0 then begin
2898       fPreviewSurface.Canvas.DrawFocusRect(
2899         TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
2900       i := fLinksCurrent;
2901       fLinksCurrent := -1;
2902       GotoBookmark(fLinks[i]);
2903     end else
2904     if (PageLeftButton.X<>0) and
2905        (cardinal(X-PageLeftButton.X)<10) and
2906        (cardinal(Y-PageLeftButton.Y)<20) then begin
2907       Page := Page-1;
2908       exit;
2909     end else
2910     if (PageRightButton.X<>0) and
2911        (cardinal(X-PageRightButton.X)<10) and
2912        (cardinal(Y-PageRightButton.Y)<20) then begin
2913       Page := Page+1;
2914       exit;
2915     end;
2916   end;
2917   if (Button=mbLeft) and (ssDouble in Shift) then
2918     // allows dblclick to alternate between PAGE_FIT and PAGE_WIDTH
2919     if ZoomStatus = zsPageWidth then
2920       Zoom := PAGE_FIT else
2921       Zoom := PAGE_WIDTH else
2922 {$ifndef MOUSE_CLICK_PERFORM_ZOOM}
2923   if Button=mbLeft then begin
2924     fButtonDown.X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
2925     fButtonDown.Y := (Y shr 3)shl 3;
2926     fButtonDownScroll.X := HorzScrollBar.Position;
2927     fButtonDownScroll.Y := VertScrollBar.Position;
2928     Screen.Cursor := crHandPoint;
2929   end;
2930 {$endif}
2931   //pass the TPaintbox mouse-down event messages to Self (TScrollBox) ...
2932   MouseDown(Button,Shift,X+fPreviewSurface.left,Y+fPreviewSurface.Top);
2933 end;
2934 
2935 procedure TGDIPages.PreviewMouseUp(Sender: TObject; Button: TMouseButton;
2936   Shift: TShiftState; X, Y: Integer);
2937 begin
2938   //pass the TPaintbox mouse-up event messages to Self (TScrollBox) ...
2939   MouseUp(Button,Shift,X+fPreviewSurface.left,Y+fPreviewSurface.Top);
2940 end;
2941 
2942 procedure TGDIPages.PreviewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
2943 {$ifndef MOUSE_CLICK_PERFORM_ZOOM}
2944 var BX, V: integer;
2945 {$endif}
2946 var i: integer;
2947 begin
2948   fMousePos.X := X+fPreviewSurface.left;
2949   fMousePos.Y := Y+fPreviewSurface.Top;
2950   if fLinksCurrent>=0 then begin
2951     fPreviewSurface.Canvas.DrawFocusRect(
2952       TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
2953     fLinksCurrent := -1;
2954   end;
2955 {$ifndef MOUSE_CLICK_PERFORM_ZOOM}
2956   if fButtonDown.X>=0 then begin
2957     X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
2958     Y := (Y shr 3)shl 3;
2959 {    OutputDebugString(pointer(format(
2960         'X=%d Y=%d ScrlIni X=%d Y=%d ScrlCurr X=%d Y=%d ',
2961         [X,Y,fButtonDownScroll.X,fButtonDownScroll.Y,
2962         HorzScrollBar.Position,VertScrollBar.Position]))); }
2963     BX := fButtonDown.X;
2964     fButtonDown.X := -1; // avoid endless recursive call
2965     V := fButtonDownScroll.X-X+BX;
2966     if (V>=0) and (HorzScrollBar.Position<>V) and (V<HorzScrollBar.Range) then begin
2967       HorzScrollBar.Position := V;
2968       fButtonDownScroll.X := V;
2969     end;
2970     V := fButtonDownScroll.Y-Y+fButtonDown.Y;
2971     if (V>=0) and (VertScrollBar.Position<>V) and (V<VertScrollBar.Range) then begin
2972       VertScrollBar.Position := V;
2973       fButtonDownScroll.Y := V;
2974     end;
2975     fButtonDown.X := BX;
2976     exit;
2977   end else
2978 {$endif}
2979   for i := 0 to fLinks.Count-1 do
2980     with TGDIPagereference(fLinks.Objects[i]) do
2981       if (Page=Self.Page) and (X>=Preview.Left) and (X<Preview.Right) and
2982          (Y>=Preview.Top) and (Y<Preview.Bottom) then begin
2983         fLinksCurrent := i;
2984         fPreviewSurface.Canvas.DrawFocusRect(Preview);
2985         break;
2986       end;
2987 end;
2988 
2989 procedure TGDIPages.CMFontChanged(var Msg: TMessage);
2990 begin
2991   inherited;
2992   if Assigned(fCanvas) then
2993     UpdateMetafileCanvasFont(fCanvas);
2994   fLineHeight := 0; // force recalculation of lineheight
2995 end;
2996 
2997 procedure TGDIPages.WMGetDlgCode(var Message: TWMGetDlgCode);
2998 begin
2999   Message.Result := DLGC_WANTARROWS;
3000 end;
3001 
3002 procedure TGDIPages.KeyDown(var Key: Word; Shift: TShiftState);
3003 
3004   procedure SetPageAndPosition(newpage,newpos: integer);
3005   begin
3006     perform(WM_SETREDRAW,0,0);
3007     Page := newpage;
3008     VertScrollbar.position := newpos;
3009     perform(WM_SETREDRAW,1,0);
3010     refresh;
3011   end;
3012 
3013 var
3014   OldPosition,lh: integer;
3015 begin
3016   lh := PrinterPxToScreenPxY(GetLineHeight);
3017   case Key of
3018   VK_DOWN:
3019     with VertScrollbar do begin
3020       OldPosition := Position;
3021       position := position + lh;
3022       if (Position = OldPosition) and (Page < PageCount) then
3023         SetPageAndPosition(Page+1,0);
3024     end;
3025   VK_UP:
3026     with VertScrollbar do begin
3027       OldPosition := Position;
3028       position := position - lh;
3029       if (Position = OldPosition) and (Page > 1) then
3030         SetPageAndPosition(Page-1,range);
3031     end;
3032   VK_RIGHT:
3033     with HorzScrollbar do
3034       position := position + max(lh,0);
3035   VK_LEFT:
3036     with HorzScrollbar do
3037       position := position - min(lh,range);
3038   VK_NEXT:
3039     with VertScrollbar do
3040       if (shift = [ssCtrl]) and (Page < PageCount) then
3041         SetPageAndPosition(PageCount,0)
3042       else begin
3043         OldPosition := Position;
3044         position := position + max(clientheight - lh,0);
3045         if (Position = OldPosition) and (Page < PageCount) then
3046           SetPageAndPosition(Page+1,0);
3047       end;
3048   VK_PRIOR:
3049     with VertScrollbar do begin
3050       if (shift = [ssCtrl]) and (Page > 1) then
3051           SetPageAndPosition(1,0)
3052       else begin
3053         OldPosition := Position;
3054         position := position - max(clientheight-lh,0);
3055         if (Position = OldPosition) and (Page > 1) then
3056           SetPageAndPosition(Page-1,range);
3057       end;
3058     end;
3059   VK_ADD, VK_SUBTRACT, 187, 189:
3060     if ssCtrl in Shift then begin
3061       fZoomIn := Key in [VK_ADD,187]; // Ctrl+ Ctrl- are standard zoom IN/OUT
3062       ZoomTimer(nil);
3063     end;
3064   VK_ESCAPE:
3065     if PreviewForm<>nil then
3066       PreviewForm.Close; // ESC will close preview form (if any)
3067   end;
3068   inherited;
3069 end;
3070 
3071 procedure TGDIPages.CreateWnd;
3072 begin
3073   inherited CreateWnd;
3074   // force page repositioning  +/-resizing
3075   case ZoomStatus of
3076     zsPercent:   ResizeAndCenterPaintbox;
3077     zsPageWidth: zoom := PAGE_WIDTH;
3078     else         zoom := PAGE_FIT;
3079   end;
3080 end;
3081 
3082 procedure TGDIPages.Resize;
3083 begin
3084   // force page repositioning  +/-resizing
3085   case ZoomStatus of
3086     zsPercent:   ResizeAndCenterPaintbox;
3087     zsPageWidth: zoom := PAGE_WIDTH;
3088     else         zoom := PAGE_FIT;
3089   end;
3090   inherited Resize;
3091 end;
3092 
3093 procedure TGDIPages.MouseDown(Button: TMouseButton;
3094   Shift: TShiftState; X, Y: Integer);
3095 begin
3096 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
3097   // allow overriding of default mouse handling...
3098   if not Assigned(OnMouseDown) then begin
3099     fZoomIn := (Button = mbLeft);
3100     ZoomTimerInternal(X, Y, fZoomIn);
3101     fZoomTimer.Enabled := true;
3102   end;
3103 {$endif}
3104   if Button=mbLeft then begin
3105     if PopupMenu<>nil then begin
3106       with fPreviewSurface do
3107       if (X<Left) or (X>Left+Width) then
3108       with Self.ClientToScreen(Point(X,Y)) do
3109         Self.PopupMenu.Popup(X,Y);
3110     end;
3111   end;
3112   if canfocus and not focused then
3113     Setfocus;
3114   inherited;
3115 end;
3116 
3117 procedure TGDIPages.MouseUp(Button: TMouseButton;
3118   Shift: TShiftState; X, Y: Integer);
3119 begin
3120 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
3121   fZoomTimer.enabled := false;
3122 {$else}
3123   fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
3124   Screen.Cursor := crDefault;
3125 {$endif}
3126   inherited;
3127 end;
3128 
3129 {$IFNDEF VER100}
DoMouseWheelnull3130 function TGDIPages.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
3131       MousePos: TPoint): Boolean;
3132 var key: word;
3133 begin
3134   //treat mousewheel events as if a down-arrow or up-arrow event ...
3135   if Shift=[] then begin
3136     if WheelDelta < 0 then
3137       key := VK_DOWN else
3138       key := VK_UP;
3139     KeyDown(Key,[]);
3140   end else
3141   if Shift=[ssCtrl] then
3142     ZoomTimerInternal(fMousePos.X,fMousePos.Y,(WheelDelta>0));
3143   Result := true;
3144 end;
3145 {$ENDIF}
3146 
3147 constructor TGDIPages.Create(AOwner: TComponent);
3148 {$ifdef USEPDFPRINTER}
3149 var i: integer;
3150     aName: string;
3151 {$endif}
3152 begin
3153   inherited Create(AOwner);
3154   SetLength(fTab,MAXTABS);
3155   {$ifndef WIN64} // Win64 gdiplus.dll raises some unexpected errors
3156   ForceInternalAntiAliased := true; // GDI+ 1.1 ConvertToEmfPlus is buggy
3157   {$endif}
3158   PopupMenuClass := TPopupMenu;
3159   // DoubleBuffered := true; // avoiding flicker is done in Paint method
3160   Height := 150;
3161   width := 200;
3162   ControlStyle := ControlStyle - [csAcceptsControls];
3163   if (AOWner<>nil) and AOWner.InheritsFrom(TCustomForm) then
3164     Color := TCustomForm(AOwner).Color else
3165     Color := clLtGray;
3166   HorzScrollBar.Tracking := True;
3167   VertScrollBar.Tracking := True;
3168   tabstop := true;
3169 
3170   Font.Name := 'Tahoma';
3171   Font.Size := 12;
3172   fLineSpacing := lsSingle;
3173   fOrientation := poPortrait;
3174   fUseOutlines := true;
3175 
3176   fHeaderLines := TObjectList.Create;
3177   fFooterLines := TObjectList.Create;
3178 
3179 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
3180   fZoomTimer := TTimer.create(Self);
3181   fZoomTimer.Interval := 200;
3182   fZoomTimer.OnTimer := ZoomTimer;
3183   fZoomTimer.enabled := false;
3184 {$else}
3185   fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
3186 {$endif}
3187   Int64(fCustomPxPerInch) := -1;
3188   Int64(fCustomPageSize) := -1;
3189   Int64(fCustomNonPrintableOffset) := -1;
3190   Int64(fCustomPageMargins.TopLeft) := -1;
3191 
3192   fHasPrinterInstalled := not (csDesigning in componentState)
3193     and PrinterDriverExists;
3194 {$ifdef USEPDFPRINTER}
3195   fPDFPrinterIndex := -1;
3196   if fHasPrinterInstalled then
3197     for i := 0 to Printer.Printers.Count-1 do begin
3198       aName := Printer.Printers[i];
3199       if pos('doPDF',aName)=1 then begin
3200         fPDFPrinterIndex := i;
3201         break;
3202       end else
3203       if pos('PDF',aName)>0 then
3204         fPDFPrinterIndex := i;
3205     end;
3206   fHasPDFPrinterInstalled := (fPDFPrinterIndex<>-1);
3207 {$else}
3208   fExportPDFUseFontFallBack := true;
3209   fExportPDFEncryptionPermissions := PDF_PERMISSION_ALL;
3210   fExportPDFEncryptionOwnerPassword := 'SynopsePDFEngine'+SYNOPSE_FRAMEWORK_VERSION;
3211 {$endif}
3212   GetPrinterParams; // necessary, but will also be updated in BeginDoc()
3213   fCanvas := nil;
3214   fPreviewSurface := TPagePaintbox.Create(Self);
3215   fPreviewSurface.parent := Self;
3216   fPreviewSurface.OnPaint := PreviewPaint;
3217   fPreviewSurface.OnMouseDown := PreviewMouseDown;
3218   fPreviewSurface.OnMouseUp := PreviewMouseUp;
3219   fPreviewSurface.OnMouseMove := PreviewMouseMove;
3220   fZoomStatus := zsPercent;
3221   fZoom := 100;
3222   fBookmarks := TStringList.Create;
3223   fLinks := TStringList.Create;
3224   fOutline := TStringList.Create;
3225   fForcedLeftOffset := -1;
3226 end;
3227 
3228 destructor TGDIPages.Destroy;
3229 begin
3230   Clear;
3231   fHeaderLines.free;
3232   fFooterLines.free;
3233   fPreviewSurface.free;
3234   PreviewSurfaceBitmap.Free;
3235 {$ifdef MOUSE_CLICK_PERFORM_ZOOM}
3236   fZoomTimer.free;
3237 {$endif}
3238   fOutline.Free;
3239   fLinks.Free;
3240   fBookmarks.Free;
3241   fMetaFileForPage.Free;
3242   fCurrentMetaFile.Free;
3243   inherited Destroy;
3244 end;
3245 
3246 procedure TGDIPages.Invalidate;
3247 begin
3248   FreeAndNil(PreviewSurfaceBitmap); // invalidate custom double buffering
3249   inherited;
3250 end;
3251 
3252 procedure TGDIPages.BeginDoc;
3253 begin
3254   if Self=nil then exit; // avoid GPF
3255   Clear;
3256   GetPrinterParams; // essential as Printers.printer object may have changed
3257   fHangIndent := 0;
3258   fAlign := taLeft;
3259   SetPageMargins(Rect(10,10,10,10));
3260   fVirtualPageNum := 0;
3261   Application.ProcessMessages;
3262   NewPageInternal;  // create a blank page
3263   // preview resize in case Printers.printer object has changed
3264   case ZoomStatus of
3265     zsPercent:   zoom := fzoom;
3266     zsPageWidth: zoom := PAGE_WIDTH;
3267     else zoom := PAGE_FIT;
3268   end;
3269   fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
3270 end;
3271 
3272 procedure TGDIPages.DrawText(const s: string; withNewLine : boolean);
3273 begin
3274   DrawTextW(StringToSynUnicode(s), withNewLine);
3275 end;
3276 
3277 procedure TGDIPages.DrawTextW(const s: SynUnicode; withNewLine: boolean);
3278 var P, Start: PWideChar;
3279     tmpStr: SynUnicode;
3280 begin
3281   if Self=nil then exit;
3282   CheckYPos;
3283   if s = '' then begin
3284     if withNewLine then
3285       NewLine;
3286   end else begin
3287     // split NewLine characters (#13 or #13#10) into multi lines
3288     P := pointer(s);
3289     while P^ <> #0 do begin
3290       Start := P;
3291       while not (ord(P^) in [0, 10, 13]) do Inc(P);
3292       SetString(tmpStr, Start, P-Start);
3293       if not fInHeaderOrFooter then
3294         fCanvasText := fCanvasText+SynUnicodeToString(tmpStr)+#13#10;
3295       PrintFormattedLine(tmpStr, FORMAT_DEFAULT, '', '', withNewLine);
3296       if P^ = #13 then Inc(P);
3297       if P^ = #10 then Inc(P);
3298     end;
3299   end;
3300 end;
3301 
3302 procedure TGDIPages.DrawTextU(const s: RawUTF8; withNewLine: boolean);
3303 begin
3304   DrawTextW(UTF8ToSynUnicode(s),withNewLine);
3305 end;
3306 
3307 procedure TGDIPages.DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false;
3308   OutlineLevel: Integer=0; const aBookmark: string=''; const aLink: string='');
3309 var H: integer;
3310     str: string;
3311 begin
3312   if Self=nil then exit; // avoid GPF
3313   CheckYPos;
3314   SaveLayout;
3315   try
3316     str := SynUnicodeToString(s);
3317     if not fInHeaderOrFooter then
3318       fCanvasText := fCanvasText+str+#13#10; // copy as text
3319     PrintFormattedLine(s,TitleFlags,aBookMark,aLink);
3320     if UseOutlines then
3321       AddOutline(str,OutlineLevel,fCurrentTextTop,fCurrentTextPage);
3322     if DrawBottomLine then begin
3323       H := (GetLineHeight*15) shr 5;
3324       dec(fCurrentYPos, H);
3325       LineInternal(fPageMarginsPx.left, fPhysicalSizePx.x-fPageMarginsPx.right, false);
3326       inc(fCurrentYPos, H*2);
3327     end;
3328   finally
3329     RestoreSavedLayout;
3330   end;
3331 end;
3332 
3333 procedure TGDIPages.DrawTextAt(s: SynUnicode; XPos: integer; const aLink: string='';
3334   CheckPageNumber: boolean=false);
3335 var i: integer;
3336     R: TRect;
3337     Size: TSize;
3338 begin
3339   if (Self=nil) or (s='') then exit;
3340   CheckYPos;
3341   if CheckPageNumber then begin
3342     i := pos(PAGENUMBER,LowerCaseU(s));
3343     if i > 0 then begin
3344       Delete(s,i,14);
3345       Insert(UTF8ToSynUnicode(Int32ToUtf8(fVirtualPageNum)),s,i);
3346     end;
3347   end;
3348   SetBkMode(fCanvas.Handle,TRANSPARENT);
3349   Size := TextExtent(fCanvas,s);
3350   R.Left := MmToPrinterPxX(XPos);
3351   case falign of
3352     taRight:  dec(R.Left,Size.cx+1);
3353     taCenter: dec(R.Left,Size.cx shr 1+1);
3354   end;
3355   R.Top := fCurrentYPos;
3356   TextOut(fCanvas,R.Left,R.Top,s);
3357   if not fInHeaderOrFooter then // copy as text on a new line
3358     fCanvasText := fCanvasText+SynUnicodeToString(s)+#13#10;
3359   if aLink<>'' then begin
3360     R.Right := R.Left+Size.cx;
3361     R.Bottom := R.Top+Size.cy;
3362     AddLink(aLink,PrinterToMM(R));
3363   end;
3364 end;
3365 
3366 procedure TGDIPages.DrawAngledTextAt(const s: SynUnicode; XPos, Angle: integer);
3367 var
3368   lf: TLogFont;
3369   OldFontHdl,NewFontHdl: HFont;
3370 begin
3371   if (s='') or (Self=nil) then exit; // avoid GPF
3372   CheckYPos;
3373   XPos := MmToPrinterPxX(XPos);
3374   SetBkMode(fCanvas.Handle,TRANSPARENT);
3375   with fCanvas do begin
3376     if GetObject(Font.Handle, SizeOf(lf), @lf) = 0 then exit;
3377     lf.lfEscapement := Angle * 10;
3378     lf.lfOrientation := Angle * 10;
3379     lf.lfOutPrecision := OUT_TT_ONLY_PRECIS;
3380     NewFontHdl := CreateFontIndirect(lf);
3381     OldFontHdl := selectObject(handle,NewFontHdl);
3382   end;
3383   TextOut(fCanvas,XPos,fCurrentYPos,s);
3384   selectObject(fCanvas.handle,OldFontHdl);
3385   DeleteObject(NewFontHdl);
3386   if not fInHeaderOrFooter then
3387     fCanvasText := fCanvasText+s+#13#10; // copy as text on a new line
3388 end;
3389 
MmToPrinternull3390 function TGDIPages.MmToPrinter(const R: TRect): TRect;
3391 begin
3392   if Self=nil then begin
3393     FillChar(result,sizeof(result),0);
3394     exit; // avoid GPF
3395   end;
3396   result.left := MmToPrinterPxX(R.left);
3397   result.top := MmToPrinterPxY(R.top);
3398   result.right := MmToPrinterPxX(R.right);
3399   result.bottom := MmToPrinterPxY(R.bottom);
3400 end;
3401 
TGDIPages.PrinterToMMnull3402 function TGDIPages.PrinterToMM(const R: TRect): TRect;
3403 begin
3404   if Self=nil then begin
3405     FillChar(result,sizeof(result),0);
3406     exit; // avoid GPF
3407   end;
3408   result.left := PrinterPxToMmX(R.left);
3409   result.top := PrinterPxToMmY(R.top);
3410   result.right := PrinterPxToMmX(R.right);
3411   result.bottom := PrinterPxToMmY(R.bottom);
3412 end;
3413 
3414 procedure TGDIPages.DrawBox(left,top,right,bottom: integer);
3415 begin
3416   if Self=nil then exit; // avoid GPF
3417   CheckHeaderDone;
3418   left := MmToPrinterPxX(left);
3419   top := MmToPrinterPxY(top);
3420   right := MmToPrinterPxX(right);
3421   bottom := MmToPrinterPxY(bottom);
3422   with fCanvas do begin
3423     Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size,8);
3424     if fsBold in Self.Font.style then
3425       Pen.Width := Pen.Width +1;
3426     MoveTo(left,top);
3427     LineTo(right,top);
3428     LineTo(right,bottom);
3429     LineTo(left,bottom);
3430     LineTo(left,top);
3431   end;
3432 end;
3433 
3434 procedure TGDIPages.DrawBoxFilled(left,top,right,bottom: integer; Color: TColor);
3435 var SavedBrushColor: TColor;
3436 begin
3437   if Self=nil then exit; // avoid GPF
3438   CheckHeaderDone;
3439   left := MmToPrinterPxX(left);
3440   top := MmToPrinterPxY(top);
3441   right := MmToPrinterPxX(right);
3442   bottom := MmToPrinterPxY(bottom);
3443   with fCanvas do begin
3444     Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size,8);
3445     if fsBold in Self.Font.style then
3446       Pen.Width := Pen.Width +1;
3447     SavedBrushColor := Brush.Color;
3448     brush.Color := Color;
3449     rectangle(left,top,right,bottom);
3450     Brush.Color := SavedBrushColor;
3451   end;
3452 end;
3453 
3454 procedure TGDIPages.DrawBMP(rec: TRect; bmp: TBitmap);
3455 begin
3456   if Self=nil then exit; // avoid GPF
3457   CheckHeaderDone;
3458   PrintBitmap(fCanvas, MmToPrinter(rec), bmp);
3459 end;
3460 
3461 procedure TGDIPages.DrawBMP(bmp: TBitmap; bLeft, bWidth: integer; const Legend: string);
3462 begin
3463   DrawGraphic(bmp,bLeft,bWidth,Legend);
3464 end;
3465 
3466 procedure TGDIPages.DrawGraphic(graph: TGraphic; bLeft, bWidth: integer;
3467   const Legend: SynUnicode);
3468 var R: TRect;
3469     H: Integer;
3470 begin
3471   if (self=nil) or (graph=nil) or graph.Empty then
3472     exit; // avoid GPF
3473   // compute position and draw bitmap
3474   if bLeft=maxInt then // do center
3475     bLeft := PrinterPxToMmX(fPageMarginsPx.Left+
3476       (fPhysicalSizePx.x-fPageMarginsPx.Right-fPageMarginsPx.Left-MmToPrinterPxX(bWidth))shr 1) else
3477     inc(bLeft,LeftMargin);
3478   R.Left := bLeft;
3479   R.Right := bLeft+bWidth;
3480   R.Bottom := (graph.Height*bWidth) div graph.Width;
3481   if Legend<>'' then
3482     H := LineHeight else
3483     H := 0;
3484   if not HasSpaceFor(R.Bottom+H) then begin
3485     NewPage;
3486     DoHeader;
3487     NewHalfLine;
3488   end;
3489   R.Top := CurrentYPos;
3490   Inc(R.Bottom,R.Top);
3491   if graph.InheritsFrom(TBitmap) then
3492     DrawBMP(R,graph as TBitmap) else
3493   if graph.InheritsFrom(TMetaFile) then
3494     DrawMeta(R,graph as TMetaFile);
3495   CurrentYPos := R.Bottom;
3496   // draw optional caption bottom
3497   if Legend<>'' then begin
3498     SaveLayout;
3499     TextAlign := taCenter;
3500     Font.Style := [];
3501     Font.Size := (Font.Size*3)shr 2; // smaller font for caption text
3502     DrawTextW(Legend);
3503     RestoreSavedLayout;
3504   end else
3505     NewHalfLine;
3506 end;
3507 
3508 procedure TGDIPages.DrawMeta(rec: TRect; meta: TMetafile);
3509 var old: Integer;
3510 begin
3511   if Self=nil then exit; // avoid GPF
3512   CheckHeaderDone;
3513   rec := MmToPrinter(rec);
3514   old := SaveDC(fCanvas.Handle);   // ensure safe metafile embedding
3515   PlayEnhMetaFile(fCanvas.Handle, meta.Handle, rec);
3516   RestoreDC(fCanvas.Handle,old);
3517 end;
3518 
3519 procedure TGDIPages.DrawArrow(Point1, Point2: TPoint;
3520       HeadSize: integer; SolidHead: boolean);
3521 begin
3522   if Self=nil then exit; // avoid GPF
3523   CheckHeaderDone;
3524   Point1.X := MmToPrinterPxX(Point1.X);
3525   Point1.Y := MmToPrinterPxY(Point1.Y);
3526   Point2.X := MmToPrinterPxX(Point2.X);
3527   Point2.Y := MmToPrinterPxY(Point2.Y);
3528   HeadSize := MmToPrinterPxX(max(HeadSize,0));
3529   fCanvas.Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size, 8);
3530   DrawArrowInternal(fCanvas, Point1, Point2, HeadSize, SolidHead);
3531 end;
3532 
3533 procedure TGDIPages.DrawLine(doubleline: boolean);
3534 begin
3535   if Self=nil then exit; // avoid GPF
3536   CheckHeaderDone;
3537   LineInternal(fPageMarginsPx.left, fPhysicalSizePx.x-fPageMarginsPx.right, doubleline);
3538   NewLine;
3539 end;
3540 
3541 procedure TGDIPages.DrawDashedLine;
3542 var
3543   Y: integer;
3544 begin
3545   if Self=nil then exit; // avoid GPF
3546   CheckHeaderDone;
3547   with fCanvas do
3548   begin
3549     Pen.Width := 1;
3550     Pen.Style := psDash;
3551     Y := fCurrentYPos + (GetLineHeight shr 1) - (Pen.Width shr 1);
3552     MoveTo(fPageMarginsPx.left, Y);
3553     LineTo(fPhysicalSizePx.x-fPageMarginsPx.right, Y);
3554     Pen.Style := psSolid;
3555   end;
3556   NewLine;
3557 end;
3558 
3559 procedure TGDIPages.NewLine;
3560 begin
3561   if Self=nil then exit; // avoid GPF
3562   CheckHeaderDone;
3563   inc(fCurrentYPos, GetLineHeight);
3564   fForcedLeftOffset := -1;
3565 //  fCanvasText := fCanvasText+#13#10;
3566 end;
3567 
3568 procedure TGDIPages.NewHalfLine;
3569 begin
3570   if Self=nil then exit; // avoid GPF
3571   CheckHeaderDone;
3572   inc(fCurrentYPos, GetLineHeight shr 1);
3573 //  fCanvasText := fCanvasText+#13#10;
3574 end;
3575 
3576 procedure TGDIPages.NewLines(count: integer);
3577 begin
3578   if Self=nil then exit; // avoid GPF
3579   CheckHeaderDone;
3580   if count < 1 then exit;
3581   inc(fCurrentYPos, GetLineHeight* count);
3582 //  fCanvasText := fCanvasText+#13#10;
3583 end;
3584 
3585 procedure TGDIPages.NewPage(ForceEndGroup: boolean);
3586 begin
3587   if Self=nil then exit; // avoid GPF
3588   if ForceEndGroup then
3589     EndGroup else
3590   if Assigned(fGroupPage) then
3591     raise Exception.Create('Cannot call NewPage within a group block.');
3592   CheckHeaderDone;
3593   NewPageInternal;
3594 end;
3595 
3596 procedure TGDIPages.NewPageIfAnyContent;
3597 begin
3598   if Self=nil then exit; // avoid GPF
3599   if fHeaderDone then
3600     NewPage;
3601 end;
3602 
3603 procedure TGDIPages.BeginGroup;
3604 begin
3605   if Self=nil then exit; // avoid GPF
3606   if not fHeaderDone then exit; // i.e. haven't even started a page yet
3607   if Assigned(fGroupPage) then
3608     raise Exception.create('Group already started!');
3609 
3610   if not GroupsMustBeOnSamePage then begin
3611     // Group "light" implementation
3612     if fHeaderDone and not HasSpaceForLines(20) then
3613       NewPageInternal;
3614     exit;
3615   end;
3616 
3617   //make sure there's room for at least 2 lines otherwise just start a new page
3618   //(a group surely contains at least 2 lines )
3619   if not HasSpaceForLines(2) then begin
3620     NewPageInternal;
3621     exit;
3622   end;
3623   fGroupVerticalSpace :=
3624     fPhysicalSizePx.y - fCurrentYPos - fPageMarginsPx.bottom - fFooterHeight;
3625   fColumnsUsedInGroup := false;
3626   fColumnHeaderInGroup := false;
3627   if Assigned(fCanvas) then
3628     FreeAndNil(fCanvas);
3629   fGroupPage := CreateMetaFile(fPhysicalSizePx.x,fGroupVerticalSpace + fPhysicalOffsetPx.Y);
3630   fCanvas := CreateMetafileCanvas(fGroupPage);
3631   fGroupVerticalPos := fCurrentYPos;
3632   fCurrentYPos := 0;
3633   fBeforeGroupText := fCanvasText;
3634   fCanvasText := '';
3635 end;
3636 
3637 procedure TGDIPages.EndGroup;
3638 begin
3639   if Self=nil then exit; // avoid GPF
3640   if not Assigned(fGroupPage) then
3641     exit;
3642   FreeAndNil(fCanvas); //closes fGroupPage canvas
3643   fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
3644   fCanvas.Draw(0,0,fCurrentMetaFile);     //re-draw the last page
3645   fCanvas.Draw(0,fGroupVerticalPos,fGroupPage); //add the Group data
3646   FreeAndNil(fGroupPage);                       //destroy Group metafile
3647   inc(fCurrentYPos,fGroupVerticalPos);
3648   fCanvasText := fBeforeGroupText+fCanvasText;
3649   fBeforeGroupText := '';
3650 end;
3651 
CurrentGroupPosStartnull3652 function TGDIPages.CurrentGroupPosStart: integer;
3653 begin
3654   if Self=nil then
3655     result := 0 else begin
3656     if Assigned(fGroupPage) then
3657       result := fGroupVerticalPos else
3658       result := fPageMarginsPx.top;
3659     result := PrinterPXtoMmY(result);
3660   end;
3661 end;
3662 
GetNextItemWnull3663 function GetNextItemW(var P: PWideChar): SynUnicode;
3664 var S: PWideChar;
3665 begin
3666   if P=nil then
3667     result := '' else begin
3668     S := P;
3669     while (S^<>#0) and (S^<>',') do
3670       inc(S);
3671     SetString(result,P,S-P);
3672     if S^<>#0 then
3673       P := S+1 else
3674       P := nil;
3675   end;
3676 end;
3677 
GetNextItemSnull3678 function GetNextItemS(var P: PChar): string;
3679 var S: PChar;
3680 begin
3681   if P=nil then
3682     result := '' else begin
3683     S := P;
3684     while (S^<>#0) and (S^<>',') do
3685       inc(S);
3686     SetString(result,P,S-P);
3687     if S^<>#0 then
3688       P := S+1 else
3689       P := nil;
3690   end;
3691 end;
3692 
3693 const // zoom percentages for popup menu entries
3694   MenuZoom: array[0..6] of byte = (25,50,75,100,125,150,200);
3695 
3696 procedure TGDIPages.EndDoc;
3697 var PC: PChar;
3698     i, n, aX: integer;
3699     Men: TGdiPagePreviewButton;
3700     M, Root: TMenuItem;
3701     Page: TMetaFile;
3702     s: string;
3703 begin
3704   if Self=nil then exit; // avoid GPF
3705   fLinksCurrent := -1;
3706   EndGroup;
3707   DoFooter;
3708   if Assigned(fCanvas) then
3709     FreeAndNil(fCanvas);
3710   n := length(fPages);
3711   if (n>1) and not HeaderDone then begin
3712     // cancel the last page if it hasn't been started ...
3713     FreeAndNil(fCurrentMetaFile);
3714     dec(n);
3715     SetLength(fPages,n);
3716   end else
3717     FlushPageContent;
3718   if (n>0) and (fPagesToFooterText<>'') then
3719     // add 'Page #/#' caption at the specified position
3720     for i := 0 to n-1 do begin
3721       Page := CreateMetaFile(fPages[i].SizePx.X,fPages[i].SizePx.Y);
3722       try
3723         fCanvas := CreateMetafileCanvas(Page);
3724         fCanvas.Draw(0,0,GetMetaFileForPage(i)); // re-draw the original page
3725         s := format(fPagesToFooterText,[i+1,n]); // add 'Page #/#' caption
3726         aX := fPagesToFooterAt.X;
3727         if aX<0 then
3728           aX := fPages[i].SizePx.X-fPages[i].MarginPx.Right;
3729         SavedState := fPagesToFooterState;
3730         if TextAlign=taRight then
3731           dec(aX,fCanvas.TextWidth(s));
3732         with fPages[i] do
3733           fCanvas.TextOut(aX,SizePx.Y-MarginPx.bottom-fFooterHeight+
3734             fFooterGap+fPagesToFooterAt.Y,s);
3735         FreeAndNil(fCanvas);
3736         SetMetaFileForPage(i,Page); // replace page content
3737       finally
3738         Page.Free;
3739       end;
3740     end;
3741   // OK, all Metafile pages have now been created and added to Pages[]
3742   if Assigned(fOnDocumentProducedEvent) then
3743     fOnDocumentProducedEvent(Self); // notify report just generated
3744   fCurrPreviewPage := 1;
3745   if Assigned(fPreviewPageChangedEvent) then
3746     fPreviewPageChangedEvent(Self); // notify page changed
3747   Invalidate;
3748   // update popup menu content
3749   if PopupMenu=nil then // caller may have created a TPopupMenu instance
3750     PopupMenu := PopupMenuClass.Create(Self) else
3751     PopupMenu.Items.Clear;
3752   PopupMenu.OnPopup := PopupMenuPopup;
3753   PC := pointer(string(sReportPopupMenu1));
3754   // 'Next,Previous,GotoPage,Zoom,Bookmarks,CopyasText,Print,PDF,Close,Pagefit,Pagewidth'
3755   for Men := rNextPage to rClose do
3756       NewPopupMenuItem(GetNextItemS(PC),-ord(Men)).Enabled :=
3757         (Men<rPrint) or (Men=rClose) or
3758         ( (Men=rPrint) and fHasPrinterInstalled) or
3759         ( (Men=rExportPDF) {$ifdef USEPDFPRINTER}and fHasPDFPrinterInstalled{$endif});
3760   PopupMenu.Items[ord(rClose)-1].Visible := false;
3761   M := PopupMenu.Items[ord(rZoom)-1];
3762   NewPopupMenuItem(GetNextItemS(PC),-1000-PAGE_FIT,M);
3763   NewPopupMenuItem(GetNextItemS(PC),-1000-PAGE_WIDTH,M);
3764   for i := 0 to high(MenuZoom) do
3765     NewPopupMenuItem(format('%d %%',[MenuZoom[i]]),-1000-MenuZoom[i],M);
3766   Root := PopupMenu.Items[ord(rBookmarks)-1];
3767   if UseOutlines and (fOutline.Count>0) then begin
3768     Root.Enabled := true;
3769     M := Root;
3770     for i := 0 to fOutline.Count-1 do
3771     with TGDIPagereference(fOutline.Objects[i]) do begin
3772       while (M<>Root) and (cardinal(-2000-M.Tag)<cardinal(fOutline.Count)) and
3773          (Rect.Bottom<=TGDIPagereference(fOutline.Objects[-2000-M.Tag]).Rect.Bottom) do
3774         M := M.Parent;
3775       M := NewPopupMenuItem(fOutline[i],-2000-i,M);
3776     end;
3777   end else
3778     Root.Enabled := false;
3779 end;
3780 
PrintPagesnull3781 function TGDIPages.PrintPages(PrintFrom, PrintTo: integer): boolean;
3782 var i: integer;
3783     rec: TRect;
3784     CheckCurrentPtr: string;
3785     UseStretchDraw: boolean;
3786     BMP: TBitmap;
3787 begin
3788   result := false;
3789   if Self=nil then exit; // avoid GPF
3790   if not fHasPrinterInstalled then
3791     raise Exception.Create('No printer driver is currently installed.');
3792   if PrintFrom<0 then
3793     with TPrintDialog.Create(nil) do
3794     try
3795       Options := [poPageNums];
3796       MinPage := 1;
3797       MaxPage := PageCount;
3798       FromPage := 1;
3799       ToPage := PageCount;
3800       if not Execute then
3801         exit;
3802       PrintFrom := FromPage;
3803       PrintTo := ToPage;
3804     finally
3805       Free;
3806     end;
3807   result := true;
3808   // ideally, the user has changed printers BEFORE generating a report, but
3809   // if they want a report sent to a different printer then use StretchDraw ...
3810   CheckCurrentPtr := CurrentPrinterName;
3811   if CheckCurrentPtr <> fCurrentPrinter then begin
3812     GetPrinterParams; // also updates fCurrentPrinter
3813     UseStretchDraw := true;
3814   end else
3815     UseStretchDraw := false;
3816   PrintFrom := max(PrintFrom-1,0);
3817   if PrintTo=0 then
3818     PrintTo := high(fPages) else
3819     PrintTo := min(PrintTo-1,high(fPages));
3820 {$ifdef PRINTERNEW} // set enhanced TPrinterNew class color/BW or duplex mode
3821   with PrinterNew do begin
3822     if ForcePrintColorMode<>printColorDefault then begin
3823       if (ForcePrintColorMode=printColor) and HasColorMode then
3824         ColorMode := true else
3825       if ForcePrintColorMode=printBW then
3826         ColorMode := false;
3827     end;
3828     if ForcePrintDuplexMode<>printDuplexDefault then begin
3829       if (ForcePrintDuplexMode=printDuplex) and HasDuplexMode then
3830         DuplexMode := true else
3831       if ForcePrintDuplexMode=printSimplex then
3832         DuplexMode := false;
3833     end;
3834 {$else}
3835   with Printer do begin
3836 {$endif}
3837     if Caption='' then
3838     {$ifndef USEPDFPRINTER}
3839       if ExportPDFApplication<>'' then
3840         Title := ExportPDFApplication else
3841     {$endif}
3842         Title := Application.Title else
3843       Title := Caption;
3844     Orientation := Self.Orientation; // just in case fPrinter changed
3845     BeginDoc;
3846     try
3847       Screen.Cursor := crHourGlass;
3848       if ForcePrintAsBitmap then begin // very slow printing
3849         BMP := TBitmap.Create;
3850         try
3851           BMP.Width := GetDeviceCaps(handle, PHYSICALWIDTH);
3852           BMP.Height := GetDeviceCaps(handle, PHYSICALHEIGHT);
3853           for i := PrintFrom to PrintTo do
3854           with fPages[i] do begin
3855             BMP.Canvas.StretchDraw(Rect(0,0,Bmp.Width,Bmp.Height),GetMetaFileForPage(i));
3856             Canvas.Draw(-OffsetPx.x,-OffsetPx.y,BMP);
3857             if i<PrintTo then
3858               NewPage;
3859           end;
3860         finally
3861           BMP.Free;
3862         end;
3863       end else
3864       for i := PrintFrom to PrintTo do begin
3865         // nb: the printer's page origin is fPhysicalOffsetPx so it's
3866         //     necessary to offset our rect by -OffsetPx ...
3867         if ForceScreenResolution then begin
3868           rec := Rect(0, 0, GetDeviceCaps(handle, PHYSICALWIDTH),
3869             GetDeviceCaps(handle, PHYSICALHEIGHT));
3870           OffsetRect(rec, -GetDeviceCaps(handle,PHYSICALOFFSETX),
3871             -GetDeviceCaps(handle,PHYSICALOFFSETY));
3872           Canvas.StretchDraw(rec,GetMetaFileForPage(i));
3873         end else
3874         with fPages[i] do
3875         if UseStretchDraw then
3876           Canvas.StretchDraw(Rect(-OffsetPx.x,-OffsetPx.y,
3877             SizePx.x-OffsetPx.x, SizePx.y-OffsetPx.y),GetMetaFileForPage(i)) else
3878           Canvas.Draw(-OffsetPx.x,-OffsetPx.y,GetMetaFileForPage(i));
3879         if i<PrintTo then
3880           NewPage;
3881       end;
3882       EndDoc;
3883     finally
3884       Screen.Cursor := crDefault;
3885     end;
3886   end;
3887 end;
3888 
3889 
3890 procedure TGDIPages.SetTabStops(const tabs: array of integer);
3891 var i: integer;
3892 begin
3893   if Self=nil then exit; // avoid GPF
3894   FillChar(fTab[0],MAXTABS*sizeof(fTab[0]),0);
3895   fTabCount := min(high(tabs)+1,MAXTABS);
3896   //ignore trailing 0 tabs in array ...
3897   if (fTabCount > 0) then
3898     while (fTabCount > 0) and (tabs[fTabCount-1] = 0) do
3899       dec(fTabCount);
3900 
3901   if (fTabCount > 1) then begin
3902     if (tabs[0] <= 0) then
3903       raise Exception.Create('Tabs stops must be greater than 0.');
3904     fTab[0] := MmToPrinterPxX(tabs[0]);
3905     for i := 1 to fTabCount -1 do
3906       if tabs[i] > tabs[i-1] then
3907         fTab[i] := MmToPrinterPxX(tabs[i]) else
3908         raise Exception.Create('Tabs stops must be in ascending order');
3909   end else
3910   if fTabCount = 1 then begin
3911     //if one tab set then use that tab as the interval for subsequent tabs
3912     for i := 0 to MAXTABS-1 do
3913       fTab[i] := MmToPrinterPxX((i+1)*tabs[0]);
3914     fTabCount := MAXTABS;
3915   end else begin
3916     //if no tabs set then default to tabs every 20mm
3917     for i := 0 to MAXTABS-1 do fTab[i] := MmToPrinterPxX((i+1)*20);
3918     fTabCount := MAXTABS;
3919   end;
3920 end;
3921 
3922 
GetPageMarginsnull3923 function TGDIPages.GetPageMargins: TRect;
3924 begin
3925   if Self=nil then
3926     FillChar(result,sizeof(result),0) else
3927     with result do begin
3928       Left := PrinterPxToMmX(fPageMarginsPx.left);
3929       Top := PrinterPxToMmY(fPageMarginsPx.top);
3930       Right := PrinterPxToMmX(fPageMarginsPx.right);
3931       Bottom := PrinterPxToMmY(fPageMarginsPx.bottom);
3932     end;
3933 end;
3934 
3935 procedure TGDIPages.SetPageMargins(Rect: TRect);
3936 begin
3937   fPageMarginsPx := MmToPrinter(Rect);
3938   if not fHeaderDone then
3939     fCurrentYPos := fPageMarginsPx.top;
3940 end;
3941 
GetLeftMarginnull3942 function TGDIPages.GetLeftMargin: integer;
3943 begin
3944   if Self=nil then
3945     result := 0 else
3946     result := PrinterPxToMmX(fPageMarginsPx.left);
3947 end;
3948 
3949 procedure TGDIPages.SetLeftMargin(const Value: integer);
3950 begin
3951   if Self=nil then exit;
3952   fPageMarginsPx.Left := MmToPrinterPxX(Value);
3953 end;
3954 
GetPaperSizenull3955 function TGDIPages.GetPaperSize: TSize;
3956 begin
3957   if Self=nil then
3958     FillChar(result,sizeof(result),0) else begin
3959     result.cx := PrinterPxToMmX(fPhysicalSizePx.X);
3960     result.cy := PrinterPxToMmY(fPhysicalSizePx.Y);
3961   end;
3962 end;
3963 
3964 procedure TGDIPages.AddLineToHeader(doubleline: boolean);
3965 begin
3966   if Self=nil then exit; // avoid GPF
3967   fHeaderLines.Add(THeaderFooter.Create(Self,doubleline));
3968 end;
3969 
3970 procedure TGDIPages.AddLineToFooter(doubleline: boolean);
3971 begin
3972   if Self=nil then exit; // avoid GPF
3973   if fFooterLines.Count = 0 then
3974     CalcFooterGap;
3975   fFooterLines.Add(THeaderFooter.Create(Self,doubleline));
3976   inc(fFooterHeight, GetLineHeight);
3977 end;
3978 
3979 
3980 procedure TGDIPages.AddTextToHeader(const s: SynUnicode);
3981 begin
3982   if Self<>nil then
3983     fHeaderLines.Add(THeaderFooter.Create(Self,false,s,true));
3984 end;
3985 
3986 procedure TGDIPages.AddTextToHeaderAt(const s: SynUnicode; XPos: integer);
3987 var Head: THeaderFooter;
3988 begin
3989   if Self=nil then exit; // avoid GPF
3990   Head := THeaderFooter.Create(Self,false,s,true);
3991   Head.State.Flags := Head.State.Flags or ((XPos+2) shl 16);
3992   fHeaderLines.Add(Head);
3993 end;
3994 
3995 procedure TGDIPages.AddTextToFooter(const s: SynUnicode);
3996 begin
3997   if Self=nil then exit; // avoid GPF
3998   if fFooterLines.Count = 0 then
3999     CalcFooterGap;
4000   fFooterLines.Add(THeaderFooter.Create(Self,false,s,true));
4001   inc(fFooterHeight, GetLineHeight);
4002 end;
4003 
4004 procedure TGDIPages.AddTextToFooterAt(const s: SynUnicode; XPos: integer);
4005 var Foot: THeaderFooter;
4006 begin
4007   if Self=nil then exit; // avoid GPF
4008   //todo - can't print at 0mm from left edge so raise exception
4009   if fFooterLines.Count = 0 then
4010     CalcFooterGap;
4011   Foot := THeaderFooter.Create(Self,false,s,true);
4012   Foot.State.Flags := Foot.State.Flags or ((XPos+2) shl 16);
4013   fFooterLines.Add(Foot);
4014 end;
4015 
4016 procedure TGDIPages.AddPagesToFooterAt(const PageText: string;
4017   XPos,YPosMultiplier: integer);
4018 begin
4019   if fPagesToFooterText<>'' then
4020     exit; // only add once
4021   fPagesToFooterText := PageText;
4022   if XPos<0 then
4023     fPagesToFooterAt.X := -1 else
4024     fPagesToFooterAt.X := MmToPrinterPxX(XPos);
4025   fPagesToFooterAt.Y := fFooterHeight * YPosMultiplier;
4026   fPagesToFooterState := SavedState;
4027 end;
4028 
TGDIPages.GetColumnCountnull4029 function TGDIPages.GetColumnCount: integer;
4030 begin
4031   if Self=nil then
4032     result := 0 else
4033     result := length(fColumns);
4034 end;
4035 
GetColumnInfonull4036 function TGDIPages.GetColumnInfo(index: integer): TColRec;
4037 begin
4038   if Self=nil then begin
4039     FillChar(result,sizeof(result),0);
4040     exit;
4041   end;
4042   if cardinal(index)>=cardinal(Length(fColumns)) then
4043     raise Exception.create('GetColumnInfo: index out of range');
4044   with fColumns[index] do begin
4045     result.ColLeft := PrinterPxToMmX(ColLeft);
4046     result.ColRight := PrinterPxToMmX(ColRight);
4047     result.ColAlign := ColAlign;
4048     result.ColBold := ColBold;
4049   end;
4050 end;
4051 
4052 procedure TGDIPages.SetColumnAlign(index: integer; align: TColAlign);
4053 begin
4054   if Self=nil then exit; // avoid GPF
4055   if cardinal(index)>=cardinal(Length(fColumns)) then
4056     raise Exception.create('SetColumnAlign: index out of range') else
4057     fColumns[index].ColAlign := align;
4058 end;
4059 
4060 procedure TGDIPages.SetColumnBold(index: integer);
4061 begin
4062   if Self=nil then exit; // avoid GPF
4063   if cardinal(index)>=cardinal(Length(fColumns)) then
4064     raise Exception.create('SetColumnAlign: index out of range') else
4065     fColumns[index].ColBold := true;
4066 end;
4067 
4068 procedure TGDIPages.AddColumn(left, right: integer; align: TColAlign; bold: boolean);
4069 var n: integer;
4070 begin
4071   if Self=nil then exit; // avoid GPF
4072   left := MmToPrinterPxX(left);
4073   right := MmToPrinterPxX(right);
4074   n := length(fColumns);
4075   if (n>0) and (left<fColumns[n-1].ColRight) then
4076     raise Exception.create('Columns overlap!');
4077   SetLength(fColumns,n+1);
4078   with fColumns[n] do begin
4079     ColLeft := left;
4080     ColRight := right;
4081     ColAlign := align;
4082     ColBold := bold;
4083   end;
4084 end;
4085 
4086 procedure TGDIPages.AddColumns(const PercentWidth: array of integer; align: TColAlign);
4087 var i, sum, left, right, ww, n: integer;
4088 begin
4089   if Self=nil then exit; // avoid GPF
4090   ClearColumns;
4091   sum := 0;
4092   for i := 0 to high(PercentWidth) do
4093     inc(sum,abs(PercentWidth[i]));
4094   if sum<=0 then
4095     exit;
4096   left := fPageMarginsPx.left;
4097   ww := fPhysicalSizePx.x-left-fPageMarginsPx.right;
4098   n := length(fColumns);
4099   SetLength(fColumns,n+length(PercentWidth));
4100   for i := 0 to high(PercentWidth) do begin
4101     right := left+(abs(PercentWidth[i])*ww) div sum;
4102     // manual adding (no mm conversion -> exact width)
4103     with fColumns[i+n] do begin
4104       ColLeft := left;
4105       ColRight := right;
4106       if PercentWidth[i]<0 then
4107         ColAlign := caCenter  else
4108         ColAlign := align;
4109       ColBold := false;
4110     end;
4111     left := right;
4112   end;
4113 end;
4114 
4115 procedure TGDIPages.AddColumnHeaders(const headers: array of SynUnicode;
4116   WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
4117   RowLineHeight: integer=0; flags: integer=0);
4118 var n,i: integer;
4119 begin
4120   if Self=nil then exit; // avoid GPF
4121   if flags=0 then begin
4122     if BoldFont then
4123       Font.Style := [fsBold];
4124     flags := TextFormatsToFlags;
4125   end;
4126   n := length(fColumnHeaderList);
4127   SetLength(fColumnHeaderList,n+1);
4128   fColumnHeaderList[n].flags := flags;
4129   SetLength(fColumnHeaderList[n].headers,Length(headers));
4130   for i := 0 to high(headers) do
4131     fColumnHeaderList[n].headers[i] := headers[i];
4132   fColumnHeaderPrinted := false;
4133   fColumnHeaderPrintedAtLeastOnce := false;
4134   fColumnsWithBottomGrayLine := WithBottomGrayLine;
4135   fColumnsRowLineHeight := RowLineHeight;
4136   if BoldFont then
4137     Font.Style := [];
4138 end;
4139 
CSVToArraynull4140 function CSVToArray(var CSV: PWideChar; n: integer): TSynUnicodeDynArray;
4141 var i: integer;
4142 begin
4143   SetLength(result,n);
4144   for i := 0 to n-1 do
4145     result[i] := GetNextItemW(CSV);
4146 end;
4147 
4148 procedure TGDIPages.AddColumnHeadersFromCSV(var CSV: PWideChar;
4149   WithBottomGrayLine, BoldFont: boolean; RowLineHeight: integer);
4150 begin
4151   if Self<>nil then // avoid GPF
4152     AddColumnHeaders(CSVToArray(CSV,length(fColumns)),
4153       WithBottomGrayLine,BoldFont,RowLineHeight);
4154 end;
4155 
4156 procedure TGDIPages.DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
4157 begin
4158   if Self<>nil then // avoid GPF
4159     DrawTextAcrossCols(CSVToArray(CSV,length(fColumns)),[],BackgroundColor);
4160 end;
4161 
4162 /// round inverted color to white or black
clAlwaysnull4163 function clAlways(cl: TColor): TColor;
4164 begin
4165   if ((GetRValue(longword(cl)) * 2) +
4166       (GetGValue(longword(cl)) * 3) +
4167       (GetBValue(longword(cl)) * 2)) < 600 then
4168     result := clWhite else
4169     result := clBlack;
4170 end;
4171 
4172 procedure TGDIPages.DrawTextAcrossCols(const StringArray: array of SynUnicode;
4173   BackgroundColor: TColor);
4174 begin
4175   DrawTextAcrossCols(StringArray,[],BackgroundColor);
4176 end;
4177 
4178 procedure TGDIPages.DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
4179   BackgroundColor: TColor);
4180 
HasCRLFnull4181 function HasCRLF(const s: SynUnicode): boolean;
4182 var i: integer;
4183 begin
4184   result := true;
4185   for i := 0 to length(s)-1 do
4186     if s[i+1]<' ' then
4187       exit;
4188   result := false;
4189 end;
4190 
WrapTextnull4191 function WrapText(s: SynUnicode; MaxWidth: integer; Lines: PSynUnicodeDynArray): integer;
4192 var j,k,sp: integer;
4193 begin
4194   result := 0; // returns the line count
4195   if Lines<>nil then
4196     SetLength(Lines^,0);
4197   repeat
4198     if HasCRLF(s) or (TextWidthC(fCanvas,s)>MaxWidth) then begin
4199       j := 1;
4200       k := 1;
4201       sp := 0;
4202       while (j<length(s)) and (TextWidthC(fCanvas,copy(s,1,j))<MaxWidth) do begin
4203         k := j; // store last fitting character index
4204         if s[j]<=' ' then begin
4205           sp := j; // mark space (=word delimiter) found
4206           if s[j]<' ' then
4207             break; // #13,#10 will force word wrap here = next line
4208         end;
4209         inc(j);
4210       end;
4211       if sp=0 then
4212         sp := k; // if no space found, use character wrapping
4213     end else
4214       sp := length(s)+1;
4215     if sp<=1 then
4216       sp := 2;
4217     if Lines<>nil then begin
4218       SetLength(Lines^,length(Lines^)+1);
4219       Lines^[high(Lines^)] := copy(s,1,sp-1);
4220     end;
4221     inc(result); // update lines count
4222     s := trim(copy(s,sp,maxInt)); // trim ' ',#13,#10 for next line
4223   until s='';
4224 end;
4225 
4226 var RowRect: TRect;
4227     lh: integer;
4228     max, i, j, k, c, H, ParenthW, LinesCount, X: integer;
4229     s: SynUnicode;
4230     line: string;
4231     Lines: TSynUnicodeDynArray;
4232     PW: PWideChar;
4233     PWLen, Options: integer;
4234     size: TSize;
4235     r: TRect;
4236 begin
4237   if Self=nil then exit; // avoid GPF
4238   max := high(fColumns);
4239   if (max<0) or (length(StringArray)=0) then
4240     exit; // no column defined
4241   if High(StringArray)<max then
4242     max := High(StringArray);
4243   if max<0 then
4244     exit; // nothing to draw
4245   // check enough place for this column content on the page
4246   lh := GetLineHeight;
4247   CheckYPos;
4248   LinesCount := 1; // by default, one line of text will be written
4249   if WordWrapLeftCols then begin // check if stay on current page after word wrap
4250     for j := 0 to max do
4251     with fColumns[j] do
4252     if (ColAlign=caLeft) and (ColRight>ColLeft) and
4253        (HasCRLF(StringArray[j]) or
4254         (TextWidthC(fCanvas,StringArray[j])>ColRight-ColLeft)) then begin
4255       k := WrapText(StringArray[j],ColRight-ColLeft,nil); // calculate line counts
4256       if k>LinesCount then
4257         LinesCount := k; // calculate maximum line count
4258     end;
4259     if (LinesCount>1) and not HasSpaceForLines(LinesCount) then begin
4260       NewPageInternal;
4261       CheckHeaderDone;
4262     end;
4263   end;
4264   if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
4265     i := length(fColumnHeaderList) + 2;
4266     if not HasSpaceForLines(i) then
4267       NewPageInternal;
4268     PrintColumnHeaders;
4269   end;
4270   // prepare column write
4271   if Assigned(fGroupPage) then
4272     fColumnsUsedInGroup := true;
4273   ParenthW := fCanvas.TextWidth(')');
4274   RowRect.Top := fCurrentYPos;
4275   RowRect.Bottom := RowRect.Top+lh*LinesCount;
4276   RowRect.Right := fColumns[max].ColRight;
4277   if BackgroundColor<>clNone then
4278   with fCanvas do begin
4279     Brush.Style := bsSolid;
4280     Brush.Color := BackgroundColor;
4281     RowRect.Left := fColumns[0].ColLeft;
4282     FillRect(RowRect);
4283     Brush.Style := bsClear;
4284     Font.Color := clAlways(BackgroundColor);
4285   end;
4286   // main loop, used to write column content
4287   line := '';
4288   for i := 0 to max do begin
4289     s := StringArray[i];
4290     line := line+SynUnicodeToString(s)+#9; // add column content + tab for report text
4291     if s<>'' then
4292     with fColumns[i], fCanvas do
4293     if ColRight>ColLeft then begin
4294       if ColBold then
4295         Font.Style := Font.Style+[fsBold];
4296       Options := ETO_CLIPPED or TextFlags; // unicode version of TextRect()
4297       if Brush.Style <> bsClear then
4298         Options := Options or ETO_OPAQUE;
4299       InternalUnicodeString(s,PW,PWLen,@size);
4300       if (ColAlign=caCenter) and (size.cx>ColRight-ColLeft) then
4301         // overlapping centered -> draw right aligned
4302         RowRect.Left := ColRight-size.cx-ParenthW else
4303       case ColAlign of
4304         caLeft: begin
4305           RowRect.Left := ColLeft;
4306           if WordWrapLeftCols and (ColRight>ColLeft) and
4307              (HasCRLF(s) or (size.cx>ColRight-ColLeft)) then begin
4308             // handle optional left aligned column content word wrap
4309             WrapText(s,ColRight-ColLeft,@Lines); // word wrap s into Lines[]
4310             dec(RowRect.Left,ParenthW);
4311             for j := 0 to high(Lines) do begin
4312               InternalUnicodeString(Lines[j],PW,PWLen,@size);
4313               if BiDiMode=bdRightToLeft then
4314                 X := ColRight-size.cx-ParenthW else
4315                 X := ColLeft;
4316               RowRect.Top := fCurrentYPos+lh*j;
4317               ExtTextOutW(Handle,X,RowRect.Top,Options,@RowRect,PW,PWLen,nil);
4318             end;
4319             RowRect.Top := fCurrentYPos;
4320             if ColBold then
4321               Font.Style := Font.Style-[fsBold];
4322             Continue; // text was written as word-wrap -> write next column
4323           end else
4324           if BiDiMode=bdRightToleft then
4325             RowRect.Left := ColRight-size.cx-ParenthW;
4326         end;
4327         caCenter:
4328           RowRect.Left := ColLeft+(ColRight-ColLeft-size.cx)shr 1;
4329         caRight:
4330           if BiDiMode=bdLeftToRight then
4331             RowRect.Left := ColRight-size.cx-ParenthW;
4332         caCurrency: begin
4333           if fNegsToParenthesesInCurrCols then
4334             InternalUnicodeString(ConvertNegsToParentheses(s),PW,PWLen,@size);
4335           RowRect.Left := ColRight-size.cx-ParenthW;
4336           // no bdRightToleft handling necessary for caCurrency
4337         end;
4338       end;
4339       dec(RowRect.Left,ParenthW);
4340       ExtTextOutW(Handle,RowRect.Left+ParenthW,fCurrentYPos,Options,@RowRect,PW,PWLen,nil);
4341       if (i<length(LinkArray)) and (LinkArray[i]<>'') then begin
4342         r.Left := PrinterPxToMmX(rowrect.Left);
4343         r.Top := PrinterPxToMmX(rowrect.Top);
4344         r.right := PrinterPxToMmX(rowrect.left+(rowrect.right-fColumns[0].ColLeft) div (max+1));
4345         r.Bottom := PrinterPxToMmX(rowrect.Bottom);
4346         AddLink(LinkArray[i],r);
4347       end;
4348       inc(RowRect.Left,size.cx+ParenthW);
4349       if ColBold then
4350         Font.Style := Font.Style-[fsBold];
4351     end;
4352   end;
4353   if not fDrawTextAcrossColsDrawingHeader or
4354      not fColumnHeaderPrintedAtLeastOnce then begin
4355     line[length(line)] := #13; // overwrite last #9
4356     line := line+#10;
4357     fCanvasText := fCanvasText+line; // append columns content to report text
4358   end;
4359   if BackgroundColor<>clNone then
4360     fCanvas.Font.Color := clBlack;
4361   if not fDrawTextAcrossColsDrawingHeader and (fColumnsRowLineHeight>LinesCount) then
4362     // custom space for Row before bottom gray line
4363     LinesCount := fColumnsRowLineHeight;
4364   for i := 2 to LinesCount do
4365     NewLine;
4366   if fColumnsWithBottomGrayLine and (RowRect.Right<>0) then begin
4367     c := fCanvas.Pen.Color;
4368     fCanvas.Pen.Color := clLtGray;
4369     H := lh shr 1-(lh*15)shr 4;
4370     dec(fCurrentYPos, H);
4371     LineInternal(GetColumnRec(0).ColLeft,RowRect.Right,false);
4372     inc(fCurrentYPos, H);
4373     fCanvas.Pen.Color := c;
4374   end;
4375   NewLine;
4376 end;
4377 
4378 procedure TGDIPages.DrawLinesInCurrencyCols(doublelines: boolean);
4379 var i: integer;
4380 begin
4381   if Self=nil then exit; // avoid GPF
4382   CheckYPos;
4383   if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
4384     i := length(fColumnHeaderList) + 2;
4385     if not HasSpaceForLines(i) then
4386       NewPageInternal;
4387     PrintColumnHeaders;
4388   end;
4389   for i := 0 to high(fColumns) do
4390     with fColumns[i] do
4391       if ColAlign = caCurrency then
4392         LineInternal(ColLeft, ColRight, doublelines);
4393   NewLine;
4394 end;
4395 
4396 procedure TGDIPages.ColumnHeadersNeeded;
4397 begin
4398   if Self=nil then exit; // avoid GPF
4399   fColumnHeaderPrinted := false;
4400 end;
4401 
4402 procedure TGDIPages.Clear;
4403 procedure ClearObjects(List: TStringList);
4404 var i: integer;
4405 begin
4406   for i := 0 to List.Count-1 do
4407     List.Objects[i].Free;
4408   List.Clear;
4409 end;
4410 begin
4411   if Self=nil then exit; // avoid GPF
4412   if Assigned(fCanvas) then
4413     FreeAndNil(fCanvas);
4414   if Assigned(fGroupPage) then
4415     FreeAndNil(fGroupPage);
4416   FreeAndNil(fCurrentMetaFile);
4417   SetLength(fPages,0);
4418   ClearObjects(fBookmarks);
4419   ClearObjects(fLinks);
4420   ClearObjects(fOutline);
4421   ClearHeaders;
4422   ClearFooters;
4423   ClearColumns;
4424   SetTabStops([20]);
4425   fCanvasText := '';
4426   fLinksCurrent := -1;
4427   fSavedCount := 0;
4428 end;
4429 
4430 procedure TGDIPages.ClearHeaders;
4431 begin
4432   if Self=nil then exit; // avoid GPF
4433   fHeaderLines.Clear;
4434 end;
4435 
4436 procedure TGDIPages.ClearFooters;
4437 begin
4438   if Self=nil then exit; // avoid GPF
4439   fFooterLines.Clear;
4440   fPagesToFooterText := '';
4441 end;
4442 
4443 procedure TGDIPages.ClearColumns;
4444 begin
4445   if Self=nil then exit; // avoid GPF
4446   SetLength(fColumns,0);
4447   ClearColumnHeaders;
4448 end;
4449 
4450 procedure TGDIPages.ClearColumnHeaders;
4451 begin
4452   if Self=nil then exit; // avoid GPF
4453   fColumnHeaderList := nil;
4454 end;
4455 
CreatePictureMetaFilenull4456 function TGDIPages.CreatePictureMetaFile(Width, Height: integer;
4457   out MetaCanvas: TCanvas): TMetaFile;
4458 begin
4459   if Self=nil then
4460     result := nil else begin
4461     result := CreateMetaFile(MmToPrinterPxX(Width),MmToPrinterPxY(Height));
4462     MetaCanvas := CreateMetafileCanvas(result);
4463   end;
4464 end;
4465 
4466 procedure TGDIPages.DrawTextFmt(const s: string; const Args: array of const;
4467   withNewLine: boolean);
4468 begin
4469   DrawText(format(s,Args),withNewLine);
4470 end;
4471 
TGDIPages.TitleFlagsnull4472 function TGDIPages.TitleFlags: integer;
4473 begin
4474   result := ((Font.Size*12) div 10) or FORMAT_BOLD or FORMAT_LEFT;
4475 end;
4476 
TGDIPages.TextWidthnull4477 function TGDIPages.TextWidth(const Text: SynUnicode): integer;
4478 begin
4479   if Self=nil then
4480     result := 0 else begin
4481     if fCanvas=nil then
4482       result := TextWidthC(Canvas,Text) else
4483       result := TextWidthC(fCanvas,Text);
4484     result := PrinterPxToMmX(result);
4485   end;
4486 end;
4487 
4488 procedure TGDIPages.ShowPreviewForm(VisibleButtons: TGdiPagePreviewButtons);
4489   procedure CopyMenus(Source,Dest: TMenuItem);
4490   var i: integer;
4491       Sub: TMenuItem;
4492   begin
4493     for i := 0 to Source.Count-1 do
4494     with Source.Items[i] do begin
4495       Sub := TMenuItem.Create(PreviewForm);
4496       Sub.Tag := Tag;
4497       Sub.OnClick := OnClick;
4498       Sub.Caption := Caption;
4499       Dest.Add(Sub);
4500       CopyMenus(Source.Items[i],Sub);
4501     end;
4502   end;
4503 const PANELWIDTH = 128;
4504 var OldParent: TWinControl;
4505     i,y,W: integer;
4506     M: TMenuItem;
4507     LeftPanel: TPanel;
4508 begin
4509   if Self=nil then exit; // avoid GPF
4510   PreviewForm := TForm.Create(nil);
4511   try
4512     PreviewForm.Position := poScreenCenter;
4513     PreviewForm.Height := Screen.Height-64;
4514     PreviewForm.Caption := Caption;
4515     PreviewForm.Font.Name := 'Tahoma';
4516     with PaperSize do begin
4517       if cy=0 then
4518         y := 1 else
4519         y := cy;
4520       PreviewForm.Width := (cx*PreviewForm.Height) div y+(64+PANELWIDTH);
4521     end;
4522     if PreviewForm.Width>Screen.WorkAreaWidth then
4523       PreviewForm.WindowState := wsMaximized;
4524     LeftPanel := TPanel.Create(PreviewForm);
4525     LeftPanel.Parent := PreviewForm;
4526     LeftPanel.Width := PANELWIDTH;
4527     LeftPanel.Align := alLeft;
4528     W := LeftPanel.ClientWidth-8;
4529     PreviewPageCountLabel := TLabel.Create(PreviewForm);
4530     PreviewPageCountLabel.Transparent := true;
4531     PreviewPageCountLabel.Parent := LeftPanel;
4532     PreviewPageCountLabel.SetBounds(4,24,W-4,24);
4533     PreviewPageCountLabel.Alignment := Classes.taCenter;
4534     PreviewPageCountLabel.AutoSize := false;
4535     PreviewPageCountLabel.Caption := format(sPageN,[Page,PageCount]);
4536     PopupMenuPopup(nil); // refresh PopupMenu.Items[]
4537     SetLength(PreviewButtons,PopupMenu.Items.Count);
4538     y := 48;
4539     for i := 0 to High(PreviewButtons) do begin
4540       M := PopupMenu.Items[i];
4541       PreviewButtons[i] := TButton.Create(PreviewForm);
4542       with PreviewButtons[i] do begin
4543         Parent := LeftPanel;
4544         SetBounds(4,y,W,32);
4545         Enabled := M.Enabled;
4546         Caption := M.Caption;
4547         Tag := M.Tag;
4548         OnClick := PopupMenuItemClick;
4549         if M.Count>0 then begin
4550           PopupMenu := PopupMenuClass.Create(PreviewForm);
4551           CopyMenus(M,PopupMenu.Items);
4552         end;
4553         if TGdiPagePreviewButton(i+1) in VisibleButtons then
4554           case TGdiPagePreviewButton(i+1) of
4555           rPrint: begin
4556             Height := 60;
4557             inc(y,64);
4558             Default := true;
4559           end;
4560           rClose, rNextPage, rPreviousPage: begin
4561             Height := 48;
4562             inc(y,52);
4563           end;
4564           rGotoPage, rZoom, rBookmarks, rExportPDF:
4565             inc(y,48);
4566           else
4567             inc(y,36);
4568           end else begin
4569             M.Visible := false;
4570             Visible := false;
4571           end;
4572       end;
4573     end;
4574     OldParent := Parent;
4575     Parent := PreviewForm;
4576     Align := alClient;
4577     Zoom := PAGE_FIT;
4578     try
4579       PreviewForm.ActiveControl := self;
4580       PreviewForm.ShowModal;
4581     finally
4582       Parent := OldParent;
4583     end;
4584   finally
4585     FreeAndNil(PreviewForm);
4586     Finalize(PreviewButtons);
4587   end;
4588 end;
4589 
GetRightMarginPosnull4590 function TGDIPages.GetRightMarginPos: integer;
4591 begin
4592   result := PrinterPxToMmX(fPhysicalSizePx.x-fPageMarginsPx.right);
4593 end;
4594 
NewPopupMenuItemnull4595 function TGDIPages.NewPopupMenuItem(const aCaption: string; Tag: integer;
4596   SubMenu: TMenuItem; OnClick: TNotifyEvent; ImageIndex: integer): TMenuItem;
4597 begin
4598   if (Self=nil) or (PopupMenu=nil) then begin
4599     result := nil;
4600     exit;
4601   end;
4602   result := TMenuItem.Create(PopupMenu);
4603   result.Caption := aCaption;
4604   result.Tag := Tag;
4605   if Assigned(OnClick) then
4606     result.OnClick := OnClick else
4607     result.OnClick := PopupMenuItemClick;
4608   if ImageIndex>=0 then
4609     result.ImageIndex := ImageIndex;
4610   if SubMenu=nil then
4611     PopupMenu.Items.Add(result) else
4612     SubMenu.Add(result);
4613 end;
4614 
4615 procedure TGDIPages.PopupMenuItemClick(Sender: TObject);
4616 var Comp: TComponent absolute Sender;
4617     i: Integer;
4618 begin
4619   if not Sender.InheritsFrom(TComponent) then
4620     exit;
4621   if Assigned(OnPopupMenuClick) then
4622   if (Comp.Tag=0) or (Comp.Tag>PageCount) then
4623      OnPopupMenuClick(Sender); // only notify custom events
4624   case -Comp.Tag of
4625     ord(rNone):
4626       exit;
4627     ord(rNextPage):
4628       Page := Page+1;
4629     ord(rPreviousPage):
4630       Page := Page-1;
4631     ord(rPageAsText):
4632       if Page>0 then
4633         Clipboard.AsText := fPages[Page-1].Text;
4634     ord(rPrint):
4635       if PrintPages(-1,-1) then
4636         if PreviewForm<>nil then
4637           PreviewForm.Close;
4638     ord(rExportPDF):
4639       ExportPDF('',true);
4640     ord(rClose):
4641       if PreviewForm<>nil then
4642         PreviewForm.Close;
4643     ord(rGotoPage), ord(rZoom), ord(rBookmarks):
4644       if Sender.InheritsFrom(TButton) and (PreviewButtons<>nil) then
4645       with PreviewButtons[-1-Comp.Tag],
4646          PreviewForm.ClientToScreen(Point(Left,Top+Height)) do
4647         PopupMenu.Popup(X,Y);
4648     991..1999: // allow -1000-PAGE_WIDTH
4649       Zoom := -1000-Comp.Tag;
4650     2000..4000: begin // allow -2000-OutlineIndex
4651       i := -2000-Comp.Tag;
4652       if cardinal(i)<cardinal(fOutline.Count) then
4653         with TGDIPagereference(fOutline.Objects[i]) do
4654           GotoPosition(Page,Rect.Top);
4655     end;
4656     else
4657       if Cardinal(Comp.Tag)<=Cardinal(PageCount) then
4658         Page := Comp.Tag;
4659   end;
4660   if PreviewForm<>nil then
4661     SetFocus;
4662 end;
4663 
4664 procedure TGDIPages.InternalUnicodeString(const s: SynUnicode;
4665   var PW: PWideChar; var PWLen: integer; size: PSize);
4666 begin
4667   if Assigned(OnStringToUnicode) then begin
4668     fInternalUnicodeString := OnStringToUnicode(s);
4669     PW := pointer(fInternalUnicodeString);
4670     PWLen := length(fInternalUnicodeString);
4671   end else begin
4672     PW := pointer(s);
4673     PWLen := length(s);
4674   end;
4675   if size<>nil then begin
4676     size^.cx := 0;
4677     size^.cy := 0;
4678     GetTextExtentPoint32W(fCanvas.Handle,PW,PWLen,size^);
4679   end;
4680 end;
4681 
4682 procedure TGDIPages.PopupMenuPopup(Sender: TObject);
4683 var P: PChar;
4684     PageFromTo, PageN: string;
4685     M,M2: TMenuItem;
4686     i,j,k: integer;
4687 procedure AddPage(Menu: TMenuItem);
4688 begin
4689   NewPopupMenuItem(format(PageN,[i]),i,Menu).Enabled := i<>Page;
4690 end;
4691 begin
4692   with PopupMenu.Items do
4693     if Count=0 then
4694       exit else
4695     while Count>ord(rClose) do
4696       Delete(ord(rClose)); // delete after "Close" entry
4697   PopupMenu.Items[Ord(rNextPage)-1].Enabled := Page<PageCount;
4698   PopupMenu.Items[Ord(rPreviousPage)-1].Enabled := Page>1;
4699   M := PopupMenu.Items[Ord(rGoToPage)-1];
4700   while M.Count>0 do
4701     M.Delete(0);
4702   M.Enabled := PageCount>1;
4703   if PageCount>=1 then begin // add 'Go to Page' sub menus (group by 10 pages)
4704     P := pointer(string(sReportPopupMenu2));
4705     PageFromTo := GetNextItemS(P); // Pages %d to %d
4706     PageN := GetNextItemS(P);      // Page %d
4707     if PageCount>10 then begin
4708       for j := 0 to PageCount div 10 do begin
4709         k := j*10+1;
4710         if k>PageCount then
4711           break;
4712         M2 := NewPopupMenuItem(format(PageFromTo,[k,k+9]),-800,M);
4713         // Tag=-800 -> no OnClick event triggered for this entry
4714         for i := k to k+9 do
4715           if i>PageCount then
4716             break else
4717             AddPage(M2);
4718       end;
4719     end else
4720       for i := 1 to PageCount do
4721         AddPage(M);
4722   end;
4723   if Assigned(OnPopupMenuPopup) then
4724     OnPopupMenuPopup(Sender);
4725 end;
4726 
4727 {$ifndef USEPDFPRINTER}
TGDIPages.ExportPDFStreamnull4728 function TGDIPages.ExportPDFStream(aDest: TStream): boolean;
4729 var PDF: TPDFDocument;
4730     BackgroundImage: TPdfImage;
4731     page: TPdfPage;
4732     i: integer;
4733 begin
4734   try
4735     PDF := TPDFDocument.Create(UseOutlines,0,ExportPDFA1,
4736       TPdfEncryption.New(ExportPDFEncryptionLevel,ExportPDFEncryptionUserPassword,
4737         ExportPDFEncryptionOwnerPassword,ExportPDFEncryptionPermissions));
4738     try
4739       PDF.GeneratePDF15File := ExportPDFGeneratePDF15File;
4740       //PDF.CompressionMethod := cmNone;
4741       with PDF.Info do begin
4742         Title := SysUtils.Trim(Caption);
4743         if ExportPDFApplication='' then
4744           Creator := trim(Application.Title) else
4745           Creator := trim(ExportPDFApplication);
4746         Author := ExportPDFAuthor;
4747         Subject := ExportPDFSubject;
4748         Keywords := ExportPDFKeywords;
4749       end;
4750       PDF.EmbeddedTTF := ExportPDFEmbeddedTTF;
4751       {$ifndef NO_USE_UNISCRIBE}
4752       PDF.UseUniscribe := ExportPDFUseUniscribe;
4753       {$endif}
4754       PDF.UseFontFallBack := ExportPDFUseFontFallBack;
4755       if ExportPDFFontFallBackName<>'' then
4756         PDF.FontFallBackName := ExportPDFFontFallBackName;
4757       PDF.ForceJPEGCompression := ExportPDFForceJPEGCompression;
4758       if ExportPDFBackground=nil then
4759         BackgroundImage := nil else begin
4760         BackgroundImage := TPdfImage.Create(PDF,ExportPDFBackground,true);
4761         PDF.AddXObject('BackgroundImage',BackgroundImage);
4762       end;
4763       PDF.SaveToStreamDirectBegin(aDest);
4764       for i := 0 to PageCount-1 do
4765       with Pages[i] do begin
4766         // this loop will do all the magic :)
4767         PDF.DefaultPageWidth := PdfCoord(25.4*SizePx.X/fPrinterPxPerInch.x);
4768         PDF.DefaultPageHeight := PdfCoord(25.4*SizePx.Y/fPrinterPxPerInch.y);
4769         page := PDF.AddPage;
4770         if BackgroundImage<>nil then
4771           PDF.Canvas.DrawXObject(0,0,page.PageWidth,page.PageHeight,'BackgroundImage');
4772         PDF.Canvas.RenderMetaFile(GetMetaFileForPage(i),Screen.PixelsPerInch/fPrinterPxPerInch.x);
4773         PDF.SaveToStreamDirectPageFlush;
4774       end;
4775       PDF.SaveToStreamDirectEnd;
4776     finally
4777       PDF.Free;
4778     end;
4779     result := true;
4780   except
4781     result := false;
4782   end;
4783 end;
4784 {$endif}
4785 
ExportPDFnull4786 function TGDIPages.ExportPDF(aPdfFileName: TFileName; ShowErrorOnScreen: boolean;
4787   LaunchAfter: boolean): boolean;
4788 {$ifdef USEPDFPRINTER}
4789 var DefaultPrinter: integer;
4790 {$else}
ValidFileNamenull4791 function ValidFileName(const FN: TFileName): TFileName;
4792 var i: integer;
4793 begin
4794   result := FN;
4795   for i := length(result) downto 1 do
4796     if ord(result[i]) in [ord('/'),ord(':'),ord('\'),ord('.')] then
4797       delete(result,i,1);
4798   i := length(Result);
4799   while (i>0) and (ord(result[i]) in [ord(' '),ord('-')]) do dec(i);
4800   SetLength(Result,i);
4801   result := trim(result);
4802 end;
4803 var PDFFileName: TFileName;
4804     PDFFile: TFileStream;
4805     i: integer;
4806     Name: string;
4807     TempDir: TFileName;
4808 {$endif}
4809 begin
4810   result := False;
4811   if Self=nil then
4812     exit;
4813   if PageCount>10 then
4814     Screen.Cursor := crHourGlass;
4815 {$ifdef USEPDFPRINTER}
4816   if HasPDFPrinterInstalled then begin
4817     DefaultPrinter := Printer.PrinterIndex;
4818     Printer.PrinterIndex := fPDFPrinterIndex;
4819     PrintPages(0,0);
4820     Printer.PrinterIndex := DefaultPrinter;
4821   end;
4822 {$else}
4823   // use the Synopse PDF engine
4824   if aPdfFileName='' then
4825   with TSaveDialog.Create(nil) do
4826   try
4827     TempDir := GetCurrentDir;
4828     Filter := sPDFFile+' (*.pdf)|*.pdf';
4829     Title := Caption;
4830     FileName := ValidFileName(Caption);
4831     DefaultExt := 'pdf';
4832     Options := [ofOverwritePrompt,ofHideReadOnly,ofEnableSizing];
4833     repeat
4834       if not Execute then
4835         exit;
4836       PDFFileName := FileName;
4837       i := FileCreate(PDFFileName); // test file create (pdf not already opened)
4838       if i>0 then break;
4839       MessageBox(0,pointer(Format(SIniFileWriteError,[PDFFileName])),nil,MB_ICONERROR);
4840     until false;
4841     FileClose(i);
4842   finally
4843     SetCurrentDir(TempDir); // allow unplug e.g. any USB
4844     Free;
4845   end else
4846     PDFFileName := aPdfFileName;
4847   try
4848     PDFFile := TFileStream.Create(PDFFileName,fmCreate);
4849     try
4850       ExportPDFStream(PDFFile);
4851     finally
4852       PDFFile.Free;
4853     end;
4854     if LaunchAfter then
4855       ShellExecute(Application.MainForm.Handle,'open',Pointer(PDFFileName),
4856         nil,nil,SW_NORMAL);
4857   except
4858     on E: Exception do begin // show any error raised during PDF creation
4859       if ShowErrorOnScreen then
4860         MessageBox(0,pointer(E.Message),Pointer(Name),MB_ICONERROR);
4861       exit;
4862     end;
4863   end;
4864 {$endif}
4865   result := true;
4866   if PageCount>10 then
4867     Screen.Cursor := crDefault;
4868 end;
4869 
4870 procedure TGDIPages.WMEraseBkgnd(var Message: TWmEraseBkgnd);
4871 var R: TRect;
4872 begin
4873   Message.Result := 1; // no erasing is necessary after this method call
4874   if Message.DC=0 then exit;
4875   // erase outside the preview surface
4876   R.Left := 0;
4877   R.Right := fPreviewSurface.left;
4878   R.Top := 0;
4879   R.Bottom := Height;
4880   FillRect(Message.DC,R,Brush.Handle);
4881   R.Left := R.Right+fPreviewSurface.Width;
4882   R.Right := Width;
4883   FillRect(Message.DC,R,Brush.Handle);
4884   R.Left := 0;
4885   R.Bottom := fPreviewSurface.Top;
4886   FillRect(Message.DC,R,Brush.Handle);
4887   R.Top := fPreviewSurface.Top+fPreviewSurface.Height;
4888   R.Bottom := Height;
4889   FillRect(Message.DC,R,Brush.Handle);
4890 end;
4891 
4892 procedure TGDIPages.AppendRichEdit(RichEditHandle: HWnd;
4893   EndOfPagePositions: PIntegerDynArray);
4894 var Range: TFormatRange;
4895     LogX, LogY, LastChar, MaxLen, OldMap: integer;
4896     TextLenEx: TGetTextLengthEx; // RichEdit 2.0 Window Class
4897 begin
4898   if (Self<>nil) and (fCanvas<>nil) then
4899   with Range do begin
4900     LogX := GetDeviceCaps(fCanvas.Handle, LOGPIXELSX);
4901     LogY := GetDeviceCaps(fCanvas.Handle, LOGPIXELSY);
4902     rcPage.Left := (fPageMarginsPx.Left*1440) div LogX;
4903     rcPage.Right := ((fPhysicalSizePx.x-fPageMarginsPx.Right)*1440) div LogX;
4904     rcPage.Top := ((fPageMarginsPx.Top+fHeaderHeight)*1440) div LogY;
4905     rcPage.Bottom := ((fPhysicalSizePx.y-fPageMarginsPx.Bottom-fFooterHeight)*1440) div LogY;
4906     CheckHeaderDone;
4907     rc := rcPage;
4908     rc.Top := (fCurrentYPos*1440) div LogY;
4909     LastChar := 0;
4910     with TextLenEx do begin
4911       flags := GTL_DEFAULT;
4912       codepage := CP_ACP;
4913     end;
4914     MaxLen := SendMessage(RichEditHandle, EM_GETTEXTLENGTHEX, Integer(@TextLenEx), 0);
4915     chrg.cpMax := -1;
4916     OldMap := SetMapMode(hdc, MM_TEXT);
4917     try
4918       SendMessage(RichEditHandle, EM_FORMATRANGE, 0, 0);
4919       repeat
4920         chrg.cpMin := LastChar;
4921         hdc := fCanvas.Handle;
4922         hdcTarget := hdc;
4923         LastChar := SendMessage(RichEditHandle, EM_FORMATRANGE, 1, Integer(@Range));
4924         if EndOfPagePositions<>nil then
4925           AddInteger(EndOfPagePositions^,LastChar);
4926         if cardinal(LastChar)>=cardinal(MaxLen) then
4927           break;
4928         NewPageInternal;
4929         DoHeader;
4930         rc := rcPage;
4931       until false;
4932       fCurrentYPos := (rc.Bottom*LogY) div 1440;
4933     finally
4934       SendMessage(RichEditHandle, EM_FORMATRANGE, 0, 0);
4935       SetMapMode(hdc, OldMap);
4936     end;
4937   end;
4938 end;
4939 
AddBookMarknull4940 function TGDIPages.AddBookMark(const aBookmarkName: string; aYPosition: integer=0): boolean;
4941 begin
4942   if fBookmarks.IndexOf(aBookmarkName)>=0 then // avoid duplicate bookmarks
4943     result := false else begin
4944     if aYPosition=0 then begin
4945       CheckYPos;
4946       aYPosition := fCurrentYPos;
4947     end;
4948     fBookMarks.AddObject(aBookmarkName,
4949       TGDIPagereference.Create(PageCount,0,aYPosition,0,0));
4950     {$ifndef USEPDFPRINTER}
4951     fCanvas.MoveTo(0,aYPosition);
4952     GDICommentBookmark(fCanvas.Handle,StringToUTF8(aBookmarkName));
4953     {$endif}
4954     result := true;
4955   end;
4956 end;
4957 
4958 procedure TGDIPages.GotoPosition(aPage: integer; aYPos: integer);
4959 begin
4960   Page := aPage;
4961   HorzScrollbar.Position := 0;
4962   VertScrollbar.Position := (aYPos*VertScrollbar.Range) div fPhysicalSizePx.y
4963 end;
4964 
GotoBookmarknull4965 function TGDIPages.GotoBookmark(const aBookmarkName: string): Boolean;
4966 var i: integer;
4967 begin
4968   i := fBookmarks.IndexOf(aBookmarkName);
4969   result := i>=0;
4970   if result then
4971     with TGDIPagereference(fBookmarks.Objects[i]) do
4972       GotoPosition(Page,Rect.Top);
4973 end;
4974 
4975 procedure TGDIPages.AddOutline(const aTitle: string; aLevel: Integer;
4976   aYPosition: integer=0; aPageNumber: integer=0);
4977 begin
4978   if aPageNumber=0 then
4979     aPageNumber := PageCount;
4980   if aYPosition=0 then begin
4981     CheckYPos;
4982     aYPosition := fCurrentYPos;
4983   end;
4984   fOutline.AddObject(aTitle,
4985     TGDIPagereference.Create(aPageNumber,0,aYPosition,0,aLevel));
4986   {$ifndef USEPDFPRINTER}
4987   fCanvas.MoveTo(0,aYPosition);
4988   GDICommentOutline(fCanvas.Handle, StringToUtf8(aTitle),aLevel);
4989   {$endif}
4990 end;
4991 
4992 procedure TGDIPages.AddLink(const aBookmarkName: string; aRect: TRect; aPageNumber: integer=0);
4993 begin
4994   if aPageNumber=0 then
4995     aPageNumber := PageCount;
4996   aRect := MmToPrinter(aRect);
4997   with aRect do
4998     fLinks.AddObject(aBookmarkName,
4999       TGDIPagereference.Create(aPageNumber,Left,Top,Right,Bottom));
5000   {$ifndef USEPDFPRINTER}
5001   GDICommentLink(fCanvas.Handle,StringToUtf8(aBookmarkName),aRect);
5002   {$endif}
5003 end;
5004 
5005 
5006 { TGDIPagereference }
5007 
5008 constructor TGDIPagereference.Create(PageNumber: integer; Left, Top, Right,
5009   Bottom: integer);
5010 begin
5011   inherited Create;
5012   Page := PageNumber;
5013   Rect.Left := Left;
5014   Rect.Top := Top;
5015   Rect.Right := Right;
5016   Rect.Bottom := Bottom;
5017 end;
5018 
5019 procedure TGDIPagereference.ToPreview(Pages: TGDIPages);
5020 var W,H: integer;
5021 begin // do it for all pages (zoom is not reset between page shift)
5022   if Page<>0 then
5023     with Pages.fPreviewSurface do begin
5024       W := Width-GRAY_MARGIN*2;
5025       H := Height-GRAY_MARGIN*2;
5026       Preview.Left := GRAY_MARGIN+(Rect.Left*W) div Pages.fPhysicalSizePx.x;
5027       Preview.Right := GRAY_MARGIN+(Rect.Right*W) div Pages.fPhysicalSizePx.x;
5028       Preview.Top := GRAY_MARGIN+(Rect.Top*H) div Pages.fPhysicalSizePx.y;
5029       Preview.Bottom := GRAY_MARGIN+(Rect.Bottom*H) div Pages.fPhysicalSizePx.y;
5030     end;
5031 end;
5032 
5033 
5034 { THeaderFooter }
5035 
5036 constructor THeaderFooter.Create(Report: TGDIPages; doubleline: boolean;
5037   const aText: SynUnicode=''; IsText: boolean=false);
5038 begin
5039   Text := aText;
5040   State := Report.SavedState;
5041   if not IsText then
5042     if doubleline then
5043       State.Flags := State.Flags or FORMAT_DOUBLELINE else
5044       State.Flags := State.Flags or FORMAT_SINGLELINE;
5045 end;
5046 
5047 
5048 {$ifdef RENDERPAGES}
5049 
5050 { TRenderPages }
5051 
5052 procedure TRenderPages.Clear;
5053 begin
5054   inherited;
5055   fRdrCol.Clear;
5056   fFontCache.Clear;
5057 end;
5058 
5059 constructor TRenderPages.Create(AOwner: TComponent);
5060 begin
5061   inherited;
5062   fRdr := TRenderBox.Create(self);
5063   fRdrCol := TObjectList.Create;
5064   fFontCache := TObjectList.Create;
5065 end;
5066 
5067 destructor TRenderPages.Destroy;
5068 begin
5069   inherited;
5070   FreeAndNil(fRdrCol);
5071   FreeAndNil(fRdr);
5072   FreeAndNil(fFontCache);
5073 end;
5074 
TRenderPages.GetCurrentFontCacheIndexnull5075 function TRenderPages.GetCurrentFontCacheIndex: integer;
5076 var F: TFont;
5077 begin
5078   for result := 0 to fFontCache.Count-1 do
5079     with TFont(fFontCache.List[result]) do
5080       if (Color=Font.Color) and (Height=Font.Height) and (Style=Font.Style) and
5081          (Name=Font.Name) then
5082         exit;
5083   F := TFont.Create;
5084   F.Assign(Font);
5085   result := fFontCache.Add(F);
5086 end;
5087 
TRenderPages.GetCurrentFontCacheIndexAndSelectnull5088 function TRenderPages.GetCurrentFontCacheIndexAndSelect: integer;
5089 var H: HDC;
5090 begin
5091   result := GetCurrentFontCacheIndex;
5092   H := Canvas.Handle;
5093   with TFont(fFontCache[result]) do begin // same as TCanvas.CreateFont
5094     SelectObject(H,Handle);
5095     SetTextColor(H,ColorToRGB(Color));
5096     if length(fFontCacheSpace)<fFontCache.Count then
5097       SetLength(fFontCacheSpace,fFontCache.Count+20);
5098     if fFontCacheSpace[result].cx=0 then
5099       GetTextExtentPoint32W(H,' ',1,fFontCacheSpace[result]);
5100   end;
5101 end;
5102 
TRenderPages.GetSavedRendernull5103 function TRenderPages.GetSavedRender: TSavedStateRender;
5104 begin
5105   with result do begin
5106     FirstLineIndent := ParagraphFirstLineIndent;
5107     Before := ParagraphBefore;
5108     After := ParagraphAfter;
5109     RightIndent := ParagraphRightIndent;
5110     LeftIndent := ParagraphLeftIndent;
5111   end;
5112 end;
5113 
5114 procedure TRenderPages.NewPageInternal;
5115 begin
5116   { TODO : close any pending paragraph }
5117   inherited;
5118 end;
5119 
5120 procedure TRenderPages.RdrParagraph;
5121 begin
5122   if ParagraphBefore<>0 then
5123     CurrentYPos := CurrentYPos+ParagraphBefore;
5124   Rdr.Flush(fPageMarginsPx.left,fCurrentYPos,false,0,False);
5125   if ParagraphAfter<>0 then
5126     CurrentYPos := CurrentYPos+ParagraphAfter;
5127 end;
5128 
5129 procedure TRenderPages.RdrPard;
5130 var State: TSavedState;
5131 begin
5132   if self=nil then
5133     exit;
5134   fAlign := taLeft;
5135   SetSavedRender(fDefaultStateRender);
5136   State := SavedState;
5137   if State.Flags<>fDefaultState.Flags then begin
5138     State.Flags := fDefaultState.Flags;
5139     SavedState := State; // will set FORMAT_RIGHT/CENTER/JUSTIFIED
5140   end;
5141 end;
5142 
5143 procedure TRenderPages.RdrPardPlain;
5144 begin
5145   if self=nil then
5146     exit;
5147   if fDefaultState.FontName='' then
5148     RdrPlain else
5149     SavedState := fDefaultState;
5150   SetSavedRender(fDefaultStateRender);
5151 end;
5152 
5153 procedure TRenderPages.RdrPlain;
5154 var State: TSavedState;
5155 begin
5156   if self=nil then
5157     exit;
5158   if (fDefaultState.FontName='') or (fDefaultState.Flags=0) then begin
5159     Font.Size := 12;
5160     Font.Style := [];
5161     Font.Color := clBlack;
5162   end else begin
5163     State := fDefaultState;
5164     State.Flags :=
5165       // void FORMAT_RIGHT/CENTER/JUSTIFIED
5166       (State.Flags and not (FORMAT_RIGHT or FORMAT_CENTER or FORMAT_JUSTIFIED)) or
5167       // keep current FORMAT_RIGHT/CENTER/JUSTIFIED
5168       (TextFormatsToFlags and (FORMAT_RIGHT or FORMAT_CENTER or FORMAT_JUSTIFIED));
5169     SavedState := State;
5170   end;
5171 end;
5172 
5173 procedure TRenderPages.RdrSetCurrentStateAsDefault;
5174 begin
5175   fDefaultState := SavedState;
5176   fDefaultStateRender := GetSavedRender;
5177 end;
5178 
RdrTableBeginnull5179 function TRenderPages.RdrTableBegin(const PercentWidth: array of integer): Boolean;
5180 var i, sum, w: integer;
5181     col: TRenderBox;
5182 begin
5183   result := (Self<>nil) and (fRdrCol.Count=0);
5184   if not result then
5185     exit;
5186   sum := 0;
5187   for i := 0 to high(PercentWidth) do
5188     inc(sum,PercentWidth[i]);
5189   if sum<=0 then begin
5190     result := false;
5191     exit;
5192   end;
5193   w := fPhysicalSizePx.x-fPageMarginsPx.Left-fPageMarginsPx.right;
5194   for i := 0 to high(PercentWidth) do begin
5195     col := TRenderBox.Create(self);
5196     col.Width := (w*100)div sum;
5197     fRdrCol.Add(col);
5198   end;
5199 end;
5200 
TRenderPages.RdrTableColumnnull5201 function TRenderPages.RdrTableColumn(aColumnIndex: Integer): TRenderBox;
5202 begin
5203   if (Self=nil) or (cardinal(aColumnIndex)>=cardinal(fRdrCol.Count-1)) then
5204     result := nil else
5205     result := TRenderBox(fRdrCol.List[aColumnIndex]);
5206 end;
5207 
TRenderPages.RdrTableEndnull5208 function TRenderPages.RdrTableEnd: Boolean;
5209 begin
5210   result := (Self<>nil) and (fRdrCol.Count>0);
5211   if not result then
5212     exit;
5213 
5214   fRdrCol.Clear;
5215 end;
5216 
5217 procedure TRenderPages.RestoreSavedLayout;
5218 begin
5219   if Self=nil then exit; // avoid GPF
5220   if fSavedCount>=length(fSavedRender) then
5221     Setlength(fSavedRender,fSavedCount+20);
5222   fSavedRender[fSavedCount] := GetSavedRender;
5223   inherited;
5224 end;
5225 
5226 procedure TRenderPages.SaveLayout;
5227 begin
5228   if Self=nil then exit; // avoid GPF
5229   if fSavedCount<=0 then
5230     exit;
5231   inherited;
5232   SetSavedRender(fSavedRender[fSavedCount]);
5233 end;
5234 
5235 procedure TRenderPages.SetSavedRender(const State: TSavedStateRender);
5236 begin
5237  with State do begin
5238     ParagraphFirstLineIndent := FirstLineIndent;
5239     ParagraphBefore := Before;
5240     ParagraphAfter := After;
5241     ParagraphRightIndent := RightIndent;
5242     ParagraphLeftIndent := LeftIndent;
5243   end;
5244 end;
5245 
5246 
5247 { TRenderBox }
5248 
5249 procedure TRenderBox.AddText(const s: string);
5250 var PW: PWideChar;
5251     PWLen: integer;
5252 begin
5253   if (self=nil) or (Owner=nil) then
5254     exit; // avoid GPF
5255   // convert text to unicode and add to fText[] internal buffer
5256   Owner.InternalUnicodeString(StringToSynUnicode(s),PW,PWLen,nil);
5257   AddText(PW,PWLen);
5258 end;
5259 
5260 procedure TRenderBox.AddText(PW: PWideChar; PWLen: integer);
5261 var PDBeg, PD: PWideChar;
5262     aFontIndex, aFontSpaceWidth: integer;
5263 begin
5264   if (self=nil) or (Owner=nil) or (PWLen=0) then
5265     exit; // avoid GPF
5266   if PWLen+fTextLen>length(fText) then
5267     SetLength(fText,length(fText)+PWLen+1024);
5268   PD := @fText[fTextLen];
5269   inc(fTextLen,PWLen);
5270   // create associated word markers
5271   aFontIndex := Owner.GetCurrentFontCacheIndexAndSelect;
5272   aFontSpaceWidth := Owner.fFontCacheSpace[aFontIndex].cx;
5273   repeat
5274     PDBeg := PD;
5275     while true do
5276       case integer(PW^) of
5277         0, 32: break;
5278         1..31: if PD<>PDBeg then break else Inc(PW);
5279         else begin
5280           PD^ := PW^;
5281           inc(PW);
5282           inc(PD);
5283         end;
5284       end;
5285     if fBoxCount>=Length(fBox) then
5286       SetLength(fBox,fBoxCount+200);
5287     with fBox[fBoxCount] do begin
5288       TextOffset := PD-@fText[0];
5289       TextLength := PD-PDBeg;
5290       FontIndex := aFontIndex;
5291       FontSpaceWidth := aFontSpaceWidth;
5292       GetTextExtentPoint32W(Owner.Canvas.Handle,PDBeg,TextLength,Size);
5293       SpaceAfterCount := 0;
5294       while integer(PW^) in [1..32] do begin
5295         PD^ := ' ';
5296         inc(PW);
5297         inc(PD);
5298         inc(SpaceAfterCount);
5299       end;
5300       LinkNumber := fLinksBookMarkNameCurrent;
5301     end;
5302     inc(fBoxCount);
5303   until PW^=#0;
5304 end;
5305 
5306 procedure TRenderBox.Clear;
5307 begin
5308   if Self=nil then
5309     exit;
5310   Finalize(fLinksBookMarkName);
5311   Finalize(fBox);
5312   fLayoutCount := 0;
5313   fBoxCount := 0;
5314   fTextLen := 0;
5315   fHeight := 0;
5316   fLinksBookMarkNameCurrent := 0;
5317 end;
5318 
5319 constructor TRenderBox.Create(Owner: TRenderPages);
5320 begin
5321   fOwner := Owner;
5322   fBiDiMode := Owner.BiDiMode;
5323   fOwnerFont := Owner.Font;
5324 end;
5325 
5326 /// format the already inserted text into the TRenderPages owner
5327 // - this TRenderBox text content will be cleared at the end of this method
5328 // - you don't have to call it usualy: use Owner.RdrParagraph instead
5329 // - by default, will render top aligned to the X=Left/Y=Top position
5330 // - for vertical alignment, specify an height in ForcedHeightBottomCentered
5331 // then will be centered if ForcedAtBottom=false, or bottom aligned if true
5332 // - if CurrentPageOnly is true, will only flush the content which will fit on
5333 // the current page - then the fLayout[] array will contain remaining boxes; otherwise,
5334 // this will flush all content to multiple pages
5335 
5336 procedure TRenderBox.Flush(Left, Top: Integer; CurrentPageOnly: boolean;
5337   ForcedHeightBottomCentered: Integer; ForcedAtBottom: boolean);
5338 var H, Y, i, fitLayout: integer;
5339     WillBreak: boolean;
5340 begin
5341   if (self=nil) or (Owner=nil) then
5342     exit; // avoid GPF
5343   H := GetHeight; // will populate fLayout[] from fBox[] if necessary
5344   { render on document Canvas }
5345   WillBreak := false;
5346   fitLayout := fLayoutCount-1;
5347   for i := 0 to fitLayout do
5348     if fLayout[i].Top>=H then begin
5349       fitLayout := i-1;
5350       WillBreak := true;
5351       break;
5352     end;
5353 
5354   { TODO : handle TGDIPagereference creation from fLayout[].LastBox.LinkNumber }
5355   // reset internal TRenderBox content
5356   Clear;
5357 end;
5358 
GetHeightnull5359 function TRenderBox.GetHeight: integer;
5360 begin
5361   if self=nil then
5362     result := 0 else begin
5363     if fHeight=0 then
5364       // need to recalculate the layout to refresh the resulting Height
5365       InternalRender;
5366     result := fHeight;
5367   end;
5368 end;
5369 
5370 procedure TRenderBox.InternalRender;
5371 var ndx, ndxFirst: integer;
5372     X, Y, H, W, LineW: integer;
5373     txt: PWideChar;
5374     Box: PRenderBoxWord;
5375     LineLayout, LineNdx: integer;
5376 procedure AddLayout(DoLineFeed, LastLine: boolean);
5377 var nspace, Adjust, i, j, aLeft, n: Integer;
5378     align: TTextAlign;
5379     TmpLayout: array of TRenderBoxLayout;
5380 begin
5381   if fLayoutCount>=length(fLayout) then
5382     SetLength(fLayout,fLayoutCount+50);
5383   with fLayout[fLayoutCount] do begin
5384     Text := txt;
5385     with Box^ do
5386       txt := @fText[TextOffset+TextLength]; // txt^ points to ' ' after text
5387     Length := txt-Text;
5388     Left := X;
5389     Top := Y;
5390     Width := W;
5391     LineIndex := LineNdx;
5392     LastBox := Box;
5393     BreakExtra := 0;
5394     BreakCount := 0;
5395   end;
5396   if DoLineFeed then begin
5397     // we must handle the line feed layout
5398     Align := Owner.TextAlign;
5399     Adjust := LineW-(X+W);
5400     if (Adjust<=0) or
5401        // force left align if wider than expected (i.e. overpass right margin)
5402        (LastLine and (Align=taJustified)) then
5403        // last line of justified paragraph is never justified
5404       Align := taLeft;
5405     if BiDiMode=bdRightToLeft then begin
5406       case Align of
5407         taLeft:  Align := taRight;
5408         taRight: Align := taLeft;
5409       end;
5410       n := fLayoutCount-LineLayout+1;
5411       if n>1 then begin
5412         // multi layouts: change logical to visual order for RTL languages
5413         SetLength(TmpLayout,n);
5414         Move(fLayout[LineLayout],TmpLayOut[0],n*sizeof(TmpLayOut[0]));
5415         aLeft := fLayout[LineLayout].Left;
5416         for i := 0 to n-1 do begin
5417           move(TmpLayout[i],fLayout[fLayoutCount-i],sizeof(TmpLayOut[0]));
5418           with fLayout[fLayoutCount-i] do begin
5419             Left := aLeft;
5420             Inc(aLeft,Width+LastBox^.FontSpaceWidth*LastBox^.SpaceAfterCount);
5421           end;
5422         end;
5423       end;
5424     end;
5425     case Align of
5426       taRight:
5427         for i := LineLayout to fLayoutCount do
5428           inc(fLayout[i].Left,Adjust);
5429       taCenter: begin
5430         Adjust := Adjust div 2;
5431         for i := LineLayout to fLayoutCount do
5432           inc(fLayout[i].Left,Adjust);
5433       end;
5434       taJustified:
5435       if Adjust>0 then begin
5436         // compute SetTextJustification() values and update Left position
5437         aLeft := fLayout[LineLayout].Left;
5438         nspace := 0;
5439         for i := LineLayout to fLayoutCount do
5440           with fLayout[i] do begin
5441             for j := 0 to Length-1 do
5442               if Text[j]=' ' then
5443                 inc(BreakCount);
5444             inc(nspace,BreakCount);
5445           end;
5446         if nspace>0 then
5447           for i := LineLayout to fLayoutCount do
5448           with fLayout[i] do begin
5449             Left := aLeft;
5450             BreakExtra := (Adjust*BreakCount) div nspace;
5451             dec(Width,LastBox^.FontSpaceWidth*BreakCount-BreakExtra);
5452             inc(aLeft,Width);
5453           end;
5454       end;
5455     end;
5456     for i := LineLayout to fLayoutCount do
5457       fLayout[i].Height := H; // same height for all fLayout[] of this line
5458     with Owner do
5459       X := MmToPrinterPxX(ParagraphLeftIndent);
5460     inc(Y,H);
5461     H := 0; // force recalculate line height
5462     LineLayout := fLayoutCount;
5463     inc(LineNdx);
5464   end else begin
5465     // just append this "word" box to fLayout[fLayoutCount]
5466     with Box^ do
5467       // compute next position
5468       inc(X,W+FontSpaceWidth*SpaceAfterCount);
5469   end;
5470   inc(fLayoutCount);
5471   ndxFirst := ndx+1;
5472   W := 0;
5473 end;
5474 begin // compute TRenderBoxWord.X/Y and fHeight
5475   fHeight := 0;
5476   fLayoutCount := 0;
5477   SetLength(fLayout,fBoxCount shr 2);
5478   if fBoxCount=0 then
5479     exit; // no text added
5480   with Owner do begin
5481     X := MmToPrinterPxX(ParagraphFirstLineIndent);
5482     LineW := self.fWidth-MmToPrinterPxX(ParagraphRightIndent);
5483   end;
5484   LineNdx := 0;
5485   Y := 0;
5486   H := 0;
5487   W := 0;
5488   LineLayout := 0;
5489   txt := @fText[0];
5490   ndxFirst := 0;
5491   for ndx := 0 to fBoxCount-1 do begin
5492     Box := @fBox[ndx];
5493     if Box^.Size.cy>H then
5494       H := Box^.Size.cy;
5495     inc(W,Box^.Size.cx);
5496     if ndx=fBoxCount-1 then
5497       // reached last box -> flush pending line content
5498       AddLayout(true,true) else
5499     with fBox[ndx+1] do
5500       if X+W+Size.cx>LineW then
5501         // not enough space in current line -> flush+adjust and go to next line
5502         AddLayout(true,false) else
5503       if (FontIndex<>Box^.FontIndex) or (LinkNumber<>Box^.LinkNumber) then
5504         // text formatting or Link will change -> add a layout box
5505         AddLayout(false,false);
5506   end;
5507   fHeight := Y;
5508 end;
5509 
5510 procedure TRenderBox.LinkBegin(const aBookmarkName: string);
5511 
5512 begin
5513   if (self=nil) or (Owner=nil) then
5514     exit; // avoid GPF
5515   LinkEnd; // no nested links
5516   fLinksBookMarkNameCurrent := Length(fLinksBookMarkName)+1;
5517   SetLength(fLinksBookMarkName,fLinksBookMarkNameCurrent);
5518   fLinksBookMarkName[fLinksBookMarkNameCurrent-1] := aBookmarkName;
5519 end;
5520 
LinkEndnull5521 function TRenderBox.LinkEnd: boolean;
5522 begin
5523   result := false;
5524   if (self=nil) or (Owner=nil) or (fLinksBookMarkNameCurrent=0) then
5525     exit; // avoid GPF
5526   fLinksBookMarkNameCurrent := 0;
5527   result := true;
5528 end;
5529 
5530 procedure TRenderBox.NewLine;
5531 begin
5532   if (self=nil) or (Owner=nil) then
5533     exit; // avoid GPF
5534 
5535 end;
5536 
5537 procedure TRenderBox.Pard;
5538 begin
5539   if (self<>nil) and (Owner<>nil) then // avoid GPF
5540     Owner.RdrPard;
5541 end;
5542 
5543 procedure TRenderBox.PardPlain;
5544 begin
5545   if (self<>nil) and (Owner<>nil) then // avoid GPF
5546     Owner.RdrPardPlain;
5547 end;
5548 
5549 procedure TRenderBox.Plain;
5550 begin
5551   if (self<>nil) and (Owner<>nil) then // avoid GPF
5552     Owner.RdrPlain;
5553 end;
5554 
5555 {$endif RENDERPAGES}
5556 
5557 end.
5558 
5559