代码拉取完成,页面将自动刷新
/// Reporting unit
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit mORMotReport;
(*
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2021 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse framework.
The Initial Developer of the Original Code is Angus Johnson.
Portions created by the Initial Developer are Copyright (C) 2003
the Initial Developer. All Rights Reserved.
Portions created by Arnaud Bouchez for Synopse are Copyright (C) 2021
Arnaud Bouchez. All Rights Reserved.
Contributor(s):
- Celery
- Kevinday
- Leo
- Mike Lamusse (mogulza)
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Initial Notes and Copyright:
******************************
Component Name: TPages
Module: Pages
Description: Report writer and previewer
Version: 1.6
Date: 25-MAY-2004 (initial version)
Target: Win32, Delphi 3 - Delphi 7
Author: Angus Johnson, http://www.angusj.com
Copyright 2003 Angus Johnson
Notes:
* TGDIPages is designed as a simple lightweight report writer. Reports are
created in code, they are not banded, nor are they directly linked to
TDatasets. If you're looking for a dataset aware report writer then
TGDIPages is not for you. TGDIPages is a visual component based on a
TScrollbox, though it isn't necessary to view reports prior to printing.
* Main features include:
+ Text can be output either wrapped between page margins, in columns
or at specified offsets.
+ Multiple alignment options -
> left, right and justified in non-columned text
> left, right and currency in columned text
+ Tabs to Assigned tabstops
+ Multi-line page headers, footers and column headers
+ Multiple fonts can be used.
+ Angled text output
+ Single, line & half, and double line spacing
+ Methods for printing bitmaps, metafiles, lines, boxes and arrows
+ Page numbering can be redefined
+ Text output 'groups' prevent blocks of text spanning across pages
+ Designed around a TScrollbox descendant preview window with:
mouse click zoom control; keyboard handling of lineup, linedown,
pageup and pagedown srolling; mouse wheel scrolling.
* In order to get the best print quality, TGDIPages uses the selected
printer driver's resolution to prepare reports.
If a report will be printed to a different printer (eg by using a
PrintDialog), it's preferable to change to that printer object BEFORE
preparing the report. Otherwise, the report will be stretch down to the
printer canvas resulting in a slight degradation in print quality.
Enhanced for the freeware Synopse framework:
**********************************************
- Windows XP, Vista and Seven compatibility adding
- fix printing metafiles and page groups on some printer (with bad drivers)
- optionnaly use antialiaised drawing (via SynGdiPlus unit)
- popup menu creation, with zoom, print or copy features (and custom entries)
- direct PDF export (if a PDF printer is installed, or via SynPdf unit)
- direct page export to clipboard as text
- optional Black and White / Duplex mode (with out TPrinterNew custom class)
- new useful methods for easy text adding (especially column definition)
- new fast double buffering drawing
- full Unicode text process (even before Delphi 2009)
- speed up and various bug fixes to work with Delphi 5 up to XE3
Modifications 2009-2021 Arnaud Bouchez
Version 1.4 - February 8, 2010
- whole Synopse SQLite3 database framework released under the GNU Lesser
General Public License version 3, instead of generic "Public Domain"
Version 1.6
- new version, using our SynGdiPlus unit: if the GDI+ is available,
it will use it to render the page using its AntiAliased engine;
under Windows 98 or 2000, no antialiasing will occur, but the program
will still run (since our SynGdiPlus unit use dynamic linking of the
gdiplus.dll library);
if only GDI+ 1.0 is available (i.e. with a Windows XP without any Office
2003/2007 installed) a pure Delphi version of GDI+ drawing is used, which
should not be able to convert 100% of page content, but should work on
most cases
Version 1.8
- some fixes for compilation under Delphi 2009/2010
Version 1.9
- new AppendRichEdit method to draw RichEdit content
- new WordWrapLeftCols property used to optionaly word wrap caLeft columns
into multiple lines, i.e. if the text is wider than the column width, its
content is wrapped to the next line (set to false by default) - this
also will handle #13/#10 in column text as a "go to next line" command
Version 1.9.2
- fix font color issue in header and footers
- safety additional code to avoid any division per 0 exception
Version 1.11
- fixed issue in TGDIPages.AppendRichEdit - see user feedback from
https://synopse.info/forum/viewtopic.php?pid=671#p671
- added Author, Subject and Keywords optional parameters to TGDIPages.ExportPDF
Version 1.12
- fixed one issue (in SynGdiPlus) for displaying bitmaps in anti-aliased mode
and displaying underlined or stroken out text - new ForceInternalAntiAliased
method (true by default) using SynGdiPlus instead of GDI+ 1.1 native conversion
- OnStringToUnicodeEvent is now called for all text, whatever the alignment is
- new property BiDiMode, for formatting the text in right to left order
- added new DrawBMP overloaded method to add some bitmap as a (centered)
paragraph, with some optional legend - bitmaps are now cached and reused
in the exported PDF, if the same one is drawn multiple time in the document
- added new AddBookMark, AddOutline and AddLink methods (working also with
the PDF export using SynPdf) :)
- live navigation via links in the preview screen, and via the new 'Bookmarks'
popup menu entry
- additional ExportPDF* properties used during PDF export
- introducing the new TRenderPages class, for high-quality document rendering
(used e.g. within SynProject for document preview and PDF generation,
with basic understanding of the rtf format)
Version 1.15
- fixed an endless loop in TGDIPages.DrawTextAcrossCols when wrapping text
- fixed an issue in TGDIPages.DrawTextAcrossCols when test is exported to pdf
(wrong clipping region set)
- if TGDIPages.WordWrapLeftCols=TRUE, won't wrap column headers
Version 1.16
- includes new TSynAnsiConvert classes for handling Ansi charsets
- some minor fixes (e.g. preview landscape or keys for popup menu)
- fix issue in TGDIPages.AppendRichEdit() when called on a blank page
- enhanced the print preview screen with a left-sided button bar
- new TGdiPages.RenderGraphic method (accepting both TBitmap and TMetaFile)
Version 1.17
- now whole text process is UNICODE-ready, even on pre-Delphi-2009 versions
- now implements font fall-back in internal Anti-Aliaised drawing,
if the new ForceInternalAntiAliasedFontFallBack property is set to TRUE
Version 1.18
- renamed SQLite3Pages.pas to mORMotReport.pas
- TGdiPages now handles several page layouts per report - see new overloaded
TGDIPages.NewPageLayout() methods and also Orientation property which now
allows several page orientations per report - feature request [204b698b3d]
- now internal page content (TMetaFile) is compressed using our SynLZ
algorithm: we were able to generate reports with more than 20,000 pages!
- added optional EndOfPagePositions parameter to TGDIPages.AppendRichEdit()
- speed up and memory resource decrease for pdf export of huge reports
- fixed issue about disabled Zoom menu entry if no Outline is defined
- fixed unexpected exception with TGDIPages.DrawText() and huge string
- proper function TGDIPages.GetLineHeight() computation - from kln feedback
- added ExportPDFBackground and ExportPDFGeneratePDF15File properties
- added ExportPDFEncryptionLevel/User/OwnerPassword/Permissions properties to
optionally export report as 40 bit or 128 bit encrypted pdf
- added setter method for ZoomStatus property (during preview) - [dd656b470b]
- added TGDIPages.ExportPDFStream() method - to be used e.g. on servers
- fixed [cfdc644038] about truncated parenthesis in pdf export for caCurrency
- fixed [e7ffb69131] about TGDIPages.DrawGraphic() when the TGraphic is Empty
- allow preview as a blank colored component at design time (thanks to Celery)
- added VisibleButtons optional parameter to TGDIPages.ShowPreviewForm method
as requested by [4d64a52675]
- added withNewLine optional parameter to DrawText*() methods so that you
may be able to append some text without creating a new paragraph - from a
proposal patch by Mike Lamusse (mogulza): thanks for sharing!
- added TGDIPages.DrawColumnLine method - thanks kevinday for the patch
*)
interface
{.$define MOUSE_CLICK_PERFORM_ZOOM} // old not user-friendly behavior
{.$define RENDERPAGES} // TRenderBox and TRenderPages are not yet finished
{$define GDIPLUSDRAW}
// optionaly (if ForceNoAntiAliased=false) use GDI+ to draw for antialiasing:
// slower but smoother (need the GDI+ library, best with version 1.1)
{.$define USEPDFPRINTER}
// do not use the Synopse PDF engine, in Delphi code, but a PDF virtual printer
{.$define PRINTERNEW}
// if our custom Printer.pas unit is installed, use TPrinterNew class instead
// of TPrinter to allow Black&White and Duplex printing
// -> disabled by default, should be enabled globaly from the Project Options
{$ifndef ENHANCEDRTL}
{$undef PRINTERNEW}
// Black&White and Duplex printing are only available with our Enhanced RTL
{$endif}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
uses
SynCommons, SynLZ,
{$ifndef USEPDFPRINTER}
SynPdf,
{$endif}
Windows, Messages, SysUtils, Classes, Contnrs,
{$ifdef GDIPLUSDRAW}
SynGdiPlus,
{$endif}
Graphics, Controls, Dialogs, Forms, StdCtrls,
ExtCtrls, WinSpool, Printers, Menus, ShellAPI, RichEdit;
const
MAXCOLS = 20;
MAXTABS = 20;
/// this constant can be used to be replaced by the page number in
// the middle of any text
PAGENUMBER = '<<pagenumber>>';
type
/// text paragraph alignment
TTextAlign = (taLeft,taRight,taCenter,taJustified);
/// text column alignment
TColAlign = (caLeft,caRight,caCenter, caCurrency);
/// text line spacing
TLineSpacing = (lsSingle, lsOneAndHalf, lsDouble);
/// available zoom mode
// - zsPercent is used with a zoom percentage (e.g. 100% or 50%)
// - zsPageFit fits the page to the report
// - zsPageWidth zooms the page to fit the report width on screen
TZoomStatus = (zsPercent, zsPageFit, zsPageWidth);
/// Event triggered when a new page is added
TNewPageEvent = procedure(Sender: TObject; PageNumber: integer) of object;
/// Event triggered when the Zoom was changed
TZoomChangedEvent = procedure(Sender: TObject;
Zoom: integer; ZoomStatus: TZoomStatus) of object;
/// Event triggered to allow custom unicode character display on the screen
// - called for all text, whatever the alignment is
// - Text content can be modified by this event handler to customize
// some characters (e.g. '>=' can be converted to the one Unicode glyph)
TOnStringToUnicodeEvent = function(const Text: SynUnicode): SynUnicode of object;
/// available known paper size for NewPageLayout() method
TGdiPagePaperSize = (
psA4, psA5, psA3, psLetter, psLegal);
TGDIPages = class;
/// a report layout state, as used by SaveLayout/RestoreSavedLayout methods
TSavedState = record
FontName: string;
FontColor: integer;
Flags: integer;
LeftMargin: integer;
RightMargin: integer;
BiDiMode: TBiDiMode;
end;
/// internal format of the header or footer text
THeaderFooter = class
public
Text: SynUnicode;
State: TSavedState;
/// initialize the header or footer parameters with current report state
constructor Create(Report: TGDIPages; doubleline: boolean;
const aText: SynUnicode=''; IsText: boolean=false);
end;
/// internal format of a text column
TColRec = record
ColLeft, ColRight: integer;
ColAlign: TColAlign;
ColBold: boolean;
end;
TPopupMenuClass = class of TPopupMenu;
/// hack the TPaintBox to allow custom background erase
TPagePaintBox = class(TPaintBox)
private
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
end;
/// internal structure used to store bookmarks or links
TGDIPagereference = class
public
/// the associated page number (starting at 1)
Page: Integer;
/// graphical coordinates of the hot zone
// - for bookmarks, Top is the Y position
// - for links, the TRect will describe the hot region
// - for Outline, Top is the Y position and Bottom the outline tree level
Rect: TRect;
/// coordinates on screen of the hot zone
Preview: TRect;
/// initialize the structure with the current page
constructor Create(PageNumber: integer; Left, Top, Right, Bottom: integer);
/// compute the coordinates on screen into Preview
procedure ToPreview(Pages: TGDIPages);
end;
/// contains one page
TGDIPageContent = record
/// SynLZ-compressed content of the page
MetaFileCompressed: RawByteString;
/// text equivalent of the page
Text: string;
/// the physical page size
SizePx: TPoint;
/// margin of the page
MarginPx: TRect;
/// non printable offset of the page
OffsetPx: TPoint;
end;
/// used to store all pages of the report
TGDIPageContentDynArray = array of TGDIPageContent;
/// the available menu items
TGdiPagePreviewButton = (
rNone, rNextPage, rPreviousPage, rGotoPage, rZoom, rBookmarks,
rPageAsText, rPrint, rExportPDF, rClose);
/// set of menu items
TGdiPagePreviewButtons = set of TGdiPagePreviewButton;
/// Report class for generating documents from code
// - data is drawn in memory, they displayed or printed as desired
// - allow preview and printing, and direct pdf export
// - handle bookmark, outlines and links inside the document
// - page coordinates are in mm's
TGDIPages = class(TScrollBox)
protected
fPreviewSurface: TPagePaintbox;
fCanvas: TMetafileCanvas;
fCanvasText: string;
fBeforeGroupText: string;
fGroupPage: TMetafile;
fPages: TGDIPageContentDynArray;
fHeaderLines: TObjectList;
fFooterLines: TObjectList;
fColumns: array of TColRec;
fColumnHeaderList: array of record
headers: TSynUnicodeDynArray;
flags: integer;
end;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
fZoomTimer: TTimer;
{$endif}
fPtrHdl: THandle;
fTabCount: integer;
fCurrentPrinter: string;
fOrientation: TPrinterOrientation;
fDefaultLineWidth: integer; //drawing line width (boxes etc)
fVirtualPageNum: integer;
fCurrPreviewPage: integer;
fZoomIn: boolean;
fLineHeight: integer; //Text line height
fLineSpacing: TLineSpacing;
fCurrentYPos: integer;
fCurrentTextTop, fCurrentTextPage: integer;
fHeaderHeight: integer;
fHangIndent: integer;
fAlign: TTextAlign;
fBiDiMode: TBiDiMode;
fPageMarginsPx: TRect;
fHasPrinterInstalled: boolean;
{$ifdef USEPDFPRINTER}
fHasPDFPrinterInstalled: boolean;
fPDFPrinterIndex: integer;
{$else}
fForceJPEGCompression: Integer;
fExportPDFApplication: string;
fExportPDFAuthor: string;
fExportPDFSubject: string;
fExportPDFKeywords: string;
fExportPDFEmbeddedTTF: boolean;
fExportPDFA1: boolean;
fExportPDFBackground: TGraphic;
{$ifndef NO_USE_UNISCRIBE}
fExportPDFUseUniscribe: boolean;
{$endif}
fExportPDFUseFontFallBack: boolean;
fExportPDFFontFallBackName: string;
fExportPDFEncryptionLevel: TPdfEncryptionLevel;
fExportPDFEncryptionUserPassword: string;
fExportPDFEncryptionOwnerPassword: string;
fExportPDFEncryptionPermissions: TPdfEncryptionPermissions;
fExportPDFGeneratePDF15File: boolean;
{$endif}
fPrinterPxPerInch: TPoint;
fPhysicalSizePx: TPoint; //size of page in printer pixels
fPhysicalOffsetPx: TPoint; //size of non-printing margins in pixels
fCustomPxPerInch: TPoint;
fCustomPageSize: TPoint;
fCustomNonPrintableOffset: TPoint;
fCustomPageMargins: TRect;
fZoom: integer;
fZoomStatus: TZoomStatus;
fNegsToParenthesesInCurrCols: boolean;
fWordWrapLeftCols: boolean;
fUseOutlines: boolean;
fForceScreenResolution: boolean;
fHeaderDone: boolean;
fFooterHeight: integer;
fFooterGap: integer;
fInHeaderOrFooter: boolean;
fColumnHeaderPrinted: boolean;
fColumnHeaderPrintedAtLeastOnce: boolean;
fDrawTextAcrossColsDrawingHeader: boolean;
fColumnHeaderInGroup: boolean;
fColumnsUsedInGroup: boolean;
fGroupVerticalSpace: integer;
fGroupVerticalPos: integer;
fZoomChangedEvent: TZoomChangedEvent;
fPreviewPageChangedEvent: TNotifyEvent;
fStartNewPage: TNewPageEvent;
fStartPageHeader: TNotifyEvent;
fEndPageHeader: TNotifyEvent;
fStartPageFooter: TNotifyEvent;
fEndPageFooter: TNotifyEvent;
fStartColumnHeader: TNotifyEvent;
fEndColumnHeader: TNotifyEvent;
fSavedCount: integer;
fSaved: array of TSavedState;
fTab: array of integer;
fColumnsWithBottomGrayLine: boolean;
fColumnsRowLineHeight: integer;
fOnDocumentProducedEvent: TNotifyEvent;
PageRightButton, PageLeftButton: TPoint;
fPagesToFooterText: string; // not SynUnicode, since calls format()
fPagesToFooterAt: TPoint;
fPagesToFooterState: TSavedState;
fMetaFileForPage: TMetaFile;
fCurrentMetaFile: TMetaFile;
procedure GetPrinterParams;
procedure SetAnyCustomPagePx;
function GetPaperSize: TSize;
procedure FlushPageContent;
function PrinterPxToScreenPxX(PrinterPx: integer): integer;
function PrinterPxToScreenPxY(PrinterPx: integer): integer;
procedure ResizeAndCenterPaintbox;
function GetMetaFileForPage(PageIndex: integer): TMetaFile;
procedure SetMetaFileForPage(PageIndex: integer; MetaFile: TMetaFile);
function GetOrientation: TPrinterOrientation;
procedure SetOrientation(orientation: TPrinterOrientation);
procedure SetTextAlign(Value: TTextAlign);
procedure SetPage(NewPreviewPage: integer);
function GetPageCount: integer;
function GetLineHeight: integer;
function GetLineHeightMm: integer;
procedure CheckYPos; //ie: if not vertical room force new page
function GetYPos: integer;
procedure SetYPos(YPos: integer);
procedure NewPageInternal; virtual;
function CreateMetaFile(aWidth, aHeight: integer): TMetaFile;
function CreateMetafileCanvas(Page: TMetafile): TMetafileCanvas;
procedure UpdateMetafileCanvasFont(aCanvas: TMetafileCanvas);
function TextFormatsToFlags: integer;
procedure SetFontWithFlags(flags: integer);
function GetPageMargins: TRect;
procedure SetPageMargins(Rect: TRect);
procedure DoHeader;
procedure DoFooter;
procedure DoHeaderFooterInternal(Lines: TObjectList);
procedure CalcFooterGap;
function GetColumnCount: integer;
function GetColumnRec(col: integer): TColRec;
procedure PrintColumnHeaders;
procedure SetZoom(zoom: integer);
procedure SetZoomStatus(aZoomStatus: TZoomStatus);
procedure ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
procedure ZoomTimer(Sender: TObject);
procedure LineInternal(start, finish : integer; doubleline : boolean); overload;
procedure LineInternal(aty, start, finish : integer; doubleline : boolean); overload;
procedure PrintFormattedLine(s: SynUnicode; flags: integer;
const aBookmark: string=''; const aLink: string=''; withNewLine: boolean=true;
aLinkNoBorder: boolean=false);
procedure LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean=true);
procedure RightOrCenterWrap(const s: SynUnicode);
procedure GetTextLimitsPx(var LeftOffset, RightOffset: integer);
procedure HandleTabsAndPrint(const leftstring: SynUnicode;
var rightstring: SynUnicode; leftOffset, rightOffset: integer);
procedure PreviewPaint(Sender: TObject);
procedure PreviewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PreviewMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PreviewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
function GetLeftMargin: integer;
procedure SetLeftMargin(const Value: integer);
function GetRightMarginPos: integer;
function GetSavedState: TSavedState;
procedure SetSavedState(const SavedState: TSavedState);
/// can be used internaly (for instance by fPagesToFooterState)
property SavedState: TSavedState read GetSavedState write SetSavedState;
protected
fMousePos: TPoint;
{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
fButtonDown, fButtonDownScroll: TPoint;
{$endif}
/// Strings[] are the bookmark names, and Objects[] are TGDIPagereference
// to get the Y position
fBookmarks: TStringList;
/// Strings[] are the bookmark names, and Objects[] are TGDIPagereference to
// get the hot region
fLinks: TStringList;
fLinksCurrent: integer;
/// Strings[] are the outline titles, and Objects[] are TGDIPagereference
// to get the Y position of the destination
fOutline: TStringList;
fInternalUnicodeString: SynUnicode;
fForcedLeftOffset : integer;
PreviewForm: TForm;
PreviewButtons: array of TButton;
PreviewPageCountLabel: TLabel;
procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure CreateWnd; override;
procedure Resize; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
{$IFNDEF VER100}
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean; override; //no mousewheel support in Delphi 3
{$ENDIF}
procedure PopupMenuPopup(Sender: TObject);
procedure CheckHeaderDone; virtual;
// warning: PW buffer is overwritten at the next method call
procedure InternalUnicodeString(const s: SynUnicode;
var PW: PWideChar; var PWLen: integer; size: PSize);
public
/// Event triggered when the ReportPopupMenu is displayed
// - default handling (i.e. leave this field nil) is to add Page naviguation
// - you can override this method for adding items to the ReportPopupMenu
OnPopupMenuPopup: TNotifyEvent;
/// Event triggered when a ReportPopupMenu item is selected
// - default handling (i.e. leave this field nil) is for Page navigation
// - you can override this method for handling additionnal items to the menu
// - the Tag component of the custom TMenuItem should be 0 or greater than
// Report pages count: use 1000 as a start for custom TMenuItem.Tag values
OnPopupMenuClick: TNotifyEvent;
/// user can customize this class to create an advanced popup menu instance
PopupMenuClass: TPopupMenuClass;
/// the title of the report
// - used for the preview caption form
// - used for the printing document name
Caption: string;
/// if true, the PrintPages() method will use a temporary bitmap for printing
// - some printer device drivers have problems with printing metafiles
// which contains other metafiles; should have been fixed
// - not useful, since slows the printing a lot and makes huge memory usage
ForcePrintAsBitmap: boolean;
/// if true the preview will not use GDI+ library to draw anti-aliaised graphics
// - this may be slow on old computers, so caller can disable it on demand
ForceNoAntiAliased: boolean;
/// if true, drawing will NOT to use native GDI+ 1.1 conversion
// - we found out that GDI+ 1.1 was not as good as our internal conversion
// function written in Delphi, e.g. for underlined fonts
// - so this property is set to true by default for proper display on screen
// - will only be used if ForceNoAntiAliased is false, of course
ForceInternalAntiAliased: boolean;
/// if true, internal text drawing will use a font-fallback mechanism
// for characters not existing within the current font (just as with GDI)
// - is disabled by default, but could be set to TRUE to force enabling
// TGDIPlusFull.ForceUseDrawString property
ForceInternalAntiAliasedFontFallBack: boolean;
{$ifdef PRINTERNEW}
// the PrintPages() will use this parameter to force black and white, or
// color mode, whatever the global printer setting is
ForcePrintColorMode: (printColorDefault, printBW, printColor);
// the PrintPages() will use this parameter to force duplex mode,
// whatever the global printer setting is
ForcePrintDuplexMode: (printDuplexDefault, printSimplex, printDuplex);
{$endif}
/// if true, the headers are copied only once to the text
ForceCopyTextAsWholeContent: boolean;
/// customize text conversion before drawing
// - Text content can be modified by this event handler to customize
// some characters (e.g. '>=' can be converted to its Unicode glyph)
OnStringToUnicode: TOnStringToUnicodeEvent;
/// set group page fill method
// - if set to true, the groups will be forced to be placed on the same page
// (this was the original default "Pages" component behavior, but this
// is not usual in page composition, so is disabled by default in TGDIPages)
// - if set to false, the groups will force a page feed if there is not
// enough place for 20 lines on the current page (default behavior)
GroupsMustBeOnSamePage: boolean;
/// the bitmap used to draw the page
PreviewSurfaceBitmap: TBitmap;
/// creates the reporting component
constructor Create(AOwner: TComponent); override;
/// finalize the component, releasing all used memory
destructor Destroy; override;
/// customized invalidate
procedure Invalidate; override;
/// Begin a Report document
// - Every report must start with BeginDoc and end with EndDoc
// - note that Printers.SetPrinter() should be set BEFORE calling BeginDoc,
// otherwise you may have a "canvas does not allow drawing" error
procedure BeginDoc;
/// Clear the current Report document
procedure Clear; virtual;
/// draw some text as a paragraph, with the current alignment
// - this method does all word-wrapping and formating if necessary
// - this method handle multiple paragraphs inside s (separated by newlines -
// i.e. #13)
// - by default, will write a paragraph, unless withNewLine is set to FALSE,
// so that the next DrawText() will continue drawing at the current position
procedure DrawText(const s: string; withNewLine: boolean=true);
{$ifdef HASINLINE}inline;{$endif}
/// draw some UTF-8 text as a paragraph, with the current alignment
// - this method does all word-wrapping and formating if necessary
// - this method handle multiple paragraphs inside s (separated by newlines -
// i.e. #13)
// - by default, will write a paragraph, unless withNewLine is set to FALSE,
// so that the next DrawText() will continue drawing at the current position
procedure DrawTextU(const s: RawUTF8; withNewLine: boolean=true);
{$ifdef HASINLINE}inline;{$endif}
/// draw some Unicode text as a paragraph, with the current alignment
// - this method does all word-wrapping and formating if necessary
// - this method handle multiple paragraphs inside s (separated by newlines -
// i.e. #13)
// - by default, will write a paragraph, unless withNewLine is set to FALSE,
// so that the next DrawText() will continue drawing at the current position
procedure DrawTextW(const s: SynUnicode; withNewLine: boolean=true);
/// draw some text as a paragraph, with the current alignment
// - this method use format() like parameterss
procedure DrawTextFmt(const s: string; const Args: array of const;
withNewLine: boolean=true);
/// get the formating flags associated to a Title
function TitleFlags: integer;
/// draw some text as a paragraph title
// - the outline level can be specified, if UseOutline property is enabled
// - if aBookmark is set, a bookmark is created at this position
// - if aLink is set, a link to the specified bookmark name (in aLink) is made
procedure DrawTitle(const s: SynUnicode; DrawBottomLine: boolean=false; OutlineLevel: Integer=0;
const aBookmark: string=''; const aLink: string=''; aLinkNoBorder: boolean=false);
/// draw one line of text, with the current alignment
procedure DrawTextAt(s: SynUnicode; XPos: integer; const aLink: string='';
CheckPageNumber: boolean=false; aLinkNoBorder: boolean=false);
/// draw one line of text, with a specified Angle and X Position
procedure DrawAngledTextAt(const s: SynUnicode; XPos, Angle: integer);
/// draw a square box at the given coordinates
procedure DrawBox(left,top,right,bottom: integer);
/// draw a filled square box at the given coordinates
procedure DrawBoxFilled(left,top,right,bottom: integer; Color: TColor);
/// Stretch draws a bitmap image at the specified page coordinates in mm's
procedure DrawBMP(rec: TRect; bmp: TBitmap); overload;
/// add the bitmap at the specified X position
// - if there is not enough place to draw the bitmap, go to next page
// - then the current Y position is updated
// - bLeft (in mm) is calculated in reference to the LeftMargin position
// - if bLeft is maxInt, the bitmap is centered to the page width
// - bitmap is stretched (keeping aspect ratio) for the resulting width to
// match the bWidth parameter (in mm)
procedure DrawBMP(bmp: TBitmap; bLeft, bWidth: integer; const Legend: string=''); overload;
/// Stretch draws a metafile image at the specified page coordinates in mm's
procedure DrawMeta(rec: TRect; meta: TMetafile);
/// add the graphic (bitmap or metafile) at the specified X position
// - handle only TBitmap and TMetafile kind of TGraphic
// - if there is not enough place to draw the bitmap, go to next page
// - then the current Y position is updated
// - bLeft (in mm) is calculated in reference to the LeftMargin position
// - if bLeft is maxInt, the bitmap is centered to the page width
// - bitmap is stretched (keeping aspect ratio) for the resulting width to
// match the bWidth parameter (in mm)
procedure DrawGraphic(graph: TGraphic; bLeft, bWidth: integer; const Legend: SynUnicode='');
/// draw an Arrow
procedure DrawArrow(Point1, Point2: TPoint; HeadSize: integer; SolidHead: boolean);
/// draw a Line, either simple or double, between the left & right margins
procedure DrawLine(doubleline: boolean=false);
/// draw a Dashed Line between the left & right margins
procedure DrawDashedLine;
/// draw a Line, following a column layout
procedure DrawColumnLine(ColIndex: integer; aAtTop: boolean;
aDoDoubleLine: boolean);
/// append a Rich Edit content to the current report
// - note that if you want the TRichEdit component to handle more than 64 KB
// of RTF content, you have to set its MaxLength property as expected (this
// is a limitation of the VCL, not of this method)
// - you can specify optionally a pointer to a TIntegerDynArray variable,
// which will be filled with the position of each page last char: it may
// be handy e.g. to add some cross-reference table about the rendered content
procedure AppendRichEdit(RichEditHandle: HWnd; EndOfPagePositions: PIntegerDynArray=nil);
/// jump some line space between paragraphs
// - Increments the current Y Position the equivalent of a single line
// relative to the current font height and line spacing
procedure NewLine;
/// jump some half line space between paragraphs
// - Increments the current Y Position the equivalent of an half single line
// relative to the current font height and line spacing
procedure NewHalfLine;
/// jump some line space between paragraphs
// - Increments the current Y Position the equivalent of 'count' lines
// relative to the current font height and line spacing
procedure NewLines(count: integer);
/// save the current font and alignment
procedure SaveLayout; virtual;
/// restore last saved font and alignment
procedure RestoreSavedLayout; virtual;
/// jump to next page, i.e. force a page break
procedure NewPage(ForceEndGroup: boolean=false);
/// jump to next page, but only if some content is pending
procedure NewPageIfAnyContent;
/// change the page layout for the upcoming page
// - will then force a page break by a call to NewPage(true) method
// - can change the default margin if margin*>=0
// - can change the default non-printable printer margin if nonPrintable*>=0
procedure NewPageLayout(sizeWidthMM, sizeHeightMM: integer;
nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1); overload;
/// change the page layout for the upcoming page
// - will then force a page break by a call to NewPage(true) method
// - can change the default margin if margin*>=0
// - can change the default non-printable printer margin if nonPrintable*>=0
procedure NewPageLayout(paperSize: TGdiPagePaperSize;
orientation: TPrinterOrientation=poPortrait;
nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1); overload;
/// begin a Group: stops the contents from being split across pages
// - BeginGroup-EndGroup text blocks can't be nested
procedure BeginGroup;
/// end a previously defined Group
// - BeginGroup-EndGroup text blocks can't be nested
procedure EndGroup;
/// End the Report document
// - Every report must start with BeginDoc and end with EndDoc
procedure EndDoc;
/// Print the selected pages to the default printer of Printer unit
// - if PrintFrom=0 and PrintTo=0, then all pages are printed
// - if PrintFrom=-1 or PrintTo=-1, then a printer dialog is displayed
function PrintPages(PrintFrom, PrintTo: integer): boolean;
/// export the current report as PDF file
{$ifdef USEPDFPRINTER}
// - uses an external 'PDF' printer
{$else}
// - uses internal PDF code, from Synopse PDF engine (handle bookmarks,
// outline and twin bitmaps) - in this case, a file name can be set
{$endif}
function ExportPDF(aPdfFileName: TFileName; ShowErrorOnScreen: boolean;
LaunchAfter: boolean=true): boolean;
{$ifndef USEPDFPRINTER}
/// export the current report as PDF in a specified stream
// - uses internal PDF code, from Synopse PDF engine (handle bookmarks,
// outline and twin bitmaps) - in this case, a file name can be set
function ExportPDFStream(aDest: TStream): boolean;
{$endif}
/// show a form with the preview, allowing the user to browse pages and
// print the report
// - you can customize the buttons and popup menu actions displayed on
// the screen - by default, all buttons are visible
procedure ShowPreviewForm(VisibleButtons: TGdiPagePreviewButtons =
[rNextPage..High(TGdiPagePreviewButton)]);
/// set the Tabs stops on every line
// - if one value is provided, it will set the Tabs as every multiple of it
// - if more than one value are provided, they will be the exact Tabs positions
procedure SetTabStops(const tabs: array of integer);
/// returns true if there is enough space in the current Report for Count lines
// - Used to check if there's sufficient vertical space remaining on the page
// for the specified number of lines based on the current Y position
function HasSpaceForLines(Count: integer): boolean;
/// returns true if there is enough space in the current Report for a
// vertical size, specified in mm
function HasSpaceFor(mm: integer): boolean;
/// Clear all already predefined Headers
procedure ClearHeaders;
/// Adds either a single line or a double line (drawn between the left &
// right page margins) to the page header
procedure AddLineToHeader(doubleline: boolean);
/// Adds text using to current font and alignment to the page header
procedure AddTextToHeader(const s: SynUnicode);
/// Adds text to the page header at the specified horizontal position and
// using to current font.
// - No Line feed will be triggered: this method doesn't increment the YPos,
// so can be used to add multiple text on the same line
// - if XPos=-1, will put the text at the current right margin
procedure AddTextToHeaderAt(const s: SynUnicode; XPos: integer);
/// Clear all already predefined Footers
procedure ClearFooters;
/// Adds either a single line or a double line (drawn between the left &
// right page margins) to the page footer
procedure AddLineToFooter(doubleline: boolean);
/// Adds text using to current font and alignment to the page footer
procedure AddTextToFooter(const s: SynUnicode);
/// Adds text to the page footer at the specified horizontal position and
// using to current font. No Line feed will be triggered.
// - if XPos=-1, will put the text at the current right margin
procedure AddTextToFooterAt(const s: SynUnicode; XPos: integer);
/// Will add the current 'Page n/n' text at the specified position
// - PageText must be of format 'Page %d/%d', in the desired language
// - if XPos=-1, will put the text at the current right margin
// - if the vertical position does not fit your need, you could set
// YPosMultiplier to a value which will be multipled by fFooterHeight to
// compute the YPos
procedure AddPagesToFooterAt(const PageText: string; XPos: integer;
YPosMultiplier: integer=1);
/// register a column, with proper alignment
procedure AddColumn(left, right: integer; align: TColAlign; bold: boolean);
/// register same alignement columns, with percentage of page column width
// - sum of all percent width should be 100, but can be of any value
// - negative widths are converted into absolute values, but
// corresponding alignment is set to right
// - if a column need to be right aligned or currency aligned,
// use SetColumnAlign() method below
// - individual column may be printed in bold with SetColumnBold() method
procedure AddColumns(const PercentWidth: array of integer; align: TColAlign=caLeft);
/// register some column headers, with the current font formating
// - Column headers will appear just above the first text output in
// columns on each page
// - you can call this method several times in order to have diverse
// font formats across the column headers
procedure AddColumnHeaders(const headers: array of SynUnicode;
WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
RowLineHeight: integer=0; flags: integer=0);
/// register some column headers, with the current font formating
// - Column headers will appear just above the first text output in
// columns on each page
// - call this method once with all columns text as CSV
procedure AddColumnHeadersFromCSV(var CSV: PWideChar;
WithBottomGrayLine: boolean; BoldFont: boolean=false; RowLineHeight: integer=0);
/// draw some text, split across every columns
// - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
// row is printed on white with this background color (e.g. to highlight errors)
procedure DrawTextAcrossCols(const StringArray: array of SynUnicode;
BackgroundColor: TColor=clNone); overload;
/// draw some text, split across every columns
// - you can specify an optional bookmark name to be used to link a column
// content via a AddLink() call
// - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
// row is printed on white with this background color (e.g. to highlight errors)
procedure DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
BackgroundColor: TColor=clNone); overload;
/// draw some text, split across every columns
// - this method expect the text to be separated by commas
// - if BackgroundColor is not clNone (i.e. clRed or clNavy or clBlack), the
// row is printed on white with this background color (e.g. to highlight errors)
procedure DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
/// draw (double if specified) lines at the bottom of all currency columns
procedure DrawLinesInCurrencyCols(doublelines: boolean);
/// retrieve the current Column count
property ColumnCount: integer read GetColumnCount;
/// retrieve the attributes of a specified column
function GetColumnInfo(index: integer): TColRec;
/// individually set column alignment
// - useful after habing used AddColumns([]) method e.g.
procedure SetColumnAlign(index: integer; align: TColAlign);
/// individually set column bold state
// - useful after habing used AddColumns([]) method e.g.
procedure SetColumnBold(index: integer);
/// erase all columns and the associated headers
procedure ClearColumns;
/// clear the Headers associated to the Columns
procedure ClearColumnHeaders;
/// ColumnHeadersNeeded will force column headers to be drawn again just
// prior to printing the next row of columned text
// - Usually column headers are drawn once per page just above the first
// column. ColumnHeadersNeeded is useful where columns of text have been
// separated by a number of lines of non-columned text
procedure ColumnHeadersNeeded;
/// create a bookmark entry at the current position of the current page
// - return false if this bookmark name was already existing, true on success
// - if aYPosition is not 0, the current Y position will be used
function AddBookMark(const aBookmarkName: string; aYPosition: integer=0): Boolean; virtual;
/// go to the specified bookmark
// - returns true if the bookmark name was existing and reached
function GotoBookmark(const aBookmarkName: string): Boolean; virtual;
/// create an outline entry at the current position of the current page
// - if aYPosition is not 0, the current Y position will be used
procedure AddOutline(const aTitle: string; aLevel: Integer;
aYPosition: integer=0; aPageNumber: integer=0); virtual;
/// create a link entry at the specified coordinates of the current page
// - coordinates are specified in mm
// - the bookmark name is not checked by this method: a bookmark can be
// linked before being marked in the document
procedure AddLink(const aBookmarkName: string; aRect: TRect;
aPageNumber: integer=0; aNoBorder: boolean=false); virtual;
/// convert a rect of mm into pixel canvas units
function MmToPrinter(const R: TRect): TRect;
/// convert a rect of pixel canvas units into mm
function PrinterToMM(const R: TRect): TRect;
/// convert a mm X position into pixel canvas units
function MmToPrinterPxX(mm: integer): integer;
/// convert a mm Y position into pixel canvas units
function MmToPrinterPxY(mm: integer): integer;
/// convert a pixel canvas X position into mm
function PrinterPxToMmX(px: integer): integer;
/// convert a pixel canvas Y position into mm
function PrinterPxToMmY(px: integer): integer;
/// return the width of the specified text, in mm
function TextWidth(const Text: SynUnicode): integer;
/// the current Text Alignment, during text adding
property TextAlign: TTextAlign read fAlign write SetTextAlign;
/// specifies the reading order (bidirectional mode) of the box
// - only bdLeftToRight and bdRightToLeft are handled
// - this will be used by DrawText[At], DrawTitle, AddTextToHeader/Footer[At],
// DrawTextAcrossCols, SaveLayout/RestoreSavedLayout methods
property BiDiMode: TBiDiMode read fBiDiMode write fBiDiMode;
/// create a meta file and its associated canvas for displaying a picture
// - you must release manually both Objects after usage
function CreatePictureMetaFile(Width, Height: integer;
out MetaCanvas: TCanvas): TMetaFile;
/// Distance (in mm's) from the top of the page to the top of the current group
// - returns CurrentYPos if no group is in use
function CurrentGroupPosStart: integer;
/// go to the specified Y position on a given page
// - used e.g. by GotoBookmark() method
procedure GotoPosition(aPage: integer; aYPos: integer);
/// access to all pages content
// - numerotation begin with Pages[0] for page 1
// - the Pages[] property should be rarely needed
property Pages: TGDIPageContentDynArray read fPages;
/// add an item to the popup menu
// - used mostly internaly to add page browsing
// - default OnClick event is to go to page set by the Tag property
function NewPopupMenuItem(const aCaption: string; Tag: integer=0;
SubMenu: TMenuItem=nil; OnClick: TNotifyEvent=nil; ImageIndex: integer=-1): TMenuItem;
/// this is the main popup menu item click event
procedure PopupMenuItemClick(Sender: TObject);
/// can be used to draw directly using GDI commands
// - The Canvas property should be rarely needed
property Canvas: TMetaFileCanvas read fCanvas;
/// Distance (in mm's) from the top of the page to the top of the next line
property CurrentYPos: integer read GetYPos write SetYPos;
/// get current line height (mm)
property LineHeight: integer read GetLineHeightMm;
/// the name of the current selected printer
// - note that Printers.SetPrinter() should be set BEFORE calling BeginDoc,
// otherwise you may have a "canvas does not allow drawing" error
property PrinterName: string read fCurrentPrinter;
/// the index of the previewed page
// - please note that the first page is 1 (not 0)
property Page: integer read fCurrPreviewPage write SetPage;
/// total number of pages
property PageCount: integer read GetPageCount;
/// Size of each margin relative to its corresponding edge in mm's
property PageMargins: TRect read GetPageMargins write SetPageMargins;
/// Size of the left margin relative to its corresponding edge in mm's
property LeftMargin: integer read GetLeftMargin write SetLeftMargin;
/// Position of the right margin, in mm
property RightMarginPos: integer read GetRightMarginPos;
/// get the current selected paper size, in mm's
property PaperSize: TSize read GetPaperSize;
/// number of pixel per inch, for X and Y directions
property PrinterPxPerInch: TPoint read fPrinterPxPerInch;
{$ifdef USEPDFPRINTER}
/// true if any printer appears to be a PDF printer
property HasPDFPrinterInstalled: boolean read fHasPDFPrinterInstalled;
{$else}
/// this property can force saving all bitmaps as JPEG in exported PDF
// - by default, this property is set to 0 by the constructor of this class,
// meaning that the JPEG compression is not forced, and the engine will use
// the native resolution of the bitmap - in this case, the resulting
// PDF file content will be bigger in size (e.g. use this for printing)
// - 60 is the prefered way e.g. for publishing PDF over the internet
// - 80/90 is a good ration if you want to have a nice PDF to see on screen
// - of course, this doesn't affect vectorial (i.e. emf) pictures
property ExportPDFForceJPEGCompression: integer read fForceJPEGCompression write fForceJPEGCompression;
/// optional application name used during Export to PDF
// - if not set, global Application.Title will be used
property ExportPDFApplication: string read fExportPDFApplication write fExportPDFApplication;
/// optional Author name used during Export to PDF
property ExportPDFAuthor: string read fExportPDFAuthor write fExportPDFAuthor;
/// optional Subject text used during Export to PDF
property ExportPDFSubject: string read fExportPDFSubject write fExportPDFSubject;
/// optional Keywords name used during Export to PDF
property ExportPDFKeywords: string read fExportPDFKeywords write fExportPDFKeywords;
/// if set to TRUE, the used True Type fonts will be embedded to the exported PDF
// - not set by default, to save disk space and produce tiny PDF
property ExportPDFEmbeddedTTF: boolean read fExportPDFEmbeddedTTF write fExportPDFEmbeddedTTF;
/// if set to TRUE, the exported PDF is made compatible with PDF/A-1 requirements
property ExportPDFA1: Boolean read fExportPDFA1 write fExportPDFA1;
/// an optional background image, to be exported on every pdf page
// - note that no private copy of the TGraphic instance is made: the caller
// has to manage it, and free it after the pdf is generated
property ExportPDFBackground: TGraphic read fExportPDFBackground write fExportPDFBackground;
{$ifndef NO_USE_UNISCRIBE}
/// set if the exporting PDF engine must use the Windows Uniscribe API to
// render Ordering and/or Shaping of the text
// - useful for Hebrew, Arabic and some Asiatic languages handling
// - set to FALSE by default, for faster content generation
property ExportPDFUseUniscribe: boolean read fExportPDFUseUniscribe write fExportPDFUseUniscribe;
{$endif}
/// used to define if the exported PDF document will handle "font fallback" for
// characters not existing in the current font: it will avoid rendering
// block/square symbols instead of the correct characters (e.g. for Chinese text)
// - will use the font specified by FontFallBackName property to add any
// Unicode glyph not existing in the currently selected font
// - default value is TRUE
property ExportPDFUseFontFallBack: boolean read fExportPDFUseFontFallBack
write fExportPDFUseFontFallBack;
/// set the font name to be used for missing characters in exported PDF document
// - used only if UseFontFallBack is TRUE
// - default value is 'Arial Unicode MS', if existing
property ExportPDFFontFallBackName: string read fExportPDFFontFallBackName
write fExportPDFFontFallBackName;
/// set encryption level to be used in exporting PDF document
property ExportPDFEncryptionLevel: TPdfEncryptionLevel
read fExportPDFEncryptionLevel write fExportPDFEncryptionLevel;
/// set encryption user password to be used in exporting PDF document
// - leave it to '' unless you want the user to be asked for this password
// at document opening
// - ExportPDFEncryptionLevel = elRC4_40/elRC4_128 expects only ASCII-7 chars
property ExportPDFEncryptionUserPassword: string
read fExportPDFEncryptionUserPassword write fExportPDFEncryptionUserPassword;
/// set encryption owner password to be used in exporting PDF document
// - it is mandatory to set it to a non void value - by default, is set to
// 'SynopsePDFEngine' by should be overridden for security
// - ExportPDFEncryptionLevel = elRC4_40/elRC4_128 expects only ASCII-7 chars
property ExportPDFEncryptionOwnerPassword: string
read fExportPDFEncryptionOwnerPassword write fExportPDFEncryptionOwnerPassword;
/// set encryption Permissions to be used in exporting PDF document
// - can be either one of the PDF_PERMISSION_ALL / PDF_PERMISSION_NOMODIF /
// PDF_PERSMISSION_NOPRINT / PDF_PERMISSION_NOCOPY /
// PDF_PERMISSION_NOCOPYNORPRINT set of options
// - default value is PDF_PERMISSION_ALL (i.e. no restriction)
property ExportPDFEncryptionPermissions: TPdfEncryptionPermissions
read fExportPDFEncryptionPermissions write fExportPDFEncryptionPermissions;
/// set to TRUE to export in PDF 1.5 format, which may produce smaller files
property ExportPDFGeneratePDF15File: Boolean
read fExportPDFGeneratePDF15File write fExportPDFGeneratePDF15File;
{$endif}
/// the current page number, during text adding
// - Page is used during preview, after text adding
property VirtualPageNum: integer read fVirtualPageNum write fVirtualPageNum;
/// true if any header as been drawn, that is if something is to be printed
property HeaderDone: boolean read fHeaderDone;
{ /// used to set if columns must be delimited at their bottom with a gray line
property ColumnsWithBottomGrayLine: boolean read fColumnsWithBottomGrayLine
write fColumnsWithBottomGrayLine; }
published
/// accounting standard layout for caCurrency columns:
// - convert all negative sign into parentheses
// - using parentheses instead of negative numbers is used in financial
// statement reporting (see e.g. http://en.wikipedia.org/wiki/Income_statement)
// - align numbers on digits, not parentheses
property NegsToParenthesesInCurrCols: boolean
read fNegsToParenthesesInCurrCols write fNegsToParenthesesInCurrCols;
/// word wrap (caLeft) left-aligned columns into multiple lines
// - if the text is wider than the column width, its content
// is wrapped to the next line
// - if the text contains some #13/#10 characters, it will be splitted into
// individual lines
// - this is disabled by default
property WordWrapLeftCols: boolean read fWordWrapLeftCols write fWordWrapLeftCols;
/// if set, any DrawTitle() call will create an Outline entry
// - used e.g. for PDF generation
// - this is enabled by default
property UseOutlines: boolean read fUseOutlines write fUseOutlines;
/// left justification hang indentation
property HangIndent: integer read fHangIndent write fHangIndent;
/// Line spacing: can be lsSingle, lsOneAndHalf or lsDouble
property LineSpacing: TLineSpacing read fLineSpacing write fLineSpacing;
/// the paper orientation
property Orientation: TPrinterOrientation read GetOrientation write SetOrientation;
/// the current Zoom value, according to the zoom status
// - you can use PAGE_WIDTH and PAGE_FIT constants to force the corresponding
// zooming mode (similar to ZoomStatus property setter)
// - set this property will work only when the report is already shown
// in preview mode, not before ShowPreviewForm method call
property Zoom: integer read fZoom write SetZoom;
/// the current Zoom procedure, i.e. zsPercent, zsPageFit or zsPageWidth
// - set this property will define the Zoom at PAGE_WIDTH or PAGE_FIT
// special constant, if needed
// - set this property will work only when the report is already shown
// in preview mode, not before ShowPreviewForm method call
property ZoomStatus: TZoomStatus read fZoomStatus write SetZoomStatus;
/// if set to true, we reduce the precision for better screen display
property ForceScreenResolution: boolean
read fForceScreenResolution write fForceScreenResolution;
/// Event triggered when each new page is created
property OnNewPage: TNewPageEvent
read fStartNewPage write fStartNewPage;
/// Event triggered when each new header is about to be drawn
property OnStartPageHeader: TNotifyEvent
read fStartPageHeader write fStartPageHeader;
/// Event triggered when each header was drawn
property OnEndPageHeader: TNotifyEvent
read fEndPageHeader write fEndPageHeader;
/// Event triggered when each new footer is about to be drawn
property OnStartPageFooter: TNotifyEvent
read fStartPageFooter write fStartPageFooter;
/// Event triggered when each footer was drawn
property OnEndPageFooter: TNotifyEvent
read fEndPageFooter write fEndPageFooter;
/// Event triggered when each new column is about to be drawn
property OnStartColumnHeader: TNotifyEvent
read fStartColumnHeader write fStartColumnHeader;
/// Event triggered when each column was drawn
property OnEndColumnHeader: TNotifyEvent
read fEndColumnHeader write fEndColumnHeader;
/// Event triggered whenever the report document generation is done
// - i.e. when the EndDoc method has just been called
property OnDocumentProduced: TNotifyEvent
read fOnDocumentProducedEvent write fOnDocumentProducedEvent;
/// Event triggered whenever the current preview page is changed
property OnPreviewPageChanged: TNotifyEvent
read fPreviewPageChangedEvent write fPreviewPageChangedEvent;
/// Event triggered whenever the preview page is zoomed in or out
property OnZoomChanged: TZoomChangedEvent
read fZoomChangedEvent write fZoomChangedEvent;
end;
{$ifdef RENDERPAGES}
TRenderPages = class;
/// a TRenderPages additional layout state
// - used by the overridden SaveLayout/RestoreSavedLayout methods
TSavedStateRender = record
FirstLineIndent: Integer;
Before: Integer;
After: Integer;
RightIndent: Integer;
LeftIndent: Integer;
end;
PRenderBoxWord = ^TRenderBoxWord;
/// the internal "Word" box structure used by TRenderBox
TRenderBoxWord = packed record
/// offset in the fText[] array
TextOffset: integer;
/// PWideChar count starting from fText[TextOffset]
TextLength: integer;
/// size on the canvas
Size: TSize;
/// used to retrieve associated font attributes
FontIndex: integer;
/// space width from current font attribute
FontSpaceWidth: integer;
/// number of spaces at the right side of this "Word" box
SpaceAfterCount: integer;
/// associated link bookmark name
// - from fLinksBookMarkName[LinkNumber-1], no link set for 0
LinkNumber: integer;
end;
PRenderBoxLayout = ^TRenderBoxLayout;
/// the internal "drawing" box structure used by TRenderBox
// - TRenderBox.InternalRender populate fLayout[] with this structures,
// ready to be drawn to the document Canvas
TRenderBoxLayout = packed record
/// pointer of the words in the fText[] array
Text: PWideChar;
/// number of PWideChar starting at Text^
Length: integer;
/// layout box X coordinate
Left: integer;
/// layout box Y coordinate
Top: integer;
/// layout box width (in pixels)
Width: integer;
/// layout box height (in pixels) - that is, the line height
Height: integer;
/// corresponding rendered line index (starting at 0)
LineIndex: integer;
/// used to retrieve associated font attributes and links e.g.
LastBox: PRenderBoxWord;
/// length of extra space, in pixels - as used by SetTextJustification()
BreakExtra: integer;
/// count of space characters in line of text - as used by SetTextJustification()
BreakCount: integer;
end;
/// used to render a "box" of text
// - will handle word adding, and formatting for a given width
// - is used by TRenderPage for a whole paragraph, or a column inside a table
TRenderBox = class
protected
fBiDiMode: TBiDiMode;
fWidth: integer;
fHeight: integer;
/// an internal buffer containing the Unicode text of this box
fText: array of WideChar;
fTextLen: integer;
/// word markers of the current text
fBox: array of TRenderBoxWord;
fBoxCount: integer;
/// InternalRender will fill this ready to be rendered layout array
fLayout: array of TRenderBoxLayout;
fLayoutCount: integer;
fOwner: TRenderPages;
fOwnerFont: TFont;
/// associated links: none set for 0, otherwise fLinksBookMarkName[number-1]
fLinksBookMarkNameCurrent: integer;
fLinksBookMarkName: array of string;
/// populate fLayout[] from fBox[] and calculate fHeight
procedure InternalRender;
function GetHeight: integer;
procedure Clear;
public
/// initialize the rendering "box"
constructor Create(Owner: TRenderPages);
/// add some text at the current position
// - the text is converted to Unicode before adding (calling
// Owner.OnStringToUnicode if was defined)
// - the current Owner Font settings are used for the rendering
// - warning: this method won't handle control chars (like #13 or #10), but
// will replace them with a space: it's about the caller to
procedure AddText(const s: string); overload;
/// add some text at the current position
// - the current Owner Font settings are used for the rendering
// - warning: this method won't handle control chars (like #13 or #10), but
// will replace them with a space: it's about the caller to
procedure AddText(PW: PWideChar; PWLen: integer); overload;
/// format the already inserted text into the TRenderPages owner
// - this TRenderBox text content will be cleared at the end of this method
// - you don't have to call it usualy: use Owner.RdrParagraph instead
// - by default, will render top aligned to the X=Left/Y=Top pixels position
// - for vertical alignment, specify an height in ForcedHeightBottomCentered
// then will be centered if ForcedAtBottom=false, or bottom aligned if true
// - if CurrentPageOnly is true, will only flush the content which will fit on
// the current page - the fLayout[] array will contain remaining boxes;
// - if CurrentPageOnly is false, this will flush all content to multiple pages
procedure Flush(Left, Top: Integer; CurrentPageOnly: boolean;
ForcedHeightBottomCentered: Integer; ForcedAtBottom: boolean);
/// render the text paragraph, but go to the next line
// - similar to the <br /> HTML tag or the \line RTF command
procedure NewLine;
/// mark that an hyperlink must begin at the current position
// - use e.g. RdrAddText method to add some text for the link
// - will cancel any previous LinkBegin with no LinkEnd: i.e. no nested
// links are handled yet (how would want it anyway, in the HTML world?)
procedure LinkBegin(const aBookmarkName: string);
/// mark that an hyperlink must begin at the current position
// - use e.g. RdrAddText method to add some text for the link
// - return false on error (e.g. no hyperlink previously opened via LinkBegin)
function LinkEnd: boolean;
/// reset font (character) formatting properties to a default value
// - default value have been set by RdrSetCurrentStateAsDefault
// - if no previous call to RdrSetCurrentStateAsDefault has been made,
// the font is reset to a 12 point, with no bold/italic/underline attributes
// - similar to the \plain RTF command
procedure Plain; {$ifdef HASINLINE}inline;{$endif}
/// reset paragraph formatting properties to a default value
// - similar to the \pard RTF command
procedure Pard; {$ifdef HASINLINE}inline;{$endif}
/// reset both paragraph and font formatting properties to a default value
// - similar to the \pard\plain RTF command
procedure PardPlain; {$ifdef HASINLINE}inline;{$endif}
/// shortcut to the owner TRenderPages
property Owner: TRenderPages read fOwner;
/// shortcut to the owner TRenderPages.Font
property Font: TFont read fOwnerFont;
/// specifies the reading order (bidirectional mode) of the box
// - only bdLeftToRight and bdRightToLeft are handled
property BiDiMode: TBiDiMode read FBiDiMode write FBiDiMode;
/// current width of the "box", in pixels
// - must be set before any call to InternalRender
property Width: integer read fWidth write fWidth;
/// current resulting height of the "box", in pixels
// - will be calculated from current text if necessary
property Height: integer read GetHeight;
end;
/// Report class specified in high-quality document rendering
// - this class add some methods for creating a document at the character
// level (whereas standard TGDIPages allows reporting at paragraph level)
// - can be used e.g. to render some RTF-like content
// - column handling is much more sophisticated than AddColumn*() methods
// - uses the Windows Uniscribe API to handle right-to-left scripting and
// process complex scripts (like Arabic)
// - uses internaly some TeX-like algorithms like widows and orphans, and
// an optional external hyphenation engine (like our hyphen unit)
TRenderPages = class(TGDIPages)
protected
fParagraphFirstLineIndent: Integer;
fParagraphBefore: Integer;
fParagraphAfter: Integer;
fParagraphRightIndent: Integer;
fParagraphLeftIndent: Integer;
fSavedRender: array of TSavedStateRender;
fDefaultState: TSavedState;
fDefaultStateRender: TSavedStateRender;
fRdr: TRenderBox;
fRdrCol: TObjectList;
/// an array of TFont, used as cache
fFontCache: TObjectList;
fFontCacheSpace: array of TSize;
procedure RdrPard;
procedure RdrPardPlain;
procedure RdrPlain;
function GetCurrentFontCacheIndex: integer;
function GetCurrentFontCacheIndexAndSelect: integer;
function GetSavedRender: TSavedStateRender;
procedure SetSavedRender(const State: TSavedStateRender);
/// will close any pending paragraph (\page makes an implicit \par)
procedure NewPageInternal; override;
public
/// will set the current Font and Paragraph properties to be used as default
// - will be used by RdrPlain and RdrPard methods
procedure RdrSetCurrentStateAsDefault;
/// render the text paragraph, and begin a new one
// - write the paragraph text as specified by all previous calls to the
// Rdr TRenderBox methods, and begin a new paragraph, using a cleaned
// TRenderBox instance
// - will use the current TextAlign property value, and the current value
// of all Paragraph* properties of this class
// - similar to the </p> HTML tag or the \par RTF command
procedure RdrParagraph;
/// create a new table at the current position
// - return false on error (e.g. a table was opened but not yet ended)
function RdrTableBegin(const PercentWidth: array of integer): Boolean;
/// get a particular column
// - return the 'box' handling the layout of the column: use its
// AddText/NewLine/Link*/Plain/Pard methods methods to add some formatted text
function RdrTableColumn(aColumnIndex: Integer): TRenderBox; {$ifdef HASINLINE}inline;{$endif}
/// end a previously opened table
// - will draw all columns to the documents
// - return false on error (e.g. a table was not opened)
function RdrTableEnd: Boolean;
/// the main paragraph 'box' of the document
// - its AddText/NewLine/Link*/Plain/Pard methods methods to add some
// formatted text
// - the paragraph will be flushed to the main document with the RdrParagraph
// method will be called
property Rdr: TRenderBox read fRdr;
public { some overridden methods }
/// creates the reporting component
constructor Create(AOwner: TComponent); override;
/// finalize the component, releasing all used memory and associated TRenderBox
destructor Destroy; override;
/// Clear the current Report document
procedure Clear; override;
/// save the current font and alignment
// - similar to a { character in some RTF content
// - this version will save also Paragraph* properties values
procedure SaveLayout; override;
/// restore last saved font and alignment
// - similar to a } character in some RTF content
// - this version will restore also Paragraph* properties values
procedure RestoreSavedLayout; override;
public
/// current paragraph "space before" spacing (in mm, the default is 0)
property ParagraphBefore: Integer read fParagraphBefore write fParagraphBefore;
/// current paragraph "space after" spacing (in mm, the default is 0)
property ParagraphAfter: Integer read fParagraphAfter write fParagraphAfter;
/// current paragraph first-line indent (in mm, the default is 0)
property ParagraphFirstLineIndent: Integer
read fParagraphFirstLineIndent write fParagraphFirstLineIndent;
/// current paragraph left indent (in mm, the default is 0)
property ParagraphLeftIndent: Integer
read fParagraphLeftIndent write fParagraphLeftIndent;
/// current paragraph right indent (in mm, the default is 0)
property ParagraphRightIndent: Integer
read fParagraphRightIndent write fParagraphRightIndent;
end;
{$endif RENDERPAGES}
resourcestring
sPDFFile = 'Acrobat File';
sPageN = 'Page %d / %d';
/// used to create the popup menu of the report
// - should match TGdiPagePreviewButton order
sReportPopupMenu1 = '&Next page,&Previous page,&Go to Page...,&Zoom...,'+
'&Bookmarks,Copy Page as &Text,P&rint,PDF &Export,&Close,Page fit,Page width';
/// used to create the pages browsing menu of the report
sReportPopupMenu2 = 'Pages %d to %d,Page %d';
const
/// minimum gray border with around preview page
GRAY_MARGIN = 10;
/// TGdiPages.Zoom property value for "Page width" layout during preview
PAGE_WIDTH = -1;
/// TGdiPages.Zoom property value for "Page fit" layout during preview
PAGE_FIT = -2;
//TEXT FORMAT FLAGS...
FORMAT_DEFAULT = $0;
//fontsize bits 0-7 .'. max = 255
FORMAT_SIZE_MASK = $FF;
//alignment bits 8-9
FORMAT_ALIGN_MASK = $300;
FORMAT_LEFT = $0;
FORMAT_RIGHT = $100;
FORMAT_CENTER = $200;
FORMAT_JUSTIFIED = $300;
//fontstyle bits 10-12
FORMAT_BOLD = $400;
FORMAT_UNDERLINE = $800;
FORMAT_ITALIC = $1000;
//undefined bit 13
FORMAT_UNDEFINED = $2000;
//line flags bits 14-15
FORMAT_SINGLELINE = $8000;
FORMAT_DOUBLELINE = $4000;
FORMAT_LINES = $C000;
//DrawTextAt XPos 16-30 bits (max value = ~64000)
FORMAT_XPOS_MASK = $FFFF0000;
PAPERSIZE_A4_WIDTH = 210;
PAPERSIZE_A4_HEIGHT = 297;
procedure SetCurrentPrinterAsDefault;
function CurrentPrinterName: string;
function CurrentPrinterPaperSize: string;
procedure UseDefaultPrinter;
procedure Register;
implementation
uses
{$ifdef ISDELPHIXE3}System.UITypes,{$endif}
Types, Clipbrd, Consts;
// Miscellaneous functions ...
function TextExtent(Canvas: TCanvas; const Text: SynUnicode; Len: integer=0): TSize;
begin
Result.cX := 0;
Result.cY := 0;
if Len=0 then
Len := length(Text);
GetTextExtentPoint32W(Canvas.Handle, pointer(Text), Len, Result);
end;
function TextWidthC(Canvas: TCanvas; const Text: SynUnicode): Integer;
begin
Result := TextExtent(Canvas,Text).cX;
end;
procedure TextOut(Canvas: TCanvas; X,Y: integer; Text: PWideChar; Len: integer); overload;
begin
ExtTextOutW(Canvas.Handle,X,Y,Canvas.TextFlags,nil,Text,Len,nil);
end;
procedure TextOut(Canvas: TCanvas; X,Y: integer; const Text: SynUnicode); overload;
begin
ExtTextOutW(Canvas.Handle,X,Y,Canvas.TextFlags,nil,pointer(Text),Length(Text),nil);
end;
procedure Register;
begin
RegisterComponents('Samples', [TGDIPages]);
end;
function ConvertNegsToParentheses(const ValStr: SynUnicode): SynUnicode;
begin
result := ValStr;
if (result = '') or (result[1] <> '-') then
exit;
result[1] := '(';
result := result+')';
end;
function PrinterDriverExists: boolean;
var Flags, Count, NumInfo: dword;
Level: Byte;
begin
// avoid using fPrinter.printers.Count as this will raise an
// exception if no printer driver is installed...
Count := 0;
try
if Win32Platform = VER_PLATFORM_WIN32_NT then begin
Flags := PRINTER_ENUM_CONNECTIONS or PRINTER_ENUM_LOCAL;
Level := 4;
end else begin
Flags := PRINTER_ENUM_LOCAL;
Level := 5;
end;
EnumPrinters(Flags, nil, Level, nil, 0, Count, NumInfo);
except
end;
result := (count > 0);
end;
function RightTrim(const S: SynUnicode): SynUnicode;
var i: integer;
begin
i := Length(s);
while (i > 0) and (ord(S[i])<=32) do dec(i);
SetString(result,PWideChar(pointer(S)),i);
end;
function LowerCaseU(const S: SynUnicode): SynUnicode;
var i: integer;
begin
SetString(result,PWideChar(pointer(S)),length(S));
for i := 0 to length(S)-1 do
if PWordArray(result)[i] in [ord('A')..ord('Z')] then
dec(PWordArray(result)[i],32);
end;
function Max(a,b: integer): integer;
begin
if a > b then
result := a else
result := b;
end;
function Min(a,b: integer): integer;
begin
if a < b then
result := a else
result := b;
end;
procedure UseDefaultPrinter;
begin
Printers.Printer.PrinterIndex := -1;
end;
function GetDefaultPrinterName: string;
var Device : array[byte] of char;
p,p2: PChar;
begin
GetProfileString('windows', 'device', '', Device, 255);
p2 := Device;
while p2^ = ' ' do inc(p2);
p := p2;
while not (ord(p2^) in [0,ord(',')]) do inc(p2);
SetLength(result, p2 - p);
if p2 > p then
move(p^, pointer(result)^, p2 - p);
end;
function GetDriverForPrinter(Device: PChar; Driver: PChar): boolean;
var
PrintHandle: THandle;
DriverInfo2: PDriverInfo2;
cnt: dword;
DriverPath: string;
begin
result := false;
if not OpenPrinter(Device,PrintHandle, nil) then exit;
try
getmem(DriverInfo2,1024);
try
if GetPrinterDriver(PrintHandle, nil, 2, DriverInfo2, 1024, cnt) then
begin
DriverPath :=
changefileext(extractfilename(DriverInfo2.pDriverPath),'');
strpcopy(Driver, DriverPath);
result := true;
end;
finally
freemem(DriverInfo2);
end;
finally
ClosePrinter(PrintHandle);
end;
end;
procedure SetCurrentPrinterAsDefault;
var Device, Driver, Port: array[byte] of char;
DefaultPrinter: string;
hDeviceMode: THandle;
begin
DefaultPrinter := GetDefaultPrinterName;
Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
if DefaultPrinter = Device then
exit;
if Driver[0] = #0 then
if not GetDriverForPrinter(Device, Driver) then
exit; // oops !
DefaultPrinter := FormatString('%,%,%',[Device, Driver, Port]);
WriteProfileString( 'windows', 'device', pointer(DefaultPrinter) );
Device := 'windows';
SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, PtrInt(@Device));
end;
function CurrentPrinterName: string;
var Device, Driver, Port: array[byte] of char;
hDeviceMode: THandle;
begin
Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
result := trim(Device);
end;
function CurrentPrinterPaperSize: string;
var PtrHdl: THandle;
PtrPPI: TPoint;
size: TSize;
begin
try
PtrHdl := Printer.Handle;
PtrPPI.x := GetDeviceCaps(PtrHdl, LOGPIXELSX);
PtrPPI.y := GetDeviceCaps(PtrHdl, LOGPIXELSY);
size.cx := MulDiv(GetDeviceCaps(PtrHdl, PHYSICALWIDTH), 254, PtrPPI.x * 10);
size.cy := MulDiv(GetDeviceCaps(PtrHdl, PHYSICALHEIGHT), 254, PtrPPI.y * 10);
except
end;
with size do
begin
if cx > cy then
begin
//landscape ...
case cy of
148: if cx = 210 then result := 'A5 (210 x 148mm)';
210: if cx = 297 then result := 'A4 (297 x 210mm)';
216: if cx = 279 then result := 'Letter (11 x 8.5")'
else if cx = 356 then result := 'Legal (14 x 8.5")';
297: if cx = 420 then result := 'A3 (420 x 297mm)';
end;
end else
begin
//portrait ...
case cx of
148: if cy = 210 then result := 'A5 (148 x 210mm)';
210: if cy = 297 then result := 'A4 (210 x 297mm)';
216: if cy = 279 then result := 'Letter (8.5 x 11")'
else if cy = 356 then result := 'Legal (8.5 x 14")';
297: if cy = 420 then result := 'A3 (297 x 420mm)';
end;
end;
if result = '' then
result := FormatString('Custom (% x %mm)',[cx, cy]);
end;
end;
// This declaration modifies Delphi's declaration of GetTextExtentExPoint
// so that the variable to receive partial string extents (p6) is ignored ...
function GetTextExtentExPointNoPartialsW(DC: HDC; p2: PWideChar; p3, p4: Integer;
var p5: Integer; p6: pointer; var p7: TSize): BOOL; stdcall;
external gdi32 name 'GetTextExtentExPointW';
// TrimLine: Splits off from LS any characters beyond the allowed width
// breaking at the end of a word if possible. Leftover chars -> RS.
procedure TrimLine(Canvas: TCanvas; var ls: SynUnicode; out rs: SynUnicode;
LineWidthInPxls: integer);
var i,len,NumCharWhichFit: integer;
dummy: TSize;
function Fits: boolean;
begin
result := GetTextExtentExPointNoPartialsW(Canvas.Handle,
pointer(ls),len,LineWidthInPxls,NumCharWhichFit,nil,dummy);
end;
begin
len := length(ls);
if len = 0 then
exit;
// get the number of characters which will fit within LineWidth...
if len>1024 then
len := 1024; // speed up the API call: we expect only one line of text
if not Fits then // fix API error (too big text) by rough binary approximation
repeat
len := len shr 1;
until (len=0) or Fits;
if NumCharWhichFit = length(ls) then
exit; // if everything fits then stop here
// find the end of the last whole word which will fit...
i := NumCharWhichFit;
while (NumCharWhichFit > 0) and (ls[NumCharWhichFit] > ' ') do
dec(NumCharWhichFit);
if (NumCharWhichFit = 0) then NumCharWhichFit := i;
i := NumCharWhichFit+1;
// ignore trailing blanks in LS...
while (ls[NumCharWhichFit] <= ' ') do dec(NumCharWhichFit);
// ignore beginning blanks in RS...
len := length(ls); // may have been reduced if len>1024 or on API error
while (i < len) and (ls[i] <= ' ') do inc(i);
rs := copy(ls,i,len);
ls := copy(ls,1,NumCharWhichFit); //nb: assign ls AFTER rs here
end;
procedure PrintBitmap(Canvas: TCanvas; DestRect: TRect; Bitmap: TBitmap);
var BitmapHeader: pBitmapInfo;
BitmapImage : POINTER;
HeaderSize : dword;
ImageSize : dword;
begin
// we expect the bitmap to be stored as DIB in the TMetaFile content
GetDIBSizes(Bitmap.Handle,HeaderSize,ImageSize);
GetMem(BitmapHeader,HeaderSize);
GetMem(BitmapImage,ImageSize);
try
GetDIB(Bitmap.Handle, Bitmap.Palette, BitmapHeader^, BitmapImage^);
// will create a EMR_STRETCHDIBITS record, ready for SynPdf and SynGdiPlus
StretchDIBits(Canvas.Handle,
DestRect.Left, DestRect.Top, // Destination Origin
DestRect.Right - DestRect.Left, // Destination Width
DestRect.Bottom - DestRect.Top, // Destination Height
0,0, // Source Origin
Bitmap.Width, Bitmap.Height, // Source Width & Height
BitmapImage,
TBitmapInfo(BitmapHeader^),
DIB_RGB_COLORS,
SRCCOPY);
finally
FreeMem(BitmapHeader);
FreeMem(BitmapImage)
end;
end;
// This DrawArrow() function is based on code downloaded from
// http://www.efg2.com/Lab/Library/Delphi/Graphics/Algorithms.htm
// (The original author is unknown)
procedure DrawArrowInternal(Canvas: TCanvas;
FromPoint, ToPoint: TPoint; HeadSize: integer; SolidArrowHead: boolean);
var
xbase : integer;
xLineDelta : integer;
xLineUnitDelta : Double;
xNormalDelta : integer;
xNormalUnitDelta: Double;
ybase : integer;
yLineDelta : integer;
yLineUnitDelta : Double;
yNormalDelta : integer;
yNormalUnitDelta: Double;
SavedBrushColor : TColor;
begin
with FromPoint do Canvas.MoveTo(x,y);
with ToPoint do Canvas.LineTo(x,y);
xLineDelta := ToPoint.X - FromPoint.X;
yLineDelta := ToPoint.Y - FromPoint.Y;
xLineUnitDelta := xLineDelta / SQRT( SQR(xLineDelta) + SQR(yLineDelta) );
yLineUnitDelta := yLineDelta / SQRT( SQR(xLineDelta) + SQR(yLineDelta) );
// (xBase,yBase) is where arrow line is perpendicular to base of triangle
xBase := ToPoint.X - ROUND(HeadSize * xLineUnitDelta);
yBase := ToPoint.Y - ROUND(HeadSize * yLineUnitDelta);
xNormalDelta := yLineDelta;
yNormalDelta := -xLineDelta;
xNormalUnitDelta := xNormalDelta / SQRT( SQR(xNormalDelta) + SQR(yNormalDelta) );
yNormalUnitDelta := yNormalDelta / SQRT( SQR(xNormalDelta) + SQR(yNormalDelta) );
SavedBrushColor := Canvas.Brush.Color;
if SolidArrowHead then
Canvas.Brush.Color := Canvas.Pen.Color;
Canvas.Polygon([ToPoint,
Point(xBase + ROUND(HeadSize*xNormalUnitDelta),
yBase + ROUND(HeadSize*yNormalUnitDelta)),
Point(xBase - ROUND(HeadSize*xNormalUnitDelta),
yBase - ROUND(HeadSize*yNormalUnitDelta)) ]);
Canvas.Brush.Color := SavedBrushColor;
end;
{ TPagePaintBox }
procedure TPagePaintBox.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
Message.Result := 1; // no erasing is necessary after this method call
end;
{ TGDIPages }
procedure TGDIPages.SetAnyCustomPagePx;
begin
if Int64(fCustomPageSize)<>-1 then
fPhysicalSizePx := fCustomPageSize;
if Int64(fCustomNonPrintableOffset)<>-1 then
fPhysicalOffsetPx := fCustomNonPrintableOffset;
if Int64(fCustomPageMargins.TopLeft)<>-1 then
fPageMarginsPx := fCustomPageMargins;
if Int64(fCustomPxPerInch)<>-1 then
fPrinterPxPerInch := fCustomPxPerInch;
fDefaultLineWidth := (fPrinterPxPerInch.y*25) div 2540;
end;
procedure TGDIPages.GetPrinterParams;
var i: integer;
begin
if Self=nil then exit;
if not fForceScreenResolution and fHasPrinterInstalled then
try
fCurrentPrinter := CurrentPrinterName;
if (Printer.orientation <> fOrientation) then
Printer.orientation := fOrientation;
fPtrHdl := Printer.Handle;
fPrinterPxPerInch.x := GetDeviceCaps(fPtrHdl, LOGPIXELSX);
fPrinterPxPerInch.y := GetDeviceCaps(fPtrHdl, LOGPIXELSY);
fPhysicalSizePx.x := GetDeviceCaps(fPtrHdl, PHYSICALWIDTH);
fPhysicalOffsetPx.x := GetDeviceCaps(fPtrHdl,PHYSICALOFFSETX);
fPhysicalSizePx.y := GetDeviceCaps(fPtrHdl, PHYSICALHEIGHT);
fPhysicalOffsetPx.y := GetDeviceCaps(fPtrHdl,PHYSICALOFFSETY);
fDefaultLineWidth := (fPrinterPxPerInch.y*25) div 2540; // 0.25 mm
exit; // if a printer was found then that's all that's needed
except
fHasPrinterInstalled := false;
end;
// ForceScreenResolution or no Printer: use screen resolution
if fHasPrinterInstalled then begin
if (Printer.orientation <> fOrientation) then
Printer.orientation := fOrientation;
fPtrHdl := printer.Handle;
fPhysicalSizePx.X := round(GetDeviceCaps(fPtrHdl, PHYSICALWIDTH) *
screen.pixelsperinch / GetDeviceCaps(fPtrHdl, LOGPIXELSX));
fPhysicalSizePx.Y := round(GetDeviceCaps(fPtrHdl, PHYSICALHEIGHT) *
screen.pixelsperinch / GetDeviceCaps(fPtrHdl, LOGPIXELSY));
end else begin
// if no printer drivers installed use the screen as device context and
// assume A4 page size...
fPtrHdl := 0; //GetDC(0);
fPhysicalSizePx.X := MulDiv(PAPERSIZE_A4_WIDTH*10,screen.pixelsperinch,254);
fPhysicalSizePx.Y := MulDiv(PAPERSIZE_A4_HEIGHT*10,screen.pixelsperinch,254);
end;
//assume 6mm non-printing offsets...
fPhysicalOffsetPx.X := MulDiv(60,screen.pixelsperinch,254);
fPhysicalOffsetPx.Y := MulDiv(60,screen.pixelsperinch,254);
fPrinterPxPerInch.X := screen.pixelsperinch;
fPrinterPxPerInch.Y := screen.pixelsperinch;
//fDefaultLineWidth ==> 0.3 mm
fDefaultLineWidth := (fPrinterPxPerInch.y*3) div 254;
if not fHasPrinterInstalled and (fOrientation = poLandscape) then begin
// no Printer.Orientation -> swap width & height if Landscape page layout
i := fPhysicalSizePx.x;
fPhysicalSizePx.x := fPhysicalSizePx.y;
fPhysicalSizePx.y := i;
end;
end;
procedure TGDIPages.SetMetaFileForPage(PageIndex: integer; MetaFile: TMetaFile);
var stream: TRawByteStringStream;
begin
if cardinal(PageIndex)>=cardinal(length(fPages)) then
exit;
stream := TRawByteStringStream.Create;
try
MetaFile.SaveToStream(stream);
fPages[PageIndex].MetaFileCompressed := stream.DataString;
CompressSynLZ(fPages[PageIndex].MetaFileCompressed,true);
finally
stream.Free;
end;
end;
function TGDIPages.GetMetaFileForPage(PageIndex: integer): TMetaFile;
var tmp: RawByteString;
stream: TStream;
begin
if fMetaFileForPage=nil then
fMetaFileForPage := TMetafile.Create else
fMetaFileForPage.Clear;
result := fMetaFileForPage;
if cardinal(PageIndex)>=cardinal(length(fPages)) then
exit;
tmp := fPages[PageIndex].MetaFileCompressed;
CompressSynLZ(tmp,false);
stream := TRawByteStringStream.Create(tmp);
try
fMetaFileForPage.LoadFromStream(stream);
finally
stream.Free;
end;
end;
function TGDIPages.PrinterPxToScreenPxX(PrinterPx: integer): integer;
begin
if (Self=nil) or (fPrinterPxPerInch.x=0) then
result := 0 else
result := (PrinterPx*screen.pixelsperinch*fZoom) div (fPrinterPxPerInch.x*100);
end;
function TGDIPages.PrinterPxToScreenPxY(PrinterPx: integer): integer;
begin
if (Self=nil) or (fPrinterPxPerInch.y=0) then
result := 0 else
result := (PrinterPx*screen.pixelsperinch*fZoom) div (fPrinterPxPerInch.y*100);
end;
function TGDIPages.MmToPrinterPxX(mm: integer): integer;
begin
if Self=nil then
result := 0 else
result := ((mm*10) * fPrinterPxPerInch.x) div 254;
end;
function TGDIPages.MmToPrinterPxY(mm: integer): integer;
begin
if Self=nil then
result := 0 else
result := ((mm*10) * fPrinterPxPerInch.y) div 254;
end;
function TGDIPages.PrinterPxToMmX(px: integer): integer;
begin
if (Self=nil) or (fPrinterPxPerInch.x=0) then
result := 0 else
result := (px*254) div (fPrinterPxPerInch.x*10);
end;
function TGDIPages.PrinterPxToMmY(px: integer): integer;
begin
if (Self=nil) or (fPrinterPxPerInch.y=0) then
result := 0 else
result := (px*254) div (fPrinterPxPerInch.y*10);
end;
procedure TGDIPages.ResizeAndCenterPaintbox;
var l,t, i: integer;
siz: TPoint;
begin
if cardinal(page-1)<cardinal(length(fPages)) then
siz := fPages[page-1].SizePx else
siz := fPhysicalSizePx;
// center the paintbox according to the new size
with fPreviewSurface do begin
siz.X := PrinterPxToScreenPxX(siz.X)+GRAY_MARGIN*2;
siz.Y := PrinterPxToScreenPxY(siz.Y)+GRAY_MARGIN*2;
l := Max((Self.ClientWidth - siz.X) div 2,0) - HorzScrollbar.Position;
t := Max((Self.ClientHeight - siz.Y) div 2,0) - VertScrollbar.Position;
SetBounds(l,t,siz.X,siz.Y);
end;
// resize any hot link
for i := 0 to fLinks.Count-1 do
TGDIPagereference(fLinks.Objects[i]).ToPreview(Self);
end;
function TGDIPages.GetOrientation: TPrinterOrientation;
begin
if (Self=nil) or (fPhysicalSizePx.x > fPhysicalSizePx.y) then
result := poLandscape else
result := poPortrait;
end;
procedure TGDIPages.SetTextAlign(Value: TTextAlign);
begin
if Self<>nil then
fAlign := Value;
end;
procedure TGDIPages.SetOrientation(orientation: TPrinterOrientation);
begin
if (Self<>nil) and (fOrientation<>orientation) then begin
fOrientation := orientation;
if fPages<>nil then begin
// changed orientation after start writing -> customize with inversed size
fCustomPageSize.X := fPhysicalSizePx.Y;
fCustomPageSize.Y := fPhysicalSizePx.X;
fCustomPxPerInch.X := fPrinterPxPerInch.Y;
fCustomPxPerInch.Y := fPrinterPxPerInch.X;
fCustomNonPrintableOffset.X := fPhysicalOffsetPx.Y;
fCustomNonPrintableOffset.Y := fPhysicalOffsetPx.X;
end;
end;
end;
procedure TGDIPages.NewPageLayout(sizeWidthMM, sizeHeightMM: integer;
nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1);
begin
if Self=nil then
exit;
fCustomPageSize.X := MmToPrinterPxX(sizeWidthMM);
fCustomPageSize.Y := MmToPrinterPxY(sizeHeightMM);
if (nonPrintableWidthMM>=0) and (nonPrintableHeightMM>=0) then begin
fCustomNonPrintableOffset.X := MmToPrinterPxX(nonPrintableWidthMM);
fCustomNonPrintableOffset.Y := MmToPrinterPxY(nonPrintableHeightMM);
end;
if fPages<>nil then // force new page if already some content
NewPage(true);
end;
procedure TGDIPages.NewPageLayout(paperSize: TGdiPagePaperSize;
orientation: TPrinterOrientation=poPortrait;
nonPrintableWidthMM: integer=-1; nonPrintableHeightMM: integer=-1);
var Siz,NonPrint: TPoint;
const // psA4, psA5, psA3, psLetter, psLegal
SIZES: array[TGdiPagePaperSize] of TPoint = (
(x:210;y:297),(x:148;y:210),(x:297;y:420),(x:216;y:279),(x:216;y:356));
begin
if orientation=poPortrait then begin
Siz := SIZES[paperSize];
NonPrint.X := nonPrintableWidthMM;
NonPrint.Y := nonPrintableHeightMM;
end else begin
Siz.X := SIZES[paperSize].Y;
Siz.Y := SIZES[paperSize].X;
NonPrint.X := nonPrintableHeightMM;
NonPrint.Y := nonPrintableWidthMM;
end;
NewPageLayout(Siz.X,Siz.Y,NonPrint.X,NonPrint.Y);
end;
procedure TGDIPages.SetPage(NewPreviewPage: integer);
begin
if Self=nil then exit;
if NewPreviewPage > length(fPages) then
NewPreviewPage := length(fPages) else
if NewPreviewPage < 1 then
NewPreviewPage := 1;
if (Pages = nil) or (fCurrPreviewPage = NewPreviewPage) then
exit;
fCurrPreviewPage := NewPreviewPage;
fLinksCurrent := -1;
FreeAndNil(PreviewSurfaceBitmap); // force double buffering Bitmap recreate
ResizeAndCenterPaintbox; // if page size changed
PreviewPaint(Self);
if Assigned(fPreviewPageChangedEvent) then
fPreviewPageChangedEvent(Self);
if PreviewForm<>nil then begin
PreviewPageCountLabel.Caption := format(sPageN,[Page,PageCount]);
PreviewButtons[ord(rNextPage)-1].Enabled := Page<PageCount;
PreviewButtons[ord(rPreviousPage)-1].Enabled := Page>1;
end;
end;
function TGDIPages.GetPageCount: integer;
begin
if Self=nil then
result := 0 else
result := length(fPages);
end;
function TGDIPages.GetLineHeight: integer;
var tm: TTextMetric;
DC: HDC;
begin
if Self=nil then begin
result := 0;
exit;
end;
if fLineHeight = 0 then begin
if not Assigned(fCanvas) then begin
// if no current fCanvas: use the Screen resolution (very fast)
DC := GetDC(0);
GetTextMetrics(DC,tm);
ReleaseDC(0,DC);
end else
GetTextMetrics(fCanvas.Handle,tm);
fLineHeight := ((tm.tmHeight+tm.tmExternalLeading)*9)shr 3;
end;
if fInHeaderOrFooter then
result := fLineHeight else
case fLineSpacing of
lsSingle: result := fLineHeight;
lsOneAndHalf: result := (fLineHeight*3) shr 1;
else result := fLineHeight*2;
end;
end;
function TGDIPages.GetLineHeightMm: integer;
begin
if Self=nil then
result := 0 else
result := PrinterPxToMmY(GetLineHeight);
end;
procedure TGDIPages.CheckHeaderDone;
begin
if not fHeaderDone then
DoHeader;
end;
procedure TGDIPages.CheckYPos;
begin
if Self=nil then exit;
if fInHeaderOrFooter then exit;
CheckHeaderDone;
if not HasSpaceForLines(1) then begin
NewPageInternal;
// nb: header is done inside a group, so we must check for it
CheckHeaderDone;
end;
end;
function TGDIPages.GetYPos: integer;
begin
if (Self=nil) or (fPrinterPxPerInch.y=0) then
result := 0 else
result := (fCurrentYPos*254) div (fPrinterPxPerInch.y*10);
end;
procedure TGDIPages.SetYPos(YPos: integer);
begin
if Self=nil then exit;
if fCurrentYPos >= fPhysicalSizePx.y then
NewPageInternal;
fCurrentYPos := MmToPrinterPxY(YPos);
end;
function TGDIPages.GetSavedState: TSavedState;
begin
with result do begin
Flags := TextFormatsToFlags;
FontName := Font.Name;
FontColor := Font.Color;
LeftMargin := fPageMarginsPx.Left;
RightMargin := fPageMarginsPx.Right;
BiDiMode := fBiDiMode;
end;
end;
procedure TGDIPages.SetSavedState(const SavedState: TSavedState);
begin
with SavedState do begin
SetFontWithFlags(Flags);
Font.Name := FontName;
Font.Color := FontColor;
fPageMarginsPx.Left := LeftMargin;
fPageMarginsPx.Right := RightMargin;
fBiDiMode := BiDiMode;
end;
end;
procedure TGDIPages.SaveLayout;
begin
if Self=nil then exit; // avoid GPF
if fSavedCount>=length(fSaved) then
SetLength(fSaved,fSavedCount+20);
fSaved[fSavedCount] := SavedState;
inc(fSavedCount);
end;
procedure TGDIPages.RestoreSavedLayout;
begin
if Self=nil then exit; // avoid GPF
if fSavedCount<=0 then
exit;
dec(fSavedCount);
SavedState := fSaved[fSavedCount];
end;
function TGDIPages.CreateMetaFile(aWidth, aHeight: integer): TMetaFile;
begin
result := TMetafile.Create;
if Self=nil then exit;
result.Enhanced := true;
result.Width := aWidth;
result.Height := aHeight;
end;
procedure TGDIPages.FlushPageContent;
var n: integer;
begin
n := length(fPages);
if n>0 then begin
with fPages[n-1] do begin
Text := fCanvasText;
SizePx := fPhysicalSizePx;
MarginPx := fPageMarginsPx;
OffsetPx := fPhysicalOffsetPx;
end;
if fCurrentMetaFile<>nil then begin
SetMetaFileForPage(n-1,fCurrentMetaFile);
FreeAndNil(fCurrentMetaFile);
end;
end;
end;
procedure TGDIPages.NewPageInternal;
var n: integer;
UsedGroupSpace: integer;
InGroup: boolean;
GroupText: string;
begin
if Self=nil then exit;
UsedGroupSpace := 0; //stops a warning
InGroup := Assigned(fGroupPage);
if InGroup then begin // close the Group Canvas
UsedGroupSpace := fCurrentYPos;
FreeAndNil(fCanvas); // now recreate/redraw a fresh fCanvas for DoFooter
fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
fCanvas.Draw(0,0,fCurrentMetaFile); // re-draw last page
GroupText := fCanvasText;
fCanvasText := fBeforeGroupText;
end;
DoFooter;
//create a new metafile and its canvas ...
if Assigned(fCanvas) then
FreeAndNil(fCanvas);
FlushPageContent;
SetAnyCustomPagePx;
//NewPage.MMWidth := (fPhysicalSizePx.x*2540) div fPrinterPxPerInch.x;
//NewPage.MMHeight := (fPhysicalSizePx.y*2540) div fPrinterPxPerInch.y;
n := Length(fPages)+1;
SetLength(fPages,n);
fCurrentMetaFile := CreateMetaFile(fPhysicalSizePx.x,fPhysicalSizePx.y);
fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
fCanvasText := '';
inc(fVirtualPageNum);
fCurrentYPos := fPageMarginsPx.top;
if Assigned(fStartNewPage) then
fStartNewPage(Self,n);
fHeaderDone := false;
fColumnHeaderPrinted := false; // when next col. started add header
if InGroup then begin // draw the group at the begining of new page + EndGroup
DoHeader;
if fColumnsUsedInGroup then begin
//The next line is a workaround to stop an endless loop. CheckYPos (called
//via PrintColumnHeaders) thinks we're still drawing on fGroupPage as it's
//still Assigned so can flag "out of room" and try to create another page.
fGroupVerticalSpace := fPhysicalSizePx.y;
if not fColumnHeaderInGroup then
PrintColumnHeaders else
fColumnHeaderPrinted := true;
end;
fCanvas.Draw(0,fCurrentYPos,fGroupPage);
FreeAndNil(fGroupPage); // idem as EndGroup
inc(fCurrentYPos,UsedGroupSpace);
fCanvasText := fCanvasText+GroupText;
end;
end;
function TGDIPages.CreateMetafileCanvas(Page: TMetafile): TMetafileCanvas;
begin
result := TMetafileCanvas.Create(Page,fPtrHdl);
if Self=nil then exit;
UpdateMetafileCanvasFont(result);
result.Pen.Width := fPrinterPxPerInch.y div screen.PixelsPerInch;
end;
procedure TGDIPages.UpdateMetafileCanvasFont(aCanvas: TMetafileCanvas);
begin
// next 2 lines are a printer bug workaround - 23Mar2000
aCanvas.Font.Size := Font.Size+1;
aCanvas.Font.PixelsPerInch := fPrinterPxPerInch.y;
aCanvas.Font := Font;
end;
function TGDIPages.TextFormatsToFlags: integer;
begin
result := min(max(font.size,4),FORMAT_SIZE_MASK); { size between 4 and 255 }
case fAlign of
taRight: result := result or FORMAT_RIGHT;
taCenter: result := result or FORMAT_CENTER;
taJustified: result := result or FORMAT_JUSTIFIED;
end;
if fsBold in font.style then
result := result or FORMAT_BOLD;
if fsUnderline in font.style then
result := result or FORMAT_UNDERLINE;
if fsItalic in font.style then
result := result or FORMAT_ITALIC;
end;
procedure TGDIPages.SetFontWithFlags(flags: integer);
var fontstyle: TFontStyles;
begin
if flags and FORMAT_SIZE_MASK<>Font.Size then
Font.size := flags and FORMAT_SIZE_MASK;
if (flags and FORMAT_BOLD) <> 0 then
fontstyle := [fsBold] else
fontstyle := [];
if (flags and FORMAT_UNDERLINE) <> 0 then
include(fontstyle,fsUnderline);
if (flags and FORMAT_ITALIC) <> 0 then
include(fontstyle,fsItalic);
if Font.Style<>fontstyle then
Font.Style := fontstyle;
case flags and FORMAT_ALIGN_MASK of
FORMAT_RIGHT: falign := taRight;
FORMAT_CENTER: falign := taCenter;
FORMAT_JUSTIFIED: falign := taJustified;
else falign := taLeft;
end;
end;
function TGDIPages.HasSpaceForLines(Count: integer): boolean;
begin
if Self=nil then
result := false else // avoid GPF
if Assigned(fGroupPage) then
result := fCurrentYPos + GetLineHeight*Count < fGroupVerticalSpace else
result := fCurrentYPos + GetLineHeight*Count <
fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight;
end;
function TGDIPages.HasSpaceFor(mm: integer): boolean;
begin
if Self=nil then
result := false else begin // avoid GPF
mm := fCurrentYPos + MmToPrinterPxY(mm);
if Assigned(fGroupPage) then
result := mm < fGroupVerticalSpace else
result := mm < fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight;
end;
end;
procedure TGDIPages.DoHeader;
begin
fHeaderDone := true;
if (fHeaderLines.Count = 0) then exit;
SaveLayout;
fInHeaderOrFooter := true;
try
if Assigned(fStartPageHeader) then
fStartPageHeader(Self);
Font.Color := clBlack;
DoHeaderFooterInternal(fHeaderLines);
if Assigned(fEndPageHeader) then
fEndPageHeader(Self);
GetLineHeight;
inc(fCurrentYPos,fLineHeight shr 2); // add a small header gap
fHeaderHeight := fCurrentYPos-fPageMarginsPx.Top;
finally
fInHeaderOrFooter := false;
RestoreSavedLayout;
end;
end;
procedure TGDIPages.DoFooter;
begin
if (fFooterLines.Count = 0) then exit;
SaveLayout;
fInHeaderOrFooter := true;
try
fCurrentYPos :=
fPhysicalSizePx.y - fPageMarginsPx.bottom - fFooterHeight + fFooterGap;
if Assigned(fStartPageFooter) then
fStartPageFooter(Self);
DoHeaderFooterInternal(fFooterLines);
if Assigned(fEndPageFooter) then
fEndPageFooter(Self);
finally
fInHeaderOrFooter := false;
RestoreSavedLayout;
end;
end;
procedure TGDIPages.DoHeaderFooterInternal(Lines: TObjectList);
var i: integer;
begin
SaveLayout;
try
for i := 0 to Lines.Count -1 do
with THeaderFooter(Lines[i]) do
begin
SavedState := State;
PrintFormattedLine(Text, State.Flags);
end;
finally
RestoreSavedLayout;
end;
end;
procedure TGDIPages.CalcFooterGap;
begin
GetLineHeight;
// make sure there's a gap of at least 1/4 of a lineheight
// between the page body and the footer ...
fFooterGap := fLineHeight shr 2;
fFooterHeight := fFooterGap;
end;
function TGDIPages.GetColumnRec(col: integer): TColRec;
begin
if Cardinal(col)<Cardinal(length(fColumns)) then
result := fColumns[col] else begin
result.ColLeft := 0;
result.ColRight := 0;
end;
end;
procedure TGDIPages.PrintColumnHeaders;
var
i,SavedFontSize,FontCol: integer;
SavedFontStyle: TFontStyles;
SavedAlign: TTextAlign;
SavedWordWrapLeftCols: boolean;
begin
if (fColumnHeaderList = nil) or (fColumns=nil) then exit;
CheckYPos;
fColumnHeaderPrinted := true; //stops an endless loop
SavedFontSize := Font.size;
SavedFontStyle := font.style;
SavedAlign := fAlign;
SavedWordWrapLeftCols := WordWrapLeftCols;
WordWrapLeftCols := false;
if Assigned(fStartColumnHeader) then
fStartColumnHeader(Self);
FontCol := fCanvas.Font.Color;
for i := 0 to High(fColumnHeaderList) do begin
SetFontWithFlags(fColumnHeaderList[i].flags);
fCanvas.Font.Color := clBlack;
fDrawTextAcrossColsDrawingHeader := true;
DrawTextAcrossCols(fColumnHeaderList[i].headers,[],clNone);
fDrawTextAcrossColsDrawingHeader := false;
end;
fCanvas.Font.Color := FontCol;
if Assigned(fEndColumnHeader) then
fEndColumnHeader(Self);
// add a small space below the column headers
// inc(fCurrentYPos,fLineHeight shr 2);
Font.Size := SavedFontSize;
Font.Style := SavedFontStyle;
fAlign := SavedAlign;
WordWrapLeftCols := SavedWordWrapLeftCols;
if Assigned(fGroupPage) then
fColumnHeaderInGroup := true;
fColumnHeaderPrintedAtLeastOnce :=
ForceCopyTextAsWholeContent; // don't reproduce headers every page
end;
procedure TGDIPages.SetZoomStatus(aZoomStatus: TZoomStatus);
var zoom: integer;
begin
if (self=nil) or (aZoomStatus=fZoomStatus) then
exit;
case aZoomStatus of
zsPageFit: zoom := PAGE_FIT;
zsPageWidth: zoom := PAGE_WIDTH;
else zoom := fZoom;
end;
SetZoom(zoom);
end;
procedure TGDIPages.SetZoom(Zoom: integer);
var scrollIncrement, zoomW, zoomH: integer;
siz: TPoint;
begin
if (Self=nil) or (zoom < PAGE_FIT) or (zoom in [0..9]) or (zoom > 200) then
exit;
fLinksCurrent := -1;
FreeAndNil(PreviewSurfaceBitmap);
if (not handleallocated) or (fZoom=Zoom) or
(cardinal(page-1)>=cardinal(length(fPages))) then
exit;
// calculate the new fZoom ...
siz := fPages[page-1].SizePx;
if (siz.x=0) or (siz.y=0) then // in case of potential div per 0 -> do it later
exit else
if zoom = PAGE_FIT then begin
ZoomW := trunc((clientWidth-GRAY_MARGIN*2)*fPrinterPxPerInch.x*
100/siz.x/screen.pixelsperinch);
ZoomH := trunc((clientHeight-GRAY_MARGIN*2)*fPrinterPxPerInch.y*
100/siz.y/screen.pixelsperinch);
//choose the smallest of width% and height% to fit on page (but min 10%)
fZoom := Max(Min(ZoomW,ZoomH),10);
end else
if zoom = PAGE_WIDTH then
fZoom := trunc((clientWidth-GRAY_MARGIN*2)*fPrinterPxPerInch.x*
100/siz.x/screen.pixelsperinch) else
fZoom := Zoom;
// ZoomStatus required when resizing...
if zoom = PAGE_FIT then
fZoomStatus := zsPageFit else
if zoom = PAGE_WIDTH then
fZoomStatus := zsPageWidth else
fZoomStatus := zsPercent;
scrollIncrement := PrinterPxToScreenPxY(GetLineHeight);
HorzScrollbar.Increment := scrollIncrement;
VertScrollbar.Increment := scrollIncrement;
// resize and center preview surface...
ResizeAndCenterPaintbox;
if Assigned(fZoomChangedEvent) then
fZoomChangedEvent(Self, fZoom, fZoomStatus);
end;
const
ZOOMSTEP = 20;
procedure TGDIPages.ZoomTimerInternal(X,Y: integer; ZoomIn: boolean);
var
OldZoom: integer;
pt, siz: TPoint;
begin
if (Self=nil) or (fPhysicalSizePx.x=0) or (fPhysicalSizePx.y=0) then
Exit;
if page>0 then
siz := fPages[page-1].SizePx else
siz := fPhysicalSizePx;
OldZoom := fZoom;
sendmessage(handle,WM_SETREDRAW,0,0);
try
if ZoomIn then begin
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
if fZoom >= 200 then
fZoomTimer.enabled := false else //(maximum 200%)
{$else}if fZoom < 200 then {$endif}
Zoom := ((fZoom + ZOOMSTEP) div ZOOMSTEP)*ZOOMSTEP; //to nearest ZOOMSTEP%
end else begin
if (fZoom > 20) then
Zoom := ((fZoom - ZOOMSTEP) div ZOOMSTEP)*ZOOMSTEP else //(minimum 20%)
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
fZoomTimer.enabled := false;
{$endif}
end;
if fZoom = OldZoom then
exit;
// work out click pos relative to page (as x & y percentages)
pt.x := ((X-fPreviewSurface.left-GRAY_MARGIN)*100) div PrinterPxToScreenPxX(siz.x);
pt.x := min(max(pt.x,0),100);
pt.y := ((Y-fPreviewSurface.top-GRAY_MARGIN)*100) div PrinterPxToScreenPxY(siz.y);
pt.y := min(max(pt.y,0),100);
// finally, adjust scrollbar positions based on click pos ...
with HorzScrollbar do position := (pt.x*(range-clientwidth)) div 100;
with VertScrollbar do position := (pt.y*(range-clientheight)) div 100;
finally
SendMessage(handle,WM_SETREDRAW,1,0);
end;
Invalidate;
end;
procedure TGDIPages.ZoomTimer(Sender: TObject);
var
CursorPos: TPoint;
begin
GetCursorPos(CursorPos);
CursorPos := ScreenToClient(CursorPos);
ZoomTimerInternal(CursorPos.x,CursorPos.y, fZoomIn);
end;
procedure TGDIPages.LineInternal(start, finish : integer; doubleline : boolean);
begin
LineInternal(fCurrentYPos + (GetLineHeight shr 1), start, finish, doubleline);
end;
procedure TGDIPages.LineInternal(aty, start, finish : integer; doubleline : boolean);
var Y: integer;
begin
if (Self <> nil) and (fCanvas <> nil) then
with fCanvas do begin
Pen.Width := MulDiv(fDefaultLineWidth, Self.Font.size, 8);
if fsBold in Self.Font.style then Pen.Width := Pen.Width + 1;
if doubleline then begin
Y := aty - (Pen.Width);
MoveTo(start, Y);
LineTo(finish, Y);
MoveTo(start, Y + (Pen.Width * 2));
LineTo(finish, Y + (Pen.Width * 2));
end else begin
Y := aty - (Pen.Width shr 1);
MoveTo(start, Y);
LineTo(finish, Y);
end;
end;
end;
procedure TGDIPages.PrintFormattedLine(s: SynUnicode; flags: integer;
const aBookmark: string; const aLink: string; withNewLine,aLinkNoBorder: boolean);
var i, xpos: integer;
leftOffset, rightOffset: integer;
begin
s := RightTrim(s);
i := pos(PAGENUMBER,LowerCaseU(s));
if i > 0 then begin
delete(s,i,14);
insert(UTF8ToSynUnicode(Int32ToUtf8(fVirtualPageNum)),s,i);
end;
if flags <> FORMAT_DEFAULT then
SetFontWithFlags(flags);
CheckYPos;
fCurrentTextTop := fCurrentYPos;
fCurrentTextPage := PageCount;
GetTextLimitsPx(leftOffset,rightOffset);
if flags and (FORMAT_SINGLELINE or FORMAT_DOUBLELINE)<>0 then begin
LineInternal(leftOffset,rightOffset,flags and FORMAT_DOUBLELINE=FORMAT_DOUBLELINE);
NewLine;
end else
if s = '' then begin
if withNewLine then
NewLine;
end else
if (flags and FORMAT_XPOS_MASK <> 0) then begin
xpos := ((flags and FORMAT_XPOS_MASK) shr 16)-2;
if xpos<0 then
xpos := RightMarginPos else
inc(xpos);
DrawTextAt(s,xpos);
end else
if (falign in [taLeft,taJustified]) then
LeftOrJustifiedWrap(s,withNewLine) else
RightOrCenterWrap(s);
if aBookmark<>'' then
AddBookMark(aBookmark,fCurrentTextTop);
if aLink<>'' then
AddLink(aLink,Rect(PrinterPxToMmX(leftOffset),PrinterPxToMmY(fCurrentTextTop),
PrinterPxToMmX(rightOffset),PrinterPxToMmY(fCurrentTextTop+fLineHeight)),
fCurrentTextPage,aLinkNoBorder);
// first line of written text is added
end;
procedure TGDIPages.LeftOrJustifiedWrap(const s: SynUnicode; withNewLine: boolean);
var indent, leftOffset, rightOffset, LineWidth: integer;
leftstring, rightstring: SynUnicode;
firstLoop: boolean;
begin
leftstring := s;
Indent := MmToPrinterPxX(fHangIndent);
firstLoop := true;
repeat
CheckYPos;
GetTextLimitsPx(leftOffset,rightOffset);
LineWidth := rightOffset-leftOffset;
// offset leftOffset if hang-indenting...
if Indent<>0 then
if firstLoop then begin
firstLoop := false;
if (Indent < 0) then begin
inc(leftOffset,-Indent);
dec(LineWidth,-Indent);
end;
end else
if (Indent > 0) and (Indent < LineWidth) then begin
inc(leftOffset,Indent);
dec(LineWidth,Indent);
end;
// dump overrun into rightstring...
TrimLine(fCanvas,leftstring,rightstring,LineWidth);
// HandleTabsAndPrint: prints leftstring after adjusting for tabs and
// prepending any further text overrun into rightstring ...
HandleTabsAndPrint(leftstring, rightstring, leftOffset, rightOffset);
if length(rightstring)=0 then
break;
leftstring := rightstring;
NewLine;
until false;
if withNewLine then
NewLine;
end;
procedure TGDIPages.RightOrCenterWrap(const s: SynUnicode);
var i,leftOffset,rightOffset, LineWidth: integer;
leftstring,rightstring: SynUnicode;
offset: integer;
begin
leftstring := s;
// remove tabs and replace by spaces
i := pos(#9,leftstring);
while i > 0 do begin
delete(leftstring,i,1);
insert(' ',leftstring,i);
i := pos(#9,leftstring);
end;
// write text
SetBkMode(fCanvas.Handle,TRANSPARENT);
repeat
GetTextLimitsPx(leftOffset,rightOffset);
LineWidth := rightOffset-leftOffset;
TrimLine(fCanvas,leftstring,rightstring,LineWidth);
case falign of
taRight: Offset := rightOffset-TextWidthC(fCanvas,leftstring)-1;
taCenter: Offset := leftOffset+
(rightOffset-leftOffset-TextWidthC(fCanvas,leftstring))div 2;
else Offset := 0; // should never happen - ?? add assert
end;
CheckYPos;
TextOut(fCanvas,Offset,fCurrentYPos,leftstring);
if length(rightstring) = 0 then break;
leftstring := rightstring;
NewLine;
until false;
NewLine;
end;
procedure TGDIPages.GetTextLimitsPx(var LeftOffset, RightOffset: integer);
begin
// Offsets (in Printer pixels) based on current page margins
LeftOffset := fPageMarginsPx.left;
if fForcedLeftOffset <> -1 then
leftOffset := fForcedLeftOffset;
RightOffset := fPhysicalSizePx.x-fPageMarginsPx.right;
if RightOffset < LeftOffset then
raise Exception.Create('GetTextLimitsPx: wrong margins');
end;
procedure TGDIPages.HandleTabsAndPrint(const leftstring: SynUnicode;
var rightstring: SynUnicode; leftOffset, rightOffset: integer);
const
// if a tabstop is very close to the right margin, it may spoil justifying...
MIN_CHAR_WIDTH_PX = 5;
var i, spacecount, linewidth, tabPos, tabIndex, PWLen: integer;
ls, rs: SynUnicode;
size: TSize;
PW: PWideChar;
begin
// handles tabs one at a time and prints text into the available space...
// (unfortunately there's no equivalent GetTextExtentExPoint() for tabbed text
// and using GetTabbedTextExtent() and TabbedDrawText() instead would appear
// to be undesirable as there's no efficient way to determine the number of
// chars that will fit within the specified space)
ls := leftstring;
linewidth := rightOffset - leftOffset;
tabPos := pos(#9,ls);
SetBkMode(fCanvas.Handle,TRANSPARENT);
while tabPos > 0 do begin // and still room to print
// split line at the tab ...
if rs <> '' then
rs := copy(ls,tabPos+1,length(ls)) + ' '+ rs else
rs := copy(ls,tabPos+1,length(ls));
// add a trailing space so next the tabstop is at least one space away ...
ls := copy(ls,1,tabPos-1)+' ';
// get offset of next tabstop ...
size := TextExtent(fCanvas,ls,tabPos);
i := leftOffset + size.cx; //minimum pos for next tabstop
tabIndex := 0;
while tabIndex < MAXTABS do
if fTab[tabIndex] > i then
break else
inc(tabIndex);
if (tabIndex = MAXTABS) or
(fTab[tabIndex] >= rightOffset - MIN_CHAR_WIDTH_PX) then begin
// no tabstop found to align 'rs' to, so ...
// rather than left aligning 'ls', remove its appended space and
// break out ready to print it ? align left&right justified.
SetLength(ls,length(ls)-1);
break;
end;
// tabstop found so DrawText 'ls' simply left aligned ...
TextOut(fCanvas,leftOffset,fCurrentYPos,ls);
leftOffset := fTab[tabIndex];
linewidth := rightOffset - leftOffset;
ls := rs;
TrimLine(fCanvas,ls,rs,linewidth);
tabPos := pos(#9,ls);
end;
if rs <> '' then
rightstring := rs + ' '+ rightstring;
// OK, no TABS now in ls...
InternalUnicodeString(ls,PW,PWLen,@size);
// print ls into (remaining) linewidth at (leftOffset, fCurrentYPos)
if (falign = taLeft) or (rightstring = '') then begin // left aligned
if BiDiMode=bdRightToLeft then
leftOffset := rightOffset-size.cx;
TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);
fForcedLeftOffset := leftOffset+size.cx;
// don't care about line width: it should be always equal or smaller,
// and we are left aligned
end else begin // justified
spacecount := 0;
for i := 1 to length(ls) do
if ls[i] = ' ' then
inc(spacecount);
if spacecount>0 then
SetTextJustification(fCanvas.Handle, linewidth - size.cx, spacecount);
TextOut(fCanvas,leftOffset,fCurrentYPos,PW,PWLen);
SetTextJustification(fCanvas.Handle,0,0);
end;
end;
procedure TGDIPages.PreviewPaint(Sender: TObject);
var R: TRect;
P1,P2: TPoint;
metapage: TMetaFile;
begin
if csDesigning in ComponentState then begin // no preview at design time
R := fPreviewSurface.ClientRect;
fPreviewSurface.Canvas.Brush.Color := Color;
fPreviewSurface.Canvas.FillRect(R);
exit;
end;
if not Visible then begin
FreeAndNil(PreviewSurfaceBitmap);
exit;
end;
if PreviewSurfaceBitmap<>nil then
fPreviewSurface.Canvas.Draw(0,0,PreviewSurfaceBitmap) else
with fPreviewSurface do begin
// paint the page white with a dark gray line around it
R := ClientRect;
PreviewSurfaceBitmap := TBitmap.Create;
PreviewSurfaceBitmap.Width := R.Right;
PreviewSurfaceBitmap.Height := R.Bottom;
with PreviewSurfaceBitmap.Canvas do begin
Brush.Color := Color; // background color
FillRect(R);
InflateRect(R,-GRAY_MARGIN,-GRAY_MARGIN);
Brush.Color := clWhite;
Pen.Width := 1;
Pen.Color := clGray;
Rectangle(R);
Refresh;
end;
// draw the metafile on the page
if (fPages<>nil) and (cardinal(Page-1)<=cardinal(High(fPages))) and
(fPages[Page-1].MetaFileCompressed<>'') then begin
// note: we must use a temporary TMetaFile, otherwise the Pages[] content
// is changed (screen dpi is changed but not reset in nested emf) and the
// resulting report is incorrect on most printers, due to a driver bug :(
metapage := GetMetaFileForPage(Page-1);
{$ifdef GDIPLUSDRAW} // anti aliased drawing:
if not ForceNoAntiAliased then
DrawEmfGdip(PreviewSurfaceBitmap.Canvas.Handle,metapage,R,
ForceInternalAntiAliased,ForceInternalAntiAliasedFontFallBack) else
{$endif} // fast direct GDI painting, with no antialiaising:
PreviewSurfaceBitmap.Canvas.StretchDraw(R,metapage);
PreviewSurfaceBitmap.Canvas.Refresh;
end;
// draw the change page grey "arrow" buttons
if Page>1 then begin
P1.X := R.Left+10;
P2.X := R.Left+1;
PageLeftButton.X := P2.X;
P1.Y := R.Top+11;
P2.Y := P1.Y;
PageLeftButton.Y := P1.Y-10;
DrawArrowInternal(PreviewSurfaceBitmap.Canvas,P1,P2,10,true);
end else
PageLeftButton.X := 0;
if Page<PageCount then begin
P1.X := R.Right-10;
PageRightButton.X := P1.X;
P2.X := R.Right-1;
P1.Y := R.Top+11;
P2.Y := P1.Y;
PageRightButton.Y := P1.Y-10;
DrawArrowInternal(PreviewSurfaceBitmap.Canvas,P1,P2,10,true);
end else
PageRightButton.X := 0;
//draw the page shadows
R.Top := GRAY_MARGIN+3;
R.Left := ClientWidth-GRAY_MARGIN;
R.Bottom := ClientHeight-GRAY_MARGIN+3;
R.Right := R.Left+3;
PreviewSurfaceBitmap.Canvas.brush.color := clGray;
PreviewSurfaceBitmap.Canvas.FillRect(R);
R.Top := ClientHeight-GRAY_MARGIN;
R.Left := GRAY_MARGIN+3;
R.Bottom := R.Top+3;
R.Right := ClientWidth-GRAY_MARGIN+3;
PreviewSurfaceBitmap.Canvas.brush.color := clGray;
PreviewSurfaceBitmap.Canvas.FillRect(R);
Canvas.Draw(0,0,PreviewSurfaceBitmap)
end;
if fLinksCurrent>=0 then
fPreviewSurface.Canvas.DrawFocusRect(
TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
end;
procedure TGDIPages.PreviewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var i: integer;
begin
if Button=mbRight then begin
if PopupMenu<>nil then begin
with fPreviewSurface.ClientToScreen(Point(X,Y)) do
PopupMenu.Popup(X,Y);
exit;
end;
end else
if Button=mbLeft then begin
if fLinksCurrent>=0 then begin
fPreviewSurface.Canvas.DrawFocusRect(
TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
i := fLinksCurrent;
fLinksCurrent := -1;
GotoBookmark(fLinks[i]);
end else
if (PageLeftButton.X<>0) and
(cardinal(X-PageLeftButton.X)<10) and
(cardinal(Y-PageLeftButton.Y)<20) then begin
Page := Page-1;
exit;
end else
if (PageRightButton.X<>0) and
(cardinal(X-PageRightButton.X)<10) and
(cardinal(Y-PageRightButton.Y)<20) then begin
Page := Page+1;
exit;
end;
end;
if (Button=mbLeft) and (ssDouble in Shift) then
// allows dblclick to alternate between PAGE_FIT and PAGE_WIDTH
if ZoomStatus = zsPageWidth then
Zoom := PAGE_FIT else
Zoom := PAGE_WIDTH else
{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
if Button=mbLeft then begin
fButtonDown.X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
fButtonDown.Y := (Y shr 3)shl 3;
fButtonDownScroll.X := HorzScrollBar.Position;
fButtonDownScroll.Y := VertScrollBar.Position;
Screen.Cursor := crHandPoint;
end;
{$endif}
//pass the TPaintbox mouse-down event messages to Self (TScrollBox) ...
MouseDown(Button,Shift,X+fPreviewSurface.left,Y+fPreviewSurface.Top);
end;
procedure TGDIPages.PreviewMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
//pass the TPaintbox mouse-up event messages to Self (TScrollBox) ...
MouseUp(Button,Shift,X+fPreviewSurface.left,Y+fPreviewSurface.Top);
end;
procedure TGDIPages.PreviewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
var BX, V: integer;
{$endif}
var i: integer;
begin
fMousePos.X := X+fPreviewSurface.left;
fMousePos.Y := Y+fPreviewSurface.Top;
if fLinksCurrent>=0 then begin
fPreviewSurface.Canvas.DrawFocusRect(
TGDIPagereference(fLinks.Objects[fLinksCurrent]).Preview);
fLinksCurrent := -1;
end;
{$ifndef MOUSE_CLICK_PERFORM_ZOOM}
if fButtonDown.X>=0 then begin
X := (X shr 3)shl 3; // move 8 pixels by 8 pixels
Y := (Y shr 3)shl 3;
{ OutputDebugString(pointer(format(
'X=%d Y=%d ScrlIni X=%d Y=%d ScrlCurr X=%d Y=%d ',
[X,Y,fButtonDownScroll.X,fButtonDownScroll.Y,
HorzScrollBar.Position,VertScrollBar.Position]))); }
BX := fButtonDown.X;
fButtonDown.X := -1; // avoid endless recursive call
V := fButtonDownScroll.X-X+BX;
if (V>=0) and (HorzScrollBar.Position<>V) and (V<HorzScrollBar.Range) then begin
HorzScrollBar.Position := V;
fButtonDownScroll.X := V;
end;
V := fButtonDownScroll.Y-Y+fButtonDown.Y;
if (V>=0) and (VertScrollBar.Position<>V) and (V<VertScrollBar.Range) then begin
VertScrollBar.Position := V;
fButtonDownScroll.Y := V;
end;
fButtonDown.X := BX;
exit;
end else
{$endif}
for i := 0 to fLinks.Count-1 do
with TGDIPagereference(fLinks.Objects[i]) do
if (Page=Self.Page) and (X>=Preview.Left) and (X<Preview.Right) and
(Y>=Preview.Top) and (Y<Preview.Bottom) then begin
fLinksCurrent := i;
fPreviewSurface.Canvas.DrawFocusRect(Preview);
break;
end;
end;
procedure TGDIPages.CMFontChanged(var Msg: TMessage);
begin
inherited;
if Assigned(fCanvas) then
UpdateMetafileCanvasFont(fCanvas);
fLineHeight := 0; // force recalculation of lineheight
end;
procedure TGDIPages.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
Message.Result := DLGC_WANTARROWS;
end;
procedure TGDIPages.KeyDown(var Key: Word; Shift: TShiftState);
procedure SetPageAndPosition(newpage,newpos: integer);
begin
perform(WM_SETREDRAW,0,0);
Page := newpage;
VertScrollbar.position := newpos;
perform(WM_SETREDRAW,1,0);
refresh;
end;
var
OldPosition,lh: integer;
begin
lh := PrinterPxToScreenPxY(GetLineHeight);
case Key of
VK_DOWN:
with VertScrollbar do begin
OldPosition := Position;
position := position + lh;
if (Position = OldPosition) and (Page < PageCount) then
SetPageAndPosition(Page+1,0);
end;
VK_UP:
with VertScrollbar do begin
OldPosition := Position;
position := position - lh;
if (Position = OldPosition) and (Page > 1) then
SetPageAndPosition(Page-1,range);
end;
VK_RIGHT:
with HorzScrollbar do
position := position + max(lh,0);
VK_LEFT:
with HorzScrollbar do
position := position - min(lh,range);
VK_NEXT:
with VertScrollbar do
if (shift = [ssCtrl]) and (Page < PageCount) then
SetPageAndPosition(PageCount,0)
else begin
OldPosition := Position;
position := position + max(clientheight - lh,0);
if (Position = OldPosition) and (Page < PageCount) then
SetPageAndPosition(Page+1,0);
end;
VK_PRIOR:
with VertScrollbar do begin
if (shift = [ssCtrl]) and (Page > 1) then
SetPageAndPosition(1,0)
else begin
OldPosition := Position;
position := position - max(clientheight-lh,0);
if (Position = OldPosition) and (Page > 1) then
SetPageAndPosition(Page-1,range);
end;
end;
VK_ADD, VK_SUBTRACT, 187, 189:
if ssCtrl in Shift then begin
fZoomIn := Key in [VK_ADD,187]; // Ctrl+ Ctrl- are standard zoom IN/OUT
ZoomTimer(nil);
end;
VK_ESCAPE:
if PreviewForm<>nil then
PreviewForm.Close; // ESC will close preview form (if any)
end;
inherited;
end;
procedure TGDIPages.CreateWnd;
begin
inherited CreateWnd;
// force page repositioning +/-resizing
case ZoomStatus of
zsPercent: ResizeAndCenterPaintbox;
zsPageWidth: zoom := PAGE_WIDTH;
else zoom := PAGE_FIT;
end;
end;
procedure TGDIPages.Resize;
begin
// force page repositioning +/-resizing
case ZoomStatus of
zsPercent: ResizeAndCenterPaintbox;
zsPageWidth: zoom := PAGE_WIDTH;
else zoom := PAGE_FIT;
end;
inherited Resize;
end;
procedure TGDIPages.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
// allow overriding of default mouse handling...
if not Assigned(OnMouseDown) then begin
fZoomIn := (Button = mbLeft);
ZoomTimerInternal(X, Y, fZoomIn);
fZoomTimer.Enabled := true;
end;
{$endif}
if Button=mbLeft then begin
if PopupMenu<>nil then begin
with fPreviewSurface do
if (X<Left) or (X>Left+Width) then
with Self.ClientToScreen(Point(X,Y)) do
Self.PopupMenu.Popup(X,Y);
end;
end;
if canfocus and not focused then
Setfocus;
inherited;
end;
procedure TGDIPages.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
fZoomTimer.enabled := false;
{$else}
fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
Screen.Cursor := crDefault;
{$endif}
inherited;
end;
{$IFNDEF VER100}
function TGDIPages.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean;
var key: word;
begin
//treat mousewheel events as if a down-arrow or up-arrow event ...
if Shift=[] then begin
if WheelDelta < 0 then
key := VK_DOWN else
key := VK_UP;
KeyDown(Key,[]);
end else
if Shift=[ssCtrl] then
ZoomTimerInternal(fMousePos.X,fMousePos.Y,(WheelDelta>0));
Result := true;
end;
{$ENDIF}
constructor TGDIPages.Create(AOwner: TComponent);
{$ifdef USEPDFPRINTER}
var i: integer;
aName: string;
{$endif}
begin
inherited Create(AOwner);
SetLength(fTab,MAXTABS);
{$ifndef WIN64} // Win64 gdiplus.dll raises some unexpected errors
ForceInternalAntiAliased := true; // GDI+ 1.1 ConvertToEmfPlus is buggy
{$endif}
PopupMenuClass := TPopupMenu;
// DoubleBuffered := true; // avoiding flicker is done in Paint method
Height := 150;
width := 200;
ControlStyle := ControlStyle - [csAcceptsControls];
if (AOWner<>nil) and AOWner.InheritsFrom(TCustomForm) then
Color := TCustomForm(AOwner).Color else
Color := clLtGray;
HorzScrollBar.Tracking := True;
VertScrollBar.Tracking := True;
tabstop := true;
Font.Name := 'Tahoma';
Font.Size := 12;
fLineSpacing := lsSingle;
fOrientation := poPortrait;
fUseOutlines := true;
fHeaderLines := TObjectList.Create;
fFooterLines := TObjectList.Create;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
fZoomTimer := TTimer.create(Self);
fZoomTimer.Interval := 200;
fZoomTimer.OnTimer := ZoomTimer;
fZoomTimer.enabled := false;
{$else}
fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
{$endif}
Int64(fCustomPxPerInch) := -1;
Int64(fCustomPageSize) := -1;
Int64(fCustomNonPrintableOffset) := -1;
Int64(fCustomPageMargins.TopLeft) := -1;
fHasPrinterInstalled := not (csDesigning in componentState)
and PrinterDriverExists;
{$ifdef USEPDFPRINTER}
fPDFPrinterIndex := -1;
if fHasPrinterInstalled then
for i := 0 to Printer.Printers.Count-1 do begin
aName := Printer.Printers[i];
if pos('doPDF',aName)=1 then begin
fPDFPrinterIndex := i;
break;
end else
if pos('PDF',aName)>0 then
fPDFPrinterIndex := i;
end;
fHasPDFPrinterInstalled := (fPDFPrinterIndex<>-1);
{$else}
fExportPDFUseFontFallBack := true;
fExportPDFEncryptionPermissions := PDF_PERMISSION_ALL;
fExportPDFEncryptionOwnerPassword := 'SynopsePDFEngine'+SYNOPSE_FRAMEWORK_VERSION;
{$endif}
GetPrinterParams; // necessary, but will also be updated in BeginDoc()
fCanvas := nil;
fPreviewSurface := TPagePaintbox.Create(Self);
fPreviewSurface.parent := Self;
fPreviewSurface.OnPaint := PreviewPaint;
fPreviewSurface.OnMouseDown := PreviewMouseDown;
fPreviewSurface.OnMouseUp := PreviewMouseUp;
fPreviewSurface.OnMouseMove := PreviewMouseMove;
fZoomStatus := zsPercent;
fZoom := 100;
fBookmarks := TStringList.Create;
fLinks := TStringList.Create;
fOutline := TStringList.Create;
fForcedLeftOffset := -1;
end;
destructor TGDIPages.Destroy;
begin
Clear;
fHeaderLines.free;
fFooterLines.free;
fPreviewSurface.free;
PreviewSurfaceBitmap.Free;
{$ifdef MOUSE_CLICK_PERFORM_ZOOM}
fZoomTimer.free;
{$endif}
fOutline.Free;
fLinks.Free;
fBookmarks.Free;
fMetaFileForPage.Free;
fCurrentMetaFile.Free;
inherited Destroy;
end;
procedure TGDIPages.Invalidate;
begin
FreeAndNil(PreviewSurfaceBitmap); // invalidate custom double buffering
inherited;
end;
procedure TGDIPages.BeginDoc;
begin
if Self=nil then exit; // avoid GPF
Clear;
GetPrinterParams; // essential as Printers.printer object may have changed
fHangIndent := 0;
fAlign := taLeft;
SetPageMargins(Rect(10,10,10,10));
fVirtualPageNum := 0;
Application.ProcessMessages;
NewPageInternal; // create a blank page
// preview resize in case Printers.printer object has changed
case ZoomStatus of
zsPercent: zoom := fzoom;
zsPageWidth: zoom := PAGE_WIDTH;
else zoom := PAGE_FIT;
end;
fButtonDown.X := -1; // so MouseMove() won't scroll paintbox
end;
procedure TGDIPages.DrawText(const s: string; withNewLine : boolean);
begin
DrawTextW(StringToSynUnicode(s), withNewLine);
end;
procedure TGDIPages.DrawTextW(const s: SynUnicode; withNewLine: boolean);
var P, Start: PWideChar;
tmpStr: SynUnicode;
begin
if Self=nil then exit;
CheckYPos;
if s = '' then begin
if withNewLine then
NewLine;
end else begin
// split NewLine characters (#13 or #13#10) into multi lines
P := pointer(s);
while P^ <> #0 do begin
Start := P;
while not (ord(P^) in [0, 10, 13]) do Inc(P);
SetString(tmpStr, Start, P-Start);
if not fInHeaderOrFooter then
fCanvasText := fCanvasText+SynUnicodeToString(tmpStr)+#13#10;
PrintFormattedLine(tmpStr, FORMAT_DEFAULT, '', '', withNewLine);
if P^ = #13 then Inc(P);
if P^ = #10 then Inc(P);
end;
end;
end;
procedure TGDIPages.DrawTextU(const s: RawUTF8; withNewLine: boolean);
begin
DrawTextW(UTF8ToSynUnicode(s),withNewLine);
end;
procedure TGDIPages.DrawTitle(const s: SynUnicode; DrawBottomLine: boolean;
OutlineLevel: Integer; const aBookmark,aLink: string; aLinkNoBorder: boolean);
var H: integer;
str: string;
begin
if Self=nil then exit; // avoid GPF
CheckYPos;
SaveLayout;
try
str := SynUnicodeToString(s);
if not fInHeaderOrFooter then
fCanvasText := fCanvasText+str+#13#10; // copy as text
PrintFormattedLine(s,TitleFlags,aBookMark,aLink,true,aLinkNoBorder);
if UseOutlines then
AddOutline(str,OutlineLevel,fCurrentTextTop,fCurrentTextPage);
if DrawBottomLine then begin
H := (GetLineHeight*15) shr 5;
dec(fCurrentYPos, H);
LineInternal(fPageMarginsPx.left, fPhysicalSizePx.x-fPageMarginsPx.right, false);
inc(fCurrentYPos, H*2);
end;
finally
RestoreSavedLayout;
end;
end;
procedure TGDIPages.DrawTextAt(s: SynUnicode; XPos: integer; const aLink: string;
CheckPageNumber,aLinkNoBorder: boolean);
var i: integer;
R: TRect;
Size: TSize;
begin
if (Self=nil) or (s='') then exit;
CheckYPos;
if CheckPageNumber then begin
i := pos(PAGENUMBER,LowerCaseU(s));
if i > 0 then begin
Delete(s,i,14);
Insert(UTF8ToSynUnicode(Int32ToUtf8(fVirtualPageNum)),s,i);
end;
end;
SetBkMode(fCanvas.Handle,TRANSPARENT);
Size := TextExtent(fCanvas,s);
R.Left := MmToPrinterPxX(XPos);
case falign of
taRight: dec(R.Left,Size.cx+1);
taCenter: dec(R.Left,Size.cx shr 1+1);
end;
R.Top := fCurrentYPos;
TextOut(fCanvas,R.Left,R.Top,s);
if not fInHeaderOrFooter then // copy as text on a new line
fCanvasText := fCanvasText+SynUnicodeToString(s)+#13#10;
if aLink<>'' then begin
R.Right := R.Left+Size.cx;
R.Bottom := R.Top+Size.cy;
AddLink(aLink,PrinterToMM(R),0,aLinkNoBorder);
end;
end;
procedure TGDIPages.DrawAngledTextAt(const s: SynUnicode; XPos, Angle: integer);
var
lf: TLogFont;
OldFontHdl,NewFontHdl: HFont;
begin
if (s='') or (Self=nil) then exit; // avoid GPF
CheckYPos;
XPos := MmToPrinterPxX(XPos);
SetBkMode(fCanvas.Handle,TRANSPARENT);
with fCanvas do begin
if GetObject(Font.Handle, SizeOf(lf), @lf) = 0 then exit;
lf.lfEscapement := Angle * 10;
lf.lfOrientation := Angle * 10;
lf.lfOutPrecision := OUT_TT_ONLY_PRECIS;
NewFontHdl := CreateFontIndirect(lf);
OldFontHdl := selectObject(handle,NewFontHdl);
end;
TextOut(fCanvas,XPos,fCurrentYPos,s);
selectObject(fCanvas.handle,OldFontHdl);
DeleteObject(NewFontHdl);
if not fInHeaderOrFooter then
fCanvasText := fCanvasText+s+#13#10; // copy as text on a new line
end;
function TGDIPages.MmToPrinter(const R: TRect): TRect;
begin
if Self=nil then begin
FillChar(result,sizeof(result),0);
exit; // avoid GPF
end;
result.left := MmToPrinterPxX(R.left);
result.top := MmToPrinterPxY(R.top);
result.right := MmToPrinterPxX(R.right);
result.bottom := MmToPrinterPxY(R.bottom);
end;
function TGDIPages.PrinterToMM(const R: TRect): TRect;
begin
if Self=nil then begin
FillChar(result,sizeof(result),0);
exit; // avoid GPF
end;
result.left := PrinterPxToMmX(R.left);
result.top := PrinterPxToMmY(R.top);
result.right := PrinterPxToMmX(R.right);
result.bottom := PrinterPxToMmY(R.bottom);
end;
procedure TGDIPages.DrawBox(left,top,right,bottom: integer);
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
left := MmToPrinterPxX(left);
top := MmToPrinterPxY(top);
right := MmToPrinterPxX(right);
bottom := MmToPrinterPxY(bottom);
with fCanvas do begin
Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size,8);
if fsBold in Self.Font.style then
Pen.Width := Pen.Width +1;
MoveTo(left,top);
LineTo(right,top);
LineTo(right,bottom);
LineTo(left,bottom);
LineTo(left,top);
end;
end;
procedure TGDIPages.DrawBoxFilled(left,top,right,bottom: integer; Color: TColor);
var SavedBrushColor: TColor;
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
left := MmToPrinterPxX(left);
top := MmToPrinterPxY(top);
right := MmToPrinterPxX(right);
bottom := MmToPrinterPxY(bottom);
with fCanvas do begin
Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size,8);
if fsBold in Self.Font.style then
Pen.Width := Pen.Width +1;
SavedBrushColor := Brush.Color;
brush.Color := Color;
rectangle(left,top,right,bottom);
Brush.Color := SavedBrushColor;
end;
end;
procedure TGDIPages.DrawBMP(rec: TRect; bmp: TBitmap);
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
PrintBitmap(fCanvas, MmToPrinter(rec), bmp);
end;
procedure TGDIPages.DrawBMP(bmp: TBitmap; bLeft, bWidth: integer; const Legend: string);
begin
DrawGraphic(bmp,bLeft,bWidth,Legend);
end;
procedure TGDIPages.DrawGraphic(graph: TGraphic; bLeft, bWidth: integer;
const Legend: SynUnicode);
var R: TRect;
H: Integer;
begin
if (self=nil) or (graph=nil) or graph.Empty then
exit; // avoid GPF
// compute position and draw bitmap
if bLeft=maxInt then // do center
bLeft := PrinterPxToMmX(fPageMarginsPx.Left+
(fPhysicalSizePx.x-fPageMarginsPx.Right-fPageMarginsPx.Left-MmToPrinterPxX(bWidth))shr 1) else
inc(bLeft,LeftMargin);
R.Left := bLeft;
R.Right := bLeft+bWidth;
R.Bottom := (graph.Height*bWidth) div graph.Width;
if Legend<>'' then
H := LineHeight else
H := 0;
if not HasSpaceFor(R.Bottom+H) then begin
NewPage;
DoHeader;
NewHalfLine;
end;
R.Top := CurrentYPos;
Inc(R.Bottom,R.Top);
if graph.InheritsFrom(TBitmap) then
DrawBMP(R,graph as TBitmap) else
if graph.InheritsFrom(TMetaFile) then
DrawMeta(R,graph as TMetaFile);
CurrentYPos := R.Bottom;
// draw optional caption bottom
if Legend<>'' then begin
SaveLayout;
TextAlign := taCenter;
Font.Style := [];
Font.Size := (Font.Size*3)shr 2; // smaller font for caption text
DrawTextW(Legend);
RestoreSavedLayout;
end else
NewHalfLine;
end;
procedure TGDIPages.DrawMeta(rec: TRect; meta: TMetafile);
var old: Integer;
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
rec := MmToPrinter(rec);
old := SaveDC(fCanvas.Handle); // ensure safe metafile embedding
PlayEnhMetaFile(fCanvas.Handle, meta.Handle, rec);
RestoreDC(fCanvas.Handle,old);
end;
procedure TGDIPages.DrawArrow(Point1, Point2: TPoint;
HeadSize: integer; SolidHead: boolean);
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
Point1.X := MmToPrinterPxX(Point1.X);
Point1.Y := MmToPrinterPxY(Point1.Y);
Point2.X := MmToPrinterPxX(Point2.X);
Point2.Y := MmToPrinterPxY(Point2.Y);
HeadSize := MmToPrinterPxX(max(HeadSize,0));
fCanvas.Pen.Width := MulDiv(fDefaultLineWidth,Self.Font.Size, 8);
DrawArrowInternal(fCanvas, Point1, Point2, HeadSize, SolidHead);
end;
procedure TGDIPages.DrawLine(doubleline: boolean);
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
LineInternal(fPageMarginsPx.left, fPhysicalSizePx.x-fPageMarginsPx.right, doubleline);
NewLine;
end;
procedure TGDIPages.DrawDashedLine;
var
Y: integer;
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
with fCanvas do
begin
Pen.Width := 1;
Pen.Style := psDash;
Y := fCurrentYPos + (GetLineHeight shr 1) - (Pen.Width shr 1);
MoveTo(fPageMarginsPx.left, Y);
LineTo(fPhysicalSizePx.x-fPageMarginsPx.right, Y);
Pen.Style := psSolid;
end;
NewLine;
end;
procedure TGDIPages.DrawColumnLine(ColIndex: integer; aAtTop: boolean;
aDoDoubleLine: boolean);
var Y: integer;
begin
if aAtTop then
Y := fCurrentYPos - 1 else
Y := fCurrentYPos + fLineHeight + 1;
with fColumns[ColIndex] do
LineInternal(Y, ColLeft, ColRight, aDoDoubleLine);
end;
procedure TGDIPages.NewLine;
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
inc(fCurrentYPos, GetLineHeight);
fForcedLeftOffset := -1;
// fCanvasText := fCanvasText+#13#10;
end;
procedure TGDIPages.NewHalfLine;
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
inc(fCurrentYPos, GetLineHeight shr 1);
// fCanvasText := fCanvasText+#13#10;
end;
procedure TGDIPages.NewLines(count: integer);
begin
if Self=nil then exit; // avoid GPF
CheckHeaderDone;
if count < 1 then exit;
inc(fCurrentYPos, GetLineHeight* count);
// fCanvasText := fCanvasText+#13#10;
end;
procedure TGDIPages.NewPage(ForceEndGroup: boolean);
begin
if Self=nil then exit; // avoid GPF
if ForceEndGroup then
EndGroup else
if Assigned(fGroupPage) then
raise Exception.Create('Cannot call NewPage within a group block.');
CheckHeaderDone;
NewPageInternal;
end;
procedure TGDIPages.NewPageIfAnyContent;
begin
if Self=nil then exit; // avoid GPF
if fHeaderDone then
NewPage;
end;
procedure TGDIPages.BeginGroup;
begin
if Self=nil then exit; // avoid GPF
if not fHeaderDone then exit; // i.e. haven't even started a page yet
if Assigned(fGroupPage) then
raise Exception.create('Group already started!');
if not GroupsMustBeOnSamePage then begin
// Group "light" implementation
if fHeaderDone and not HasSpaceForLines(20) then
NewPageInternal;
exit;
end;
//make sure there's room for at least 2 lines otherwise just start a new page
//(a group surely contains at least 2 lines )
if not HasSpaceForLines(2) then begin
NewPageInternal;
exit;
end;
fGroupVerticalSpace :=
fPhysicalSizePx.y - fCurrentYPos - fPageMarginsPx.bottom - fFooterHeight;
fColumnsUsedInGroup := false;
fColumnHeaderInGroup := false;
if Assigned(fCanvas) then
FreeAndNil(fCanvas);
fGroupPage := CreateMetaFile(fPhysicalSizePx.x,fGroupVerticalSpace + fPhysicalOffsetPx.Y);
fCanvas := CreateMetafileCanvas(fGroupPage);
fGroupVerticalPos := fCurrentYPos;
fCurrentYPos := 0;
fBeforeGroupText := fCanvasText;
fCanvasText := '';
end;
procedure TGDIPages.EndGroup;
begin
if Self=nil then exit; // avoid GPF
if not Assigned(fGroupPage) then
exit;
FreeAndNil(fCanvas); //closes fGroupPage canvas
fCanvas := CreateMetafileCanvas(fCurrentMetaFile);
fCanvas.Draw(0,0,fCurrentMetaFile); //re-draw the last page
fCanvas.Draw(0,fGroupVerticalPos,fGroupPage); //add the Group data
FreeAndNil(fGroupPage); //destroy Group metafile
inc(fCurrentYPos,fGroupVerticalPos);
fCanvasText := fBeforeGroupText+fCanvasText;
fBeforeGroupText := '';
end;
function TGDIPages.CurrentGroupPosStart: integer;
begin
if Self=nil then
result := 0 else begin
if Assigned(fGroupPage) then
result := fGroupVerticalPos else
result := fPageMarginsPx.top;
result := PrinterPXtoMmY(result);
end;
end;
function GetNextItemW(var P: PWideChar): SynUnicode;
var S: PWideChar;
begin
if P=nil then
result := '' else begin
S := P;
while (S^<>#0) and (S^<>',') do
inc(S);
SetString(result,P,S-P);
if S^<>#0 then
P := S+1 else
P := nil;
end;
end;
function GetNextItemS(var P: PChar): string;
var S: PChar;
begin
if P=nil then
result := '' else begin
S := P;
while (S^<>#0) and (S^<>',') do
inc(S);
SetString(result,P,S-P);
if S^<>#0 then
P := S+1 else
P := nil;
end;
end;
const // zoom percentages for popup menu entries
MenuZoom: array[0..6] of byte = (25,50,75,100,125,150,200);
procedure TGDIPages.EndDoc;
var PC: PChar;
i, n, aX: integer;
Men: TGdiPagePreviewButton;
M, Root: TMenuItem;
Page: TMetaFile;
s: string;
begin
if Self=nil then exit; // avoid GPF
fLinksCurrent := -1;
EndGroup;
DoFooter;
if Assigned(fCanvas) then
FreeAndNil(fCanvas);
n := length(fPages);
if (n>1) and not HeaderDone then begin
// cancel the last page if it hasn't been started ...
FreeAndNil(fCurrentMetaFile);
dec(n);
SetLength(fPages,n);
end else
FlushPageContent;
if (n>0) and (fPagesToFooterText<>'') then
// add 'Page #/#' caption at the specified position
for i := 0 to n-1 do begin
Page := CreateMetaFile(fPages[i].SizePx.X,fPages[i].SizePx.Y);
try
fCanvas := CreateMetafileCanvas(Page);
fCanvas.Draw(0,0,GetMetaFileForPage(i)); // re-draw the original page
s := format(fPagesToFooterText,[i+1,n]); // add 'Page #/#' caption
aX := fPagesToFooterAt.X;
if aX<0 then
aX := fPages[i].SizePx.X-fPages[i].MarginPx.Right;
SavedState := fPagesToFooterState;
if TextAlign=taRight then
dec(aX,fCanvas.TextWidth(s));
with fPages[i] do
fCanvas.TextOut(aX,SizePx.Y-MarginPx.bottom-fFooterHeight+
fFooterGap+fPagesToFooterAt.Y,s);
FreeAndNil(fCanvas);
SetMetaFileForPage(i,Page); // replace page content
finally
Page.Free;
end;
end;
// OK, all Metafile pages have now been created and added to Pages[]
if Assigned(fOnDocumentProducedEvent) then
fOnDocumentProducedEvent(Self); // notify report just generated
fCurrPreviewPage := 1;
if Assigned(fPreviewPageChangedEvent) then
fPreviewPageChangedEvent(Self); // notify page changed
Invalidate;
// update popup menu content
if PopupMenu=nil then // caller may have created a TPopupMenu instance
PopupMenu := PopupMenuClass.Create(Self) else
PopupMenu.Items.Clear;
PopupMenu.OnPopup := PopupMenuPopup;
PC := pointer(string(sReportPopupMenu1));
// 'Next,Previous,GotoPage,Zoom,Bookmarks,CopyasText,Print,PDF,Close,Pagefit,Pagewidth'
for Men := rNextPage to rClose do
NewPopupMenuItem(GetNextItemS(PC),-ord(Men)).Enabled :=
(Men<rPrint) or (Men=rClose) or
( (Men=rPrint) and fHasPrinterInstalled) or
( (Men=rExportPDF) {$ifdef USEPDFPRINTER}and fHasPDFPrinterInstalled{$endif});
PopupMenu.Items[ord(rClose)-1].Visible := false;
M := PopupMenu.Items[ord(rZoom)-1];
NewPopupMenuItem(GetNextItemS(PC),-1000-PAGE_FIT,M);
NewPopupMenuItem(GetNextItemS(PC),-1000-PAGE_WIDTH,M);
for i := 0 to high(MenuZoom) do
NewPopupMenuItem(format('%d %%',[MenuZoom[i]]),-1000-MenuZoom[i],M);
Root := PopupMenu.Items[ord(rBookmarks)-1];
if UseOutlines and (fOutline.Count>0) then begin
Root.Enabled := true;
M := Root;
for i := 0 to fOutline.Count-1 do
with TGDIPagereference(fOutline.Objects[i]) do begin
while (M<>Root) and (cardinal(-2000-M.Tag)<cardinal(fOutline.Count)) and
(Rect.Bottom<=TGDIPagereference(fOutline.Objects[-2000-M.Tag]).Rect.Bottom) do
M := M.Parent;
M := NewPopupMenuItem(fOutline[i],-2000-i,M);
end;
end else
Root.Enabled := false;
end;
function TGDIPages.PrintPages(PrintFrom, PrintTo: integer): boolean;
var i: integer;
rec: TRect;
CheckCurrentPtr: string;
UseStretchDraw: boolean;
BMP: TBitmap;
begin
result := false;
if Self=nil then exit; // avoid GPF
if not fHasPrinterInstalled then
raise Exception.Create('No printer driver is currently installed.');
if PrintFrom<0 then
with TPrintDialog.Create(nil) do
try
Options := [poPageNums];
MinPage := 1;
MaxPage := PageCount;
FromPage := 1;
ToPage := PageCount;
if not Execute then
exit;
PrintFrom := FromPage;
PrintTo := ToPage;
finally
Free;
end;
result := true;
// ideally, the user has changed printers BEFORE generating a report, but
// if they want a report sent to a different printer then use StretchDraw ...
CheckCurrentPtr := CurrentPrinterName;
if CheckCurrentPtr <> fCurrentPrinter then begin
GetPrinterParams; // also updates fCurrentPrinter
UseStretchDraw := true;
end else
UseStretchDraw := false;
PrintFrom := max(PrintFrom-1,0);
if PrintTo=0 then
PrintTo := high(fPages) else
PrintTo := min(PrintTo-1,high(fPages));
{$ifdef PRINTERNEW} // set enhanced TPrinterNew class color/BW or duplex mode
with PrinterNew do begin
if ForcePrintColorMode<>printColorDefault then begin
if (ForcePrintColorMode=printColor) and HasColorMode then
ColorMode := true else
if ForcePrintColorMode=printBW then
ColorMode := false;
end;
if ForcePrintDuplexMode<>printDuplexDefault then begin
if (ForcePrintDuplexMode=printDuplex) and HasDuplexMode then
DuplexMode := true else
if ForcePrintDuplexMode=printSimplex then
DuplexMode := false;
end;
{$else}
with Printer do begin
{$endif}
if Caption='' then
{$ifndef USEPDFPRINTER}
if ExportPDFApplication<>'' then
Title := ExportPDFApplication else
{$endif}
Title := Application.Title else
Title := Caption;
Orientation := Self.Orientation; // just in case fPrinter changed
BeginDoc;
try
Screen.Cursor := crHourGlass;
if ForcePrintAsBitmap then begin // very slow printing
BMP := TBitmap.Create;
try
BMP.Width := GetDeviceCaps(handle, PHYSICALWIDTH);
BMP.Height := GetDeviceCaps(handle, PHYSICALHEIGHT);
for i := PrintFrom to PrintTo do
with fPages[i] do begin
BMP.Canvas.StretchDraw(Rect(0,0,Bmp.Width,Bmp.Height),GetMetaFileForPage(i));
Canvas.Draw(-OffsetPx.x,-OffsetPx.y,BMP);
if i<PrintTo then
NewPage;
end;
finally
BMP.Free;
end;
end else
for i := PrintFrom to PrintTo do begin
// nb: the printer's page origin is fPhysicalOffsetPx so it's
// necessary to offset our rect by -OffsetPx ...
if ForceScreenResolution then begin
rec := Rect(0, 0, GetDeviceCaps(handle, PHYSICALWIDTH),
GetDeviceCaps(handle, PHYSICALHEIGHT));
OffsetRect(rec, -GetDeviceCaps(handle,PHYSICALOFFSETX),
-GetDeviceCaps(handle,PHYSICALOFFSETY));
Canvas.StretchDraw(rec,GetMetaFileForPage(i));
end else
with fPages[i] do
if UseStretchDraw then
Canvas.StretchDraw(Rect(-OffsetPx.x,-OffsetPx.y,
SizePx.x-OffsetPx.x, SizePx.y-OffsetPx.y),GetMetaFileForPage(i)) else
Canvas.Draw(-OffsetPx.x,-OffsetPx.y,GetMetaFileForPage(i));
if i<PrintTo then
NewPage;
end;
EndDoc;
finally
Screen.Cursor := crDefault;
end;
end;
end;
procedure TGDIPages.SetTabStops(const tabs: array of integer);
var i: integer;
begin
if Self=nil then exit; // avoid GPF
FillChar(fTab[0],MAXTABS*sizeof(fTab[0]),0);
fTabCount := min(high(tabs)+1,MAXTABS);
//ignore trailing 0 tabs in array ...
if (fTabCount > 0) then
while (fTabCount > 0) and (tabs[fTabCount-1] = 0) do
dec(fTabCount);
if (fTabCount > 1) then begin
if (tabs[0] <= 0) then
raise Exception.Create('Tabs stops must be greater than 0.');
fTab[0] := MmToPrinterPxX(tabs[0]);
for i := 1 to fTabCount -1 do
if tabs[i] > tabs[i-1] then
fTab[i] := MmToPrinterPxX(tabs[i]) else
raise Exception.Create('Tabs stops must be in ascending order');
end else
if fTabCount = 1 then begin
//if one tab set then use that tab as the interval for subsequent tabs
for i := 0 to MAXTABS-1 do
fTab[i] := MmToPrinterPxX((i+1)*tabs[0]);
fTabCount := MAXTABS;
end else begin
//if no tabs set then default to tabs every 20mm
for i := 0 to MAXTABS-1 do fTab[i] := MmToPrinterPxX((i+1)*20);
fTabCount := MAXTABS;
end;
end;
function TGDIPages.GetPageMargins: TRect;
begin
if Self=nil then
FillChar(result,sizeof(result),0) else
with result do begin
Left := PrinterPxToMmX(fPageMarginsPx.left);
Top := PrinterPxToMmY(fPageMarginsPx.top);
Right := PrinterPxToMmX(fPageMarginsPx.right);
Bottom := PrinterPxToMmY(fPageMarginsPx.bottom);
end;
end;
procedure TGDIPages.SetPageMargins(Rect: TRect);
begin
fPageMarginsPx := MmToPrinter(Rect);
if not fHeaderDone then
fCurrentYPos := fPageMarginsPx.top;
end;
function TGDIPages.GetLeftMargin: integer;
begin
if Self=nil then
result := 0 else
result := PrinterPxToMmX(fPageMarginsPx.left);
end;
procedure TGDIPages.SetLeftMargin(const Value: integer);
begin
if Self=nil then exit;
fPageMarginsPx.Left := MmToPrinterPxX(Value);
end;
function TGDIPages.GetPaperSize: TSize;
begin
if Self=nil then
FillChar(result,sizeof(result),0) else begin
result.cx := PrinterPxToMmX(fPhysicalSizePx.X);
result.cy := PrinterPxToMmY(fPhysicalSizePx.Y);
end;
end;
procedure TGDIPages.AddLineToHeader(doubleline: boolean);
begin
if Self=nil then exit; // avoid GPF
fHeaderLines.Add(THeaderFooter.Create(Self,doubleline));
end;
procedure TGDIPages.AddLineToFooter(doubleline: boolean);
begin
if Self=nil then exit; // avoid GPF
if fFooterLines.Count = 0 then
CalcFooterGap;
fFooterLines.Add(THeaderFooter.Create(Self,doubleline));
inc(fFooterHeight, GetLineHeight);
end;
procedure TGDIPages.AddTextToHeader(const s: SynUnicode);
begin
if Self<>nil then
fHeaderLines.Add(THeaderFooter.Create(Self,false,s,true));
end;
procedure TGDIPages.AddTextToHeaderAt(const s: SynUnicode; XPos: integer);
var Head: THeaderFooter;
begin
if Self=nil then exit; // avoid GPF
Head := THeaderFooter.Create(Self,false,s,true);
Head.State.Flags := Head.State.Flags or ((XPos+2) shl 16);
fHeaderLines.Add(Head);
end;
procedure TGDIPages.AddTextToFooter(const s: SynUnicode);
begin
if Self=nil then exit; // avoid GPF
if fFooterLines.Count = 0 then
CalcFooterGap;
fFooterLines.Add(THeaderFooter.Create(Self,false,s,true));
inc(fFooterHeight, GetLineHeight);
end;
procedure TGDIPages.AddTextToFooterAt(const s: SynUnicode; XPos: integer);
var Foot: THeaderFooter;
begin
if Self=nil then exit; // avoid GPF
//todo - can't print at 0mm from left edge so raise exception
if fFooterLines.Count = 0 then
CalcFooterGap;
Foot := THeaderFooter.Create(Self,false,s,true);
Foot.State.Flags := Foot.State.Flags or ((XPos+2) shl 16);
fFooterLines.Add(Foot);
end;
procedure TGDIPages.AddPagesToFooterAt(const PageText: string;
XPos,YPosMultiplier: integer);
begin
if fPagesToFooterText<>'' then
exit; // only add once
fPagesToFooterText := PageText;
if XPos<0 then
fPagesToFooterAt.X := -1 else
fPagesToFooterAt.X := MmToPrinterPxX(XPos);
fPagesToFooterAt.Y := fFooterHeight * YPosMultiplier;
fPagesToFooterState := SavedState;
end;
function TGDIPages.GetColumnCount: integer;
begin
if Self=nil then
result := 0 else
result := length(fColumns);
end;
function TGDIPages.GetColumnInfo(index: integer): TColRec;
begin
if Self=nil then begin
FillChar(result,sizeof(result),0);
exit;
end;
if cardinal(index)>=cardinal(Length(fColumns)) then
raise Exception.create('GetColumnInfo: index out of range');
with fColumns[index] do begin
result.ColLeft := PrinterPxToMmX(ColLeft);
result.ColRight := PrinterPxToMmX(ColRight);
result.ColAlign := ColAlign;
result.ColBold := ColBold;
end;
end;
procedure TGDIPages.SetColumnAlign(index: integer; align: TColAlign);
begin
if Self=nil then exit; // avoid GPF
if cardinal(index)>=cardinal(Length(fColumns)) then
raise Exception.create('SetColumnAlign: index out of range') else
fColumns[index].ColAlign := align;
end;
procedure TGDIPages.SetColumnBold(index: integer);
begin
if Self=nil then exit; // avoid GPF
if cardinal(index)>=cardinal(Length(fColumns)) then
raise Exception.create('SetColumnAlign: index out of range') else
fColumns[index].ColBold := true;
end;
procedure TGDIPages.AddColumn(left, right: integer; align: TColAlign; bold: boolean);
var n: integer;
begin
if Self=nil then exit; // avoid GPF
left := MmToPrinterPxX(left);
right := MmToPrinterPxX(right);
n := length(fColumns);
if (n>0) and (left<fColumns[n-1].ColRight) then
raise Exception.create('Columns overlap!');
SetLength(fColumns,n+1);
with fColumns[n] do begin
ColLeft := left;
ColRight := right;
ColAlign := align;
ColBold := bold;
end;
end;
procedure TGDIPages.AddColumns(const PercentWidth: array of integer; align: TColAlign);
var i, sum, left, right, ww, n: integer;
begin
if Self=nil then exit; // avoid GPF
ClearColumns;
sum := 0;
for i := 0 to high(PercentWidth) do
inc(sum,abs(PercentWidth[i]));
if sum<=0 then
exit;
left := fPageMarginsPx.left;
ww := fPhysicalSizePx.x-left-fPageMarginsPx.right;
n := length(fColumns);
SetLength(fColumns,n+length(PercentWidth));
for i := 0 to high(PercentWidth) do begin
right := left+(abs(PercentWidth[i])*ww) div sum;
// manual adding (no mm conversion -> exact width)
with fColumns[i+n] do begin
ColLeft := left;
ColRight := right;
if PercentWidth[i]<0 then
ColAlign := caCenter else
ColAlign := align;
ColBold := false;
end;
left := right;
end;
end;
procedure TGDIPages.AddColumnHeaders(const headers: array of SynUnicode;
WithBottomGrayLine: boolean=false; BoldFont: boolean=false;
RowLineHeight: integer=0; flags: integer=0);
var n,i: integer;
begin
if Self=nil then exit; // avoid GPF
if flags=0 then begin
if BoldFont then
Font.Style := [fsBold];
flags := TextFormatsToFlags;
end;
n := length(fColumnHeaderList);
SetLength(fColumnHeaderList,n+1);
fColumnHeaderList[n].flags := flags;
SetLength(fColumnHeaderList[n].headers,Length(headers));
for i := 0 to high(headers) do
fColumnHeaderList[n].headers[i] := headers[i];
fColumnHeaderPrinted := false;
fColumnHeaderPrintedAtLeastOnce := false;
fColumnsWithBottomGrayLine := WithBottomGrayLine;
fColumnsRowLineHeight := RowLineHeight;
if BoldFont then
Font.Style := [];
end;
function CSVToArray(var CSV: PWideChar; n: integer): TSynUnicodeDynArray;
var i: integer;
begin
SetLength(result,n);
for i := 0 to n-1 do
result[i] := GetNextItemW(CSV);
end;
procedure TGDIPages.AddColumnHeadersFromCSV(var CSV: PWideChar;
WithBottomGrayLine, BoldFont: boolean; RowLineHeight: integer);
begin
if Self<>nil then // avoid GPF
AddColumnHeaders(CSVToArray(CSV,length(fColumns)),
WithBottomGrayLine,BoldFont,RowLineHeight);
end;
procedure TGDIPages.DrawTextAcrossColsFromCSV(var CSV: PWideChar; BackgroundColor: TColor=clNone);
begin
if Self<>nil then // avoid GPF
DrawTextAcrossCols(CSVToArray(CSV,length(fColumns)),[],BackgroundColor);
end;
/// round inverted color to white or black
function clAlways(cl: TColor): TColor;
begin
if ((GetRValue(cardinal(cl)) * 2) +
(GetGValue(cardinal(cl)) * 3) +
(GetBValue(cardinal(cl)) * 2)) < 600 then
result := clWhite else
result := clBlack;
end;
procedure TGDIPages.DrawTextAcrossCols(const StringArray: array of SynUnicode;
BackgroundColor: TColor);
begin
DrawTextAcrossCols(StringArray,[],BackgroundColor);
end;
procedure TGDIPages.DrawTextAcrossCols(const StringArray, LinkArray: array of SynUnicode;
BackgroundColor: TColor);
function HasCRLF(const s: SynUnicode): boolean;
var i: integer;
begin
result := true;
for i := 0 to length(s)-1 do
if s[i+1]<' ' then
exit;
result := false;
end;
function WrapText(s: SynUnicode; MaxWidth: integer; Lines: PSynUnicodeDynArray): integer;
var j,k,sp: integer;
begin
result := 0; // returns the line count
if Lines<>nil then
SetLength(Lines^,0);
repeat
if HasCRLF(s) or (TextWidthC(fCanvas,s)>MaxWidth) then begin
j := 1;
k := 1;
sp := 0;
while (j<length(s)) and (TextWidthC(fCanvas,copy(s,1,j))<MaxWidth) do begin
k := j; // store last fitting character index
if s[j]<=' ' then begin
sp := j; // mark space (=word delimiter) found
if s[j]<' ' then
break; // #13,#10 will force word wrap here = next line
end;
inc(j);
end;
if sp=0 then
sp := k; // if no space found, use character wrapping
end else
sp := length(s)+1;
if sp<=1 then
sp := 2;
if Lines<>nil then begin
SetLength(Lines^,length(Lines^)+1);
Lines^[high(Lines^)] := copy(s,1,sp-1);
end;
inc(result); // update lines count
s := trim(copy(s,sp,maxInt)); // trim ' ',#13,#10 for next line
until s='';
end;
var RowRect: TRect;
lh: integer;
max, i, j, k, c, H, ParenthW, LinesCount, X: integer;
s: SynUnicode;
line: string;
Lines: TSynUnicodeDynArray;
PW: PWideChar;
PWLen, Options: integer;
size: TSize;
r: TRect;
begin
if Self=nil then exit; // avoid GPF
max := high(fColumns);
if (max<0) or (length(StringArray)=0) then
exit; // no column defined
if High(StringArray)<max then
max := High(StringArray);
if max<0 then
exit; // nothing to draw
// check enough place for this column content on the page
lh := GetLineHeight;
CheckYPos;
LinesCount := 1; // by default, one line of text will be written
if WordWrapLeftCols then begin // check if stay on current page after word wrap
for j := 0 to max do
with fColumns[j] do
if (ColAlign=caLeft) and (ColRight>ColLeft) and
(HasCRLF(StringArray[j]) or
(TextWidthC(fCanvas,StringArray[j])>ColRight-ColLeft)) then begin
k := WrapText(StringArray[j],ColRight-ColLeft,nil); // calculate line counts
if k>LinesCount then
LinesCount := k; // calculate maximum line count
end;
if (LinesCount>1) and not HasSpaceForLines(LinesCount) then begin
NewPageInternal;
CheckHeaderDone;
end;
end;
if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
i := length(fColumnHeaderList) + 2;
if not HasSpaceForLines(i) then
NewPageInternal;
PrintColumnHeaders;
end;
// prepare column write
if Assigned(fGroupPage) then
fColumnsUsedInGroup := true;
ParenthW := fCanvas.TextWidth(')');
RowRect.Top := fCurrentYPos;
RowRect.Bottom := RowRect.Top+lh*LinesCount;
RowRect.Right := fColumns[max].ColRight;
if BackgroundColor<>clNone then
with fCanvas do begin
Brush.Style := bsSolid;
Brush.Color := BackgroundColor;
RowRect.Left := fColumns[0].ColLeft;
FillRect(RowRect);
Brush.Style := bsClear;
Font.Color := clAlways(BackgroundColor);
end;
// main loop, used to write column content
line := '';
for i := 0 to max do begin
s := StringArray[i];
line := line+SynUnicodeToString(s)+#9; // add column content + tab for report text
if s<>'' then
with fColumns[i], fCanvas do
if ColRight>ColLeft then begin
if ColBold then
Font.Style := Font.Style+[fsBold];
Options := ETO_CLIPPED or TextFlags; // unicode version of TextRect()
if Brush.Style <> bsClear then
Options := Options or ETO_OPAQUE;
InternalUnicodeString(s,PW,PWLen,@size);
if (ColAlign=caCenter) and (size.cx>ColRight-ColLeft) then
// overlapping centered -> draw right aligned
RowRect.Left := ColRight-size.cx-ParenthW else
case ColAlign of
caLeft: begin
RowRect.Left := ColLeft;
if WordWrapLeftCols and (ColRight>ColLeft) and
(HasCRLF(s) or (size.cx>ColRight-ColLeft)) then begin
// handle optional left aligned column content word wrap
WrapText(s,ColRight-ColLeft,@Lines); // word wrap s into Lines[]
dec(RowRect.Left,ParenthW);
for j := 0 to high(Lines) do begin
InternalUnicodeString(Lines[j],PW,PWLen,@size);
if BiDiMode=bdRightToLeft then
X := ColRight-size.cx-ParenthW else
X := ColLeft;
RowRect.Top := fCurrentYPos+lh*j;
ExtTextOutW(Handle,X,RowRect.Top,Options,@RowRect,PW,PWLen,nil);
end;
RowRect.Top := fCurrentYPos;
if ColBold then
Font.Style := Font.Style-[fsBold];
Continue; // text was written as word-wrap -> write next column
end else
if BiDiMode=bdRightToleft then
RowRect.Left := ColRight-size.cx-ParenthW;
end;
caCenter:
RowRect.Left := ColLeft+(ColRight-ColLeft-size.cx)shr 1;
caRight:
if BiDiMode=bdLeftToRight then
RowRect.Left := ColRight-size.cx-ParenthW;
caCurrency: begin
if fNegsToParenthesesInCurrCols then
InternalUnicodeString(ConvertNegsToParentheses(s),PW,PWLen,@size);
RowRect.Left := ColRight-size.cx-ParenthW;
// no bdRightToleft handling necessary for caCurrency
end;
end;
dec(RowRect.Left,ParenthW);
ExtTextOutW(Handle,RowRect.Left+ParenthW,fCurrentYPos,Options,@RowRect,PW,PWLen,nil);
if (i<length(LinkArray)) and (LinkArray[i]<>'') then begin
r.Left := PrinterPxToMmX(rowrect.Left);
r.Top := PrinterPxToMmX(rowrect.Top);
r.right := PrinterPxToMmX(rowrect.left+(rowrect.right-fColumns[0].ColLeft) div (max+1));
r.Bottom := PrinterPxToMmX(rowrect.Bottom);
AddLink(LinkArray[i],r);
end;
inc(RowRect.Left,size.cx+ParenthW);
if ColBold then
Font.Style := Font.Style-[fsBold];
end;
end;
if not fDrawTextAcrossColsDrawingHeader or
not fColumnHeaderPrintedAtLeastOnce then begin
line[length(line)] := #13; // overwrite last #9
line := line+#10;
fCanvasText := fCanvasText+line; // append columns content to report text
end;
if BackgroundColor<>clNone then
fCanvas.Font.Color := clBlack;
if not fDrawTextAcrossColsDrawingHeader and (fColumnsRowLineHeight>LinesCount) then
// custom space for Row before bottom gray line
LinesCount := fColumnsRowLineHeight;
for i := 2 to LinesCount do
NewLine;
if fColumnsWithBottomGrayLine and (RowRect.Right<>0) then begin
c := fCanvas.Pen.Color;
fCanvas.Pen.Color := clLtGray;
H := lh shr 1-(lh*15)shr 4;
dec(fCurrentYPos, H);
LineInternal(GetColumnRec(0).ColLeft,RowRect.Right,false);
inc(fCurrentYPos, H);
fCanvas.Pen.Color := c;
end;
NewLine;
end;
procedure TGDIPages.DrawLinesInCurrencyCols(doublelines: boolean);
var i: integer;
begin
if Self=nil then exit; // avoid GPF
CheckYPos;
if (fColumnHeaderList<>nil) and not fColumnHeaderPrinted then begin
i := length(fColumnHeaderList) + 2;
if not HasSpaceForLines(i) then
NewPageInternal;
PrintColumnHeaders;
end;
for i := 0 to high(fColumns) do
with fColumns[i] do
if ColAlign = caCurrency then
LineInternal(ColLeft, ColRight, doublelines);
NewLine;
end;
procedure TGDIPages.ColumnHeadersNeeded;
begin
if Self=nil then exit; // avoid GPF
fColumnHeaderPrinted := false;
end;
procedure TGDIPages.Clear;
procedure ClearObjects(List: TStringList);
var i: integer;
begin
for i := 0 to List.Count-1 do
List.Objects[i].Free;
List.Clear;
end;
begin
if Self=nil then exit; // avoid GPF
if Assigned(fCanvas) then
FreeAndNil(fCanvas);
if Assigned(fGroupPage) then
FreeAndNil(fGroupPage);
FreeAndNil(fCurrentMetaFile);
SetLength(fPages,0);
ClearObjects(fBookmarks);
ClearObjects(fLinks);
ClearObjects(fOutline);
ClearHeaders;
ClearFooters;
ClearColumns;
SetTabStops([20]);
fCanvasText := '';
fLinksCurrent := -1;
fSavedCount := 0;
end;
procedure TGDIPages.ClearHeaders;
begin
if Self=nil then exit; // avoid GPF
fHeaderLines.Clear;
end;
procedure TGDIPages.ClearFooters;
begin
if Self=nil then exit; // avoid GPF
fFooterLines.Clear;
fPagesToFooterText := '';
end;
procedure TGDIPages.ClearColumns;
begin
if Self=nil then exit; // avoid GPF
SetLength(fColumns,0);
ClearColumnHeaders;
end;
procedure TGDIPages.ClearColumnHeaders;
begin
if Self=nil then exit; // avoid GPF
fColumnHeaderList := nil;
end;
function TGDIPages.CreatePictureMetaFile(Width, Height: integer;
out MetaCanvas: TCanvas): TMetaFile;
begin
if Self=nil then
result := nil else begin
result := CreateMetaFile(MmToPrinterPxX(Width),MmToPrinterPxY(Height));
MetaCanvas := CreateMetafileCanvas(result);
end;
end;
procedure TGDIPages.DrawTextFmt(const s: string; const Args: array of const;
withNewLine: boolean);
begin
DrawText(format(s,Args),withNewLine);
end;
function TGDIPages.TitleFlags: integer;
begin
result := ((Font.Size*12) div 10) or FORMAT_BOLD or FORMAT_LEFT;
end;
function TGDIPages.TextWidth(const Text: SynUnicode): integer;
begin
if Self=nil then
result := 0 else begin
if fCanvas=nil then
result := TextWidthC(Canvas,Text) else
result := TextWidthC(fCanvas,Text);
result := PrinterPxToMmX(result);
end;
end;
procedure TGDIPages.ShowPreviewForm(VisibleButtons: TGdiPagePreviewButtons);
procedure CopyMenus(Source,Dest: TMenuItem);
var i: integer;
Sub: TMenuItem;
begin
for i := 0 to Source.Count-1 do
with Source.Items[i] do begin
Sub := TMenuItem.Create(PreviewForm);
Sub.Tag := Tag;
Sub.OnClick := OnClick;
Sub.Caption := Caption;
Dest.Add(Sub);
CopyMenus(Source.Items[i],Sub);
end;
end;
const PANELWIDTH = 128;
var OldParent: TWinControl;
i,y,W: integer;
M: TMenuItem;
LeftPanel: TPanel;
begin
if Self=nil then exit; // avoid GPF
PreviewForm := TForm.Create(nil);
try
PreviewForm.Position := poScreenCenter;
PreviewForm.Height := Screen.Height-64;
PreviewForm.Caption := Caption;
PreviewForm.Font.Name := 'Tahoma';
with PaperSize do begin
if cy=0 then
y := 1 else
y := cy;
PreviewForm.Width := (cx*PreviewForm.Height) div y+(64+PANELWIDTH);
end;
if PreviewForm.Width>Screen.WorkAreaWidth then
PreviewForm.WindowState := wsMaximized;
LeftPanel := TPanel.Create(PreviewForm);
LeftPanel.Parent := PreviewForm;
LeftPanel.Width := PANELWIDTH;
LeftPanel.Align := alLeft;
W := LeftPanel.ClientWidth-8;
PreviewPageCountLabel := TLabel.Create(PreviewForm);
PreviewPageCountLabel.Transparent := true;
PreviewPageCountLabel.Parent := LeftPanel;
PreviewPageCountLabel.SetBounds(4,24,W-4,24);
PreviewPageCountLabel.Alignment := Classes.taCenter;
PreviewPageCountLabel.AutoSize := false;
PreviewPageCountLabel.Caption := format(sPageN,[Page,PageCount]);
PopupMenuPopup(nil); // refresh PopupMenu.Items[]
SetLength(PreviewButtons,PopupMenu.Items.Count);
y := 48;
for i := 0 to High(PreviewButtons) do begin
M := PopupMenu.Items[i];
PreviewButtons[i] := TButton.Create(PreviewForm);
with PreviewButtons[i] do begin
Parent := LeftPanel;
SetBounds(4,y,W,32);
Enabled := M.Enabled;
Caption := M.Caption;
Tag := M.Tag;
OnClick := PopupMenuItemClick;
if M.Count>0 then begin
PopupMenu := PopupMenuClass.Create(PreviewForm);
CopyMenus(M,PopupMenu.Items);
end;
if TGdiPagePreviewButton(i+1) in VisibleButtons then
case TGdiPagePreviewButton(i+1) of
rPrint: begin
Height := 60;
inc(y,64);
Default := true;
end;
rClose, rNextPage, rPreviousPage: begin
Height := 48;
inc(y,52);
end;
rGotoPage, rZoom, rBookmarks, rExportPDF:
inc(y,48);
else
inc(y,36);
end else begin
M.Visible := false;
Visible := false;
end;
end;
end;
OldParent := Parent;
Parent := PreviewForm;
Align := alClient;
Zoom := PAGE_FIT;
try
PreviewForm.ActiveControl := self;
PreviewForm.ShowModal;
finally
Parent := OldParent;
end;
finally
FreeAndNil(PreviewForm);
Finalize(PreviewButtons);
end;
end;
function TGDIPages.GetRightMarginPos: integer;
begin
result := PrinterPxToMmX(fPhysicalSizePx.x-fPageMarginsPx.right);
end;
function TGDIPages.NewPopupMenuItem(const aCaption: string; Tag: integer;
SubMenu: TMenuItem; OnClick: TNotifyEvent; ImageIndex: integer): TMenuItem;
begin
if (Self=nil) or (PopupMenu=nil) then begin
result := nil;
exit;
end;
result := TMenuItem.Create(PopupMenu);
result.Caption := aCaption;
result.Tag := Tag;
if Assigned(OnClick) then
result.OnClick := OnClick else
result.OnClick := PopupMenuItemClick;
if ImageIndex>=0 then
result.ImageIndex := ImageIndex;
if SubMenu=nil then
PopupMenu.Items.Add(result) else
SubMenu.Add(result);
end;
procedure TGDIPages.PopupMenuItemClick(Sender: TObject);
var Comp: TComponent absolute Sender;
i: Integer;
begin
if not Sender.InheritsFrom(TComponent) then
exit;
if Assigned(OnPopupMenuClick) then
if (Comp.Tag=0) or (Comp.Tag>PageCount) then
OnPopupMenuClick(Sender); // only notify custom events
case -Comp.Tag of
ord(rNone):
exit;
ord(rNextPage):
Page := Page+1;
ord(rPreviousPage):
Page := Page-1;
ord(rPageAsText):
if Page>0 then
Clipboard.AsText := fPages[Page-1].Text;
ord(rPrint):
if PrintPages(-1,-1) then
if PreviewForm<>nil then
PreviewForm.Close;
ord(rExportPDF):
ExportPDF('',true);
ord(rClose):
if PreviewForm<>nil then
PreviewForm.Close;
ord(rGotoPage), ord(rZoom), ord(rBookmarks):
if Sender.InheritsFrom(TButton) and (PreviewButtons<>nil) then
with PreviewButtons[-1-Comp.Tag],
PreviewForm.ClientToScreen(Point(Left,Top+Height)) do
PopupMenu.Popup(X,Y);
991..1999: // allow -1000-PAGE_WIDTH
Zoom := -1000-Comp.Tag;
2000..4000: begin // allow -2000-OutlineIndex
i := -2000-Comp.Tag;
if cardinal(i)<cardinal(fOutline.Count) then
with TGDIPagereference(fOutline.Objects[i]) do
GotoPosition(Page,Rect.Top);
end;
else
if Cardinal(Comp.Tag)<=Cardinal(PageCount) then
Page := Comp.Tag;
end;
if PreviewForm<>nil then
SetFocus;
end;
procedure TGDIPages.InternalUnicodeString(const s: SynUnicode;
var PW: PWideChar; var PWLen: integer; size: PSize);
begin
if Assigned(OnStringToUnicode) then begin
fInternalUnicodeString := OnStringToUnicode(s);
PW := pointer(fInternalUnicodeString);
PWLen := length(fInternalUnicodeString);
end else begin
PW := pointer(s);
PWLen := length(s);
end;
if size<>nil then begin
size^.cx := 0;
size^.cy := 0;
GetTextExtentPoint32W(fCanvas.Handle,PW,PWLen,size^);
end;
end;
procedure TGDIPages.PopupMenuPopup(Sender: TObject);
var P: PChar;
PageFromTo, PageN: string;
M,M2: TMenuItem;
i,j,k: integer;
procedure AddPage(Menu: TMenuItem);
begin
NewPopupMenuItem(format(PageN,[i]),i,Menu).Enabled := i<>Page;
end;
begin
with PopupMenu.Items do
if Count=0 then
exit else
while Count>ord(rClose) do
Delete(ord(rClose)); // delete after "Close" entry
PopupMenu.Items[Ord(rNextPage)-1].Enabled := Page<PageCount;
PopupMenu.Items[Ord(rPreviousPage)-1].Enabled := Page>1;
M := PopupMenu.Items[Ord(rGoToPage)-1];
while M.Count>0 do
M.Delete(0);
M.Enabled := PageCount>1;
if PageCount>=1 then begin // add 'Go to Page' sub menus (group by 10 pages)
P := pointer(string(sReportPopupMenu2));
PageFromTo := GetNextItemS(P); // Pages %d to %d
PageN := GetNextItemS(P); // Page %d
if PageCount>10 then begin
for j := 0 to PageCount div 10 do begin
k := j*10+1;
if k>PageCount then
break;
M2 := NewPopupMenuItem(format(PageFromTo,[k,k+9]),-800,M);
// Tag=-800 -> no OnClick event triggered for this entry
for i := k to k+9 do
if i>PageCount then
break else
AddPage(M2);
end;
end else
for i := 1 to PageCount do
AddPage(M);
end;
if Assigned(OnPopupMenuPopup) then
OnPopupMenuPopup(Sender);
end;
{$ifndef USEPDFPRINTER}
function TGDIPages.ExportPDFStream(aDest: TStream): boolean;
var PDF: TPDFDocument;
BackgroundImage: TPdfImage;
page: TPdfPage;
i: integer;
begin
try
PDF := TPDFDocument.Create(UseOutlines,0,ExportPDFA1,
TPdfEncryption.New(ExportPDFEncryptionLevel,ExportPDFEncryptionUserPassword,
ExportPDFEncryptionOwnerPassword,ExportPDFEncryptionPermissions));
try
PDF.GeneratePDF15File := ExportPDFGeneratePDF15File;
//PDF.CompressionMethod := cmNone;
with PDF.Info do begin
Title := SysUtils.Trim(Caption);
if ExportPDFApplication='' then
Creator := trim(Application.Title) else
Creator := trim(ExportPDFApplication);
Author := ExportPDFAuthor;
Subject := ExportPDFSubject;
Keywords := ExportPDFKeywords;
end;
PDF.EmbeddedTTF := ExportPDFEmbeddedTTF;
{$ifndef NO_USE_UNISCRIBE}
PDF.UseUniscribe := ExportPDFUseUniscribe;
{$endif}
PDF.UseFontFallBack := ExportPDFUseFontFallBack;
if ExportPDFFontFallBackName<>'' then
PDF.FontFallBackName := ExportPDFFontFallBackName;
PDF.ForceJPEGCompression := ExportPDFForceJPEGCompression;
if ExportPDFBackground=nil then
BackgroundImage := nil else begin
BackgroundImage := TPdfImage.Create(PDF,ExportPDFBackground,true);
PDF.AddXObject('BackgroundImage',BackgroundImage);
end;
PDF.SaveToStreamDirectBegin(aDest);
for i := 0 to PageCount-1 do
with Pages[i] do begin
// this loop will do all the magic :)
PDF.DefaultPageWidth := PdfCoord(25.4*SizePx.X/fPrinterPxPerInch.x);
PDF.DefaultPageHeight := PdfCoord(25.4*SizePx.Y/fPrinterPxPerInch.y);
page := PDF.AddPage;
if BackgroundImage<>nil then
PDF.Canvas.DrawXObject(0,0,page.PageWidth,page.PageHeight,'BackgroundImage');
PDF.Canvas.RenderMetaFile(GetMetaFileForPage(i),
Screen.PixelsPerInch/fPrinterPxPerInch.x,Screen.PixelsPerInch/fPrinterPxPerInch.y);
PDF.SaveToStreamDirectPageFlush;
end;
PDF.SaveToStreamDirectEnd;
finally
PDF.Free;
end;
result := true;
except
result := false;
end;
end;
{$endif}
function TGDIPages.ExportPDF(aPdfFileName: TFileName; ShowErrorOnScreen: boolean;
LaunchAfter: boolean): boolean;
{$ifdef USEPDFPRINTER}
var DefaultPrinter: integer;
{$else}
function ValidFileName(const FN: TFileName): TFileName;
var i: integer;
begin
result := FN;
for i := length(result) downto 1 do
if ord(result[i]) in [ord('/'),ord(':'),ord('\'),ord('.')] then
delete(result,i,1);
i := length(Result);
while (i>0) and (ord(result[i]) in [ord(' '),ord('-')]) do dec(i);
SetLength(Result,i);
result := trim(result);
end;
var PDFFileName: TFileName;
PDFFile: TFileStream;
i: integer;
Name: string;
TempDir: TFileName;
{$endif}
begin
result := False;
if Self=nil then
exit;
if PageCount>10 then
Screen.Cursor := crHourGlass;
{$ifdef USEPDFPRINTER}
if HasPDFPrinterInstalled then begin
DefaultPrinter := Printer.PrinterIndex;
Printer.PrinterIndex := fPDFPrinterIndex;
PrintPages(0,0);
Printer.PrinterIndex := DefaultPrinter;
end;
{$else}
// use the Synopse PDF engine
if aPdfFileName='' then
with TSaveDialog.Create(nil) do
try
TempDir := GetCurrentDir;
Filter := sPDFFile+' (*.pdf)|*.pdf';
Title := Caption;
FileName := ValidFileName(Caption);
DefaultExt := 'pdf';
Options := [ofOverwritePrompt,ofHideReadOnly,ofEnableSizing];
repeat
if not Execute then
exit;
PDFFileName := FileName;
i := FileCreate(PDFFileName); // test file create (pdf not already opened)
if i>0 then break;
MessageBox(0,pointer(Format(SIniFileWriteError,[PDFFileName])),nil,MB_ICONERROR);
until false;
FileClose(i);
finally
SetCurrentDir(TempDir); // allow unplug e.g. any USB
Free;
end else
PDFFileName := aPdfFileName;
try
PDFFile := TFileStream.Create(PDFFileName,fmCreate);
try
ExportPDFStream(PDFFile);
finally
PDFFile.Free;
end;
if LaunchAfter then
ShellExecute(Application.MainForm.Handle,'open',Pointer(PDFFileName),
nil,nil,SW_NORMAL);
except
on E: Exception do begin // show any error raised during PDF creation
if ShowErrorOnScreen then
MessageBox(0,pointer(E.Message),Pointer(Name),MB_ICONERROR);
exit;
end;
end;
{$endif}
result := true;
if PageCount>10 then
Screen.Cursor := crDefault;
end;
procedure TGDIPages.WMEraseBkgnd(var Message: TWmEraseBkgnd);
var R: TRect;
begin
Message.Result := 1; // no erasing is necessary after this method call
if Message.DC=0 then exit;
// erase outside the preview surface
R.Left := 0;
R.Right := fPreviewSurface.left;
R.Top := 0;
R.Bottom := Height;
FillRect(Message.DC,R,Brush.Handle);
R.Left := R.Right+fPreviewSurface.Width;
R.Right := Width;
FillRect(Message.DC,R,Brush.Handle);
R.Left := 0;
R.Bottom := fPreviewSurface.Top;
FillRect(Message.DC,R,Brush.Handle);
R.Top := fPreviewSurface.Top+fPreviewSurface.Height;
R.Bottom := Height;
FillRect(Message.DC,R,Brush.Handle);
end;
procedure TGDIPages.AppendRichEdit(RichEditHandle: HWnd;
EndOfPagePositions: PIntegerDynArray);
var Range: TFormatRange;
LogX, LogY, LastChar, MaxLen, OldMap: integer;
TextLenEx: TGetTextLengthEx; // RichEdit 2.0 Window Class
begin
if (Self<>nil) and (fCanvas<>nil) then
with Range do begin
LogX := GetDeviceCaps(fCanvas.Handle, LOGPIXELSX);
LogY := GetDeviceCaps(fCanvas.Handle, LOGPIXELSY);
rcPage.Left := (fPageMarginsPx.Left*1440) div LogX;
rcPage.Right := ((fPhysicalSizePx.x-fPageMarginsPx.Right)*1440) div LogX;
rcPage.Top := ((fPageMarginsPx.Top+fHeaderHeight)*1440) div LogY;
rcPage.Bottom := ((fPhysicalSizePx.y-fPageMarginsPx.Bottom-fFooterHeight)*1440) div LogY;
CheckHeaderDone;
rc := rcPage;
rc.Top := (fCurrentYPos*1440) div LogY;
LastChar := 0;
TextLenEx.flags := GTL_DEFAULT;
TextLenEx.codepage := CP_ACP;
MaxLen := SendMessage(RichEditHandle, EM_GETTEXTLENGTHEX, PtrInt(@TextLenEx), 0);
chrg.cpMax := -1;
OldMap := SetMapMode(hdc, MM_TEXT);
try
SendMessage(RichEditHandle, EM_FORMATRANGE, 0, 0);
repeat
chrg.cpMin := LastChar;
hdc := fCanvas.Handle;
hdcTarget := hdc;
LastChar := SendMessage(RichEditHandle, EM_FORMATRANGE, 1, PtrInt(@Range));
if EndOfPagePositions<>nil then
AddInteger(EndOfPagePositions^,LastChar);
if cardinal(LastChar)>=cardinal(MaxLen) then
break;
NewPageInternal;
DoHeader;
rc := rcPage;
until false;
fCurrentYPos := (rc.Bottom*LogY) div 1440;
finally
SendMessage(RichEditHandle, EM_FORMATRANGE, 0, 0);
SetMapMode(hdc, OldMap);
end;
end;
end;
function TGDIPages.AddBookMark(const aBookmarkName: string; aYPosition: integer=0): boolean;
begin
if fBookmarks.IndexOf(aBookmarkName)>=0 then // avoid duplicate bookmarks
result := false else begin
if aYPosition=0 then begin
CheckYPos;
aYPosition := fCurrentYPos;
end;
fBookMarks.AddObject(aBookmarkName,
TGDIPagereference.Create(PageCount,0,aYPosition,0,0));
{$ifndef USEPDFPRINTER}
fCanvas.MoveTo(0,aYPosition);
GDICommentBookmark(fCanvas.Handle,StringToUTF8(aBookmarkName));
{$endif}
result := true;
end;
end;
procedure TGDIPages.GotoPosition(aPage: integer; aYPos: integer);
begin
Page := aPage;
HorzScrollbar.Position := 0;
VertScrollbar.Position := (aYPos*VertScrollbar.Range) div fPhysicalSizePx.y
end;
function TGDIPages.GotoBookmark(const aBookmarkName: string): Boolean;
var i: integer;
begin
i := fBookmarks.IndexOf(aBookmarkName);
result := i>=0;
if result then
with TGDIPagereference(fBookmarks.Objects[i]) do
GotoPosition(Page,Rect.Top);
end;
procedure TGDIPages.AddOutline(const aTitle: string; aLevel: Integer;
aYPosition: integer=0; aPageNumber: integer=0);
begin
if aPageNumber=0 then
aPageNumber := PageCount;
if aYPosition=0 then begin
CheckYPos;
aYPosition := fCurrentYPos;
end;
fOutline.AddObject(aTitle,
TGDIPagereference.Create(aPageNumber,0,aYPosition,0,aLevel));
{$ifndef USEPDFPRINTER}
fCanvas.MoveTo(0,aYPosition);
GDICommentOutline(fCanvas.Handle, StringToUtf8(aTitle),aLevel);
{$endif}
end;
procedure TGDIPages.AddLink(const aBookmarkName: string; aRect: TRect;
aPageNumber: integer; aNoBorder: boolean);
begin
if aPageNumber=0 then
aPageNumber := PageCount;
aRect := MmToPrinter(aRect);
with aRect do
fLinks.AddObject(aBookmarkName,
TGDIPagereference.Create(aPageNumber,Left,Top,Right,Bottom));
{$ifndef USEPDFPRINTER}
GDICommentLink(fCanvas.Handle,StringToUtf8(aBookmarkName),aRect,aNoBorder);
{$endif}
end;
{ TGDIPagereference }
constructor TGDIPagereference.Create(PageNumber: integer; Left, Top, Right,
Bottom: integer);
begin
inherited Create;
Page := PageNumber;
Rect.Left := Left;
Rect.Top := Top;
Rect.Right := Right;
Rect.Bottom := Bottom;
end;
procedure TGDIPagereference.ToPreview(Pages: TGDIPages);
var W,H: integer;
begin // do it for all pages (zoom is not reset between page shift)
if Page<>0 then
with Pages.fPreviewSurface do begin
W := Width-GRAY_MARGIN*2;
H := Height-GRAY_MARGIN*2;
Preview.Left := GRAY_MARGIN+(Rect.Left*W) div Pages.fPhysicalSizePx.x;
Preview.Right := GRAY_MARGIN+(Rect.Right*W) div Pages.fPhysicalSizePx.x;
Preview.Top := GRAY_MARGIN+(Rect.Top*H) div Pages.fPhysicalSizePx.y;
Preview.Bottom := GRAY_MARGIN+(Rect.Bottom*H) div Pages.fPhysicalSizePx.y;
end;
end;
{ THeaderFooter }
constructor THeaderFooter.Create(Report: TGDIPages; doubleline: boolean;
const aText: SynUnicode=''; IsText: boolean=false);
begin
Text := aText;
State := Report.SavedState;
if not IsText then
if doubleline then
State.Flags := State.Flags or FORMAT_DOUBLELINE else
State.Flags := State.Flags or FORMAT_SINGLELINE;
end;
{$ifdef RENDERPAGES}
{ TRenderPages }
procedure TRenderPages.Clear;
begin
inherited;
fRdrCol.Clear;
fFontCache.Clear;
end;
constructor TRenderPages.Create(AOwner: TComponent);
begin
inherited;
fRdr := TRenderBox.Create(self);
fRdrCol := TObjectList.Create;
fFontCache := TObjectList.Create;
end;
destructor TRenderPages.Destroy;
begin
inherited;
FreeAndNil(fRdrCol);
FreeAndNil(fRdr);
FreeAndNil(fFontCache);
end;
function TRenderPages.GetCurrentFontCacheIndex: integer;
var F: TFont;
begin
for result := 0 to fFontCache.Count-1 do
with TFont(fFontCache.List[result]) do
if (Color=Font.Color) and (Height=Font.Height) and (Style=Font.Style) and
(Name=Font.Name) then
exit;
F := TFont.Create;
F.Assign(Font);
result := fFontCache.Add(F);
end;
function TRenderPages.GetCurrentFontCacheIndexAndSelect: integer;
var H: HDC;
begin
result := GetCurrentFontCacheIndex;
H := Canvas.Handle;
with TFont(fFontCache[result]) do begin // same as TCanvas.CreateFont
SelectObject(H,Handle);
SetTextColor(H,ColorToRGB(Color));
if length(fFontCacheSpace)<fFontCache.Count then
SetLength(fFontCacheSpace,fFontCache.Count+20);
if fFontCacheSpace[result].cx=0 then
GetTextExtentPoint32W(H,' ',1,fFontCacheSpace[result]);
end;
end;
function TRenderPages.GetSavedRender: TSavedStateRender;
begin
with result do begin
FirstLineIndent := ParagraphFirstLineIndent;
Before := ParagraphBefore;
After := ParagraphAfter;
RightIndent := ParagraphRightIndent;
LeftIndent := ParagraphLeftIndent;
end;
end;
procedure TRenderPages.NewPageInternal;
begin
{ TODO : close any pending paragraph }
inherited;
end;
procedure TRenderPages.RdrParagraph;
begin
if ParagraphBefore<>0 then
CurrentYPos := CurrentYPos+ParagraphBefore;
Rdr.Flush(fPageMarginsPx.left,fCurrentYPos,false,0,False);
if ParagraphAfter<>0 then
CurrentYPos := CurrentYPos+ParagraphAfter;
end;
procedure TRenderPages.RdrPard;
var State: TSavedState;
begin
if self=nil then
exit;
fAlign := taLeft;
SetSavedRender(fDefaultStateRender);
State := SavedState;
if State.Flags<>fDefaultState.Flags then begin
State.Flags := fDefaultState.Flags;
SavedState := State; // will set FORMAT_RIGHT/CENTER/JUSTIFIED
end;
end;
procedure TRenderPages.RdrPardPlain;
begin
if self=nil then
exit;
if fDefaultState.FontName='' then
RdrPlain else
SavedState := fDefaultState;
SetSavedRender(fDefaultStateRender);
end;
procedure TRenderPages.RdrPlain;
var State: TSavedState;
begin
if self=nil then
exit;
if (fDefaultState.FontName='') or (fDefaultState.Flags=0) then begin
Font.Size := 12;
Font.Style := [];
Font.Color := clBlack;
end else begin
State := fDefaultState;
State.Flags :=
// void FORMAT_RIGHT/CENTER/JUSTIFIED
(State.Flags and not (FORMAT_RIGHT or FORMAT_CENTER or FORMAT_JUSTIFIED)) or
// keep current FORMAT_RIGHT/CENTER/JUSTIFIED
(TextFormatsToFlags and (FORMAT_RIGHT or FORMAT_CENTER or FORMAT_JUSTIFIED));
SavedState := State;
end;
end;
procedure TRenderPages.RdrSetCurrentStateAsDefault;
begin
fDefaultState := SavedState;
fDefaultStateRender := GetSavedRender;
end;
function TRenderPages.RdrTableBegin(const PercentWidth: array of integer): Boolean;
var i, sum, w: integer;
col: TRenderBox;
begin
result := (Self<>nil) and (fRdrCol.Count=0);
if not result then
exit;
sum := 0;
for i := 0 to high(PercentWidth) do
inc(sum,PercentWidth[i]);
if sum<=0 then begin
result := false;
exit;
end;
w := fPhysicalSizePx.x-fPageMarginsPx.Left-fPageMarginsPx.right;
for i := 0 to high(PercentWidth) do begin
col := TRenderBox.Create(self);
col.Width := (w*100)div sum;
fRdrCol.Add(col);
end;
end;
function TRenderPages.RdrTableColumn(aColumnIndex: Integer): TRenderBox;
begin
if (Self=nil) or (cardinal(aColumnIndex)>=cardinal(fRdrCol.Count-1)) then
result := nil else
result := TRenderBox(fRdrCol.List[aColumnIndex]);
end;
function TRenderPages.RdrTableEnd: Boolean;
begin
result := (Self<>nil) and (fRdrCol.Count>0);
if not result then
exit;
fRdrCol.Clear;
end;
procedure TRenderPages.RestoreSavedLayout;
begin
if Self=nil then exit; // avoid GPF
if fSavedCount>=length(fSavedRender) then
Setlength(fSavedRender,fSavedCount+20);
fSavedRender[fSavedCount] := GetSavedRender;
inherited;
end;
procedure TRenderPages.SaveLayout;
begin
if Self=nil then exit; // avoid GPF
if fSavedCount<=0 then
exit;
inherited;
SetSavedRender(fSavedRender[fSavedCount]);
end;
procedure TRenderPages.SetSavedRender(const State: TSavedStateRender);
begin
with State do begin
ParagraphFirstLineIndent := FirstLineIndent;
ParagraphBefore := Before;
ParagraphAfter := After;
ParagraphRightIndent := RightIndent;
ParagraphLeftIndent := LeftIndent;
end;
end;
{ TRenderBox }
procedure TRenderBox.AddText(const s: string);
var PW: PWideChar;
PWLen: integer;
begin
if (self=nil) or (Owner=nil) then
exit; // avoid GPF
// convert text to unicode and add to fText[] internal buffer
Owner.InternalUnicodeString(StringToSynUnicode(s),PW,PWLen,nil);
AddText(PW,PWLen);
end;
procedure TRenderBox.AddText(PW: PWideChar; PWLen: integer);
var PDBeg, PD: PWideChar;
aFontIndex, aFontSpaceWidth: integer;
begin
if (self=nil) or (Owner=nil) or (PWLen=0) then
exit; // avoid GPF
if PWLen+fTextLen>length(fText) then
SetLength(fText,length(fText)+PWLen+1024);
PD := @fText[fTextLen];
inc(fTextLen,PWLen);
// create associated word markers
aFontIndex := Owner.GetCurrentFontCacheIndexAndSelect;
aFontSpaceWidth := Owner.fFontCacheSpace[aFontIndex].cx;
repeat
PDBeg := PD;
while true do
case integer(PW^) of
0, 32: break;
1..31: if PD<>PDBeg then break else Inc(PW);
else begin
PD^ := PW^;
inc(PW);
inc(PD);
end;
end;
if fBoxCount>=Length(fBox) then
SetLength(fBox,fBoxCount+200);
with fBox[fBoxCount] do begin
TextOffset := PD-@fText[0];
TextLength := PD-PDBeg;
FontIndex := aFontIndex;
FontSpaceWidth := aFontSpaceWidth;
GetTextExtentPoint32W(Owner.Canvas.Handle,PDBeg,TextLength,Size);
SpaceAfterCount := 0;
while integer(PW^) in [1..32] do begin
PD^ := ' ';
inc(PW);
inc(PD);
inc(SpaceAfterCount);
end;
LinkNumber := fLinksBookMarkNameCurrent;
end;
inc(fBoxCount);
until PW^=#0;
end;
procedure TRenderBox.Clear;
begin
if Self=nil then
exit;
Finalize(fLinksBookMarkName);
Finalize(fBox);
fLayoutCount := 0;
fBoxCount := 0;
fTextLen := 0;
fHeight := 0;
fLinksBookMarkNameCurrent := 0;
end;
constructor TRenderBox.Create(Owner: TRenderPages);
begin
fOwner := Owner;
fBiDiMode := Owner.BiDiMode;
fOwnerFont := Owner.Font;
end;
/// format the already inserted text into the TRenderPages owner
// - this TRenderBox text content will be cleared at the end of this method
// - you don't have to call it usualy: use Owner.RdrParagraph instead
// - by default, will render top aligned to the X=Left/Y=Top position
// - for vertical alignment, specify an height in ForcedHeightBottomCentered
// then will be centered if ForcedAtBottom=false, or bottom aligned if true
// - if CurrentPageOnly is true, will only flush the content which will fit on
// the current page - then the fLayout[] array will contain remaining boxes; otherwise,
// this will flush all content to multiple pages
procedure TRenderBox.Flush(Left, Top: Integer; CurrentPageOnly: boolean;
ForcedHeightBottomCentered: Integer; ForcedAtBottom: boolean);
var H, Y, i, fitLayout: integer;
WillBreak: boolean;
begin
if (self=nil) or (Owner=nil) then
exit; // avoid GPF
H := GetHeight; // will populate fLayout[] from fBox[] if necessary
{ render on document Canvas }
WillBreak := false;
fitLayout := fLayoutCount-1;
for i := 0 to fitLayout do
if fLayout[i].Top>=H then begin
fitLayout := i-1;
WillBreak := true;
break;
end;
{ TODO : handle TGDIPagereference creation from fLayout[].LastBox.LinkNumber }
// reset internal TRenderBox content
Clear;
end;
function TRenderBox.GetHeight: integer;
begin
if self=nil then
result := 0 else begin
if fHeight=0 then
// need to recalculate the layout to refresh the resulting Height
InternalRender;
result := fHeight;
end;
end;
procedure TRenderBox.InternalRender;
var ndx, ndxFirst: integer;
X, Y, H, W, LineW: integer;
txt: PWideChar;
Box: PRenderBoxWord;
LineLayout, LineNdx: integer;
procedure AddLayout(DoLineFeed, LastLine: boolean);
var nspace, Adjust, i, j, aLeft, n: Integer;
align: TTextAlign;
TmpLayout: array of TRenderBoxLayout;
begin
if fLayoutCount>=length(fLayout) then
SetLength(fLayout,fLayoutCount+50);
with fLayout[fLayoutCount] do begin
Text := txt;
with Box^ do
txt := @fText[TextOffset+TextLength]; // txt^ points to ' ' after text
Length := txt-Text;
Left := X;
Top := Y;
Width := W;
LineIndex := LineNdx;
LastBox := Box;
BreakExtra := 0;
BreakCount := 0;
end;
if DoLineFeed then begin
// we must handle the line feed layout
Align := Owner.TextAlign;
Adjust := LineW-(X+W);
if (Adjust<=0) or
// force left align if wider than expected (i.e. overpass right margin)
(LastLine and (Align=taJustified)) then
// last line of justified paragraph is never justified
Align := taLeft;
if BiDiMode=bdRightToLeft then begin
case Align of
taLeft: Align := taRight;
taRight: Align := taLeft;
end;
n := fLayoutCount-LineLayout+1;
if n>1 then begin
// multi layouts: change logical to visual order for RTL languages
SetLength(TmpLayout,n);
Move(fLayout[LineLayout],TmpLayOut[0],n*sizeof(TmpLayOut[0]));
aLeft := fLayout[LineLayout].Left;
for i := 0 to n-1 do begin
move(TmpLayout[i],fLayout[fLayoutCount-i],sizeof(TmpLayOut[0]));
with fLayout[fLayoutCount-i] do begin
Left := aLeft;
Inc(aLeft,Width+LastBox^.FontSpaceWidth*LastBox^.SpaceAfterCount);
end;
end;
end;
end;
case Align of
taRight:
for i := LineLayout to fLayoutCount do
inc(fLayout[i].Left,Adjust);
taCenter: begin
Adjust := Adjust div 2;
for i := LineLayout to fLayoutCount do
inc(fLayout[i].Left,Adjust);
end;
taJustified:
if Adjust>0 then begin
// compute SetTextJustification() values and update Left position
aLeft := fLayout[LineLayout].Left;
nspace := 0;
for i := LineLayout to fLayoutCount do
with fLayout[i] do begin
for j := 0 to Length-1 do
if Text[j]=' ' then
inc(BreakCount);
inc(nspace,BreakCount);
end;
if nspace>0 then
for i := LineLayout to fLayoutCount do
with fLayout[i] do begin
Left := aLeft;
BreakExtra := (Adjust*BreakCount) div nspace;
dec(Width,LastBox^.FontSpaceWidth*BreakCount-BreakExtra);
inc(aLeft,Width);
end;
end;
end;
for i := LineLayout to fLayoutCount do
fLayout[i].Height := H; // same height for all fLayout[] of this line
with Owner do
X := MmToPrinterPxX(ParagraphLeftIndent);
inc(Y,H);
H := 0; // force recalculate line height
LineLayout := fLayoutCount;
inc(LineNdx);
end else begin
// just append this "word" box to fLayout[fLayoutCount]
with Box^ do
// compute next position
inc(X,W+FontSpaceWidth*SpaceAfterCount);
end;
inc(fLayoutCount);
ndxFirst := ndx+1;
W := 0;
end;
begin // compute TRenderBoxWord.X/Y and fHeight
fHeight := 0;
fLayoutCount := 0;
SetLength(fLayout,fBoxCount shr 2);
if fBoxCount=0 then
exit; // no text added
with Owner do begin
X := MmToPrinterPxX(ParagraphFirstLineIndent);
LineW := self.fWidth-MmToPrinterPxX(ParagraphRightIndent);
end;
LineNdx := 0;
Y := 0;
H := 0;
W := 0;
LineLayout := 0;
txt := @fText[0];
ndxFirst := 0;
for ndx := 0 to fBoxCount-1 do begin
Box := @fBox[ndx];
if Box^.Size.cy>H then
H := Box^.Size.cy;
inc(W,Box^.Size.cx);
if ndx=fBoxCount-1 then
// reached last box -> flush pending line content
AddLayout(true,true) else
with fBox[ndx+1] do
if X+W+Size.cx>LineW then
// not enough space in current line -> flush+adjust and go to next line
AddLayout(true,false) else
if (FontIndex<>Box^.FontIndex) or (LinkNumber<>Box^.LinkNumber) then
// text formatting or Link will change -> add a layout box
AddLayout(false,false);
end;
fHeight := Y;
end;
procedure TRenderBox.LinkBegin(const aBookmarkName: string);
begin
if (self=nil) or (Owner=nil) then
exit; // avoid GPF
LinkEnd; // no nested links
fLinksBookMarkNameCurrent := Length(fLinksBookMarkName)+1;
SetLength(fLinksBookMarkName,fLinksBookMarkNameCurrent);
fLinksBookMarkName[fLinksBookMarkNameCurrent-1] := aBookmarkName;
end;
function TRenderBox.LinkEnd: boolean;
begin
result := false;
if (self=nil) or (Owner=nil) or (fLinksBookMarkNameCurrent=0) then
exit; // avoid GPF
fLinksBookMarkNameCurrent := 0;
result := true;
end;
procedure TRenderBox.NewLine;
begin
if (self=nil) or (Owner=nil) then
exit; // avoid GPF
end;
procedure TRenderBox.Pard;
begin
if (self<>nil) and (Owner<>nil) then // avoid GPF
Owner.RdrPard;
end;
procedure TRenderBox.PardPlain;
begin
if (self<>nil) and (Owner<>nil) then // avoid GPF
Owner.RdrPardPlain;
end;
procedure TRenderBox.Plain;
begin
if (self<>nil) and (Owner<>nil) then // avoid GPF
Owner.RdrPlain;
end;
{$endif RENDERPAGES}
end.
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。