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