lazarus-ccr/components/gridprinter/source/gridprn.pas
2024-01-29 21:27:36 +00:00

2053 lines
62 KiB
ObjectPascal

unit GridPrn;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, LCLType, LCLIntf, LCLVersion, Types, Graphics, StdCtrls, Grids,
Printers, PrintersDlgs;
type
TGridPrinter = class; // forward declaration
TGridPrnDialog = (gpdNone, gpdPageSetup, gpdPrintDialog, gpdPrinterSetup);
TGridPrnPrintCellEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
ACanvas: TCanvas; ACol, ARow: Integer; ARect: TRect) of object;
TGridPrnGetCellTextEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
ACol, ARow: Integer; var AText: String) of object;
TGridPrnGetColCountEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
var AColCount: Integer) of object;
TGridPrnGetRowCountEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
var ARowCount: Integer) of object;
TGridPrnNewLineEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
ARow: Integer) of object;
TGridPrnLinePrintedEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
ARow, ALastCol: Integer) of object;
TGridPrnNewPageEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
APageNo: Integer; AStartCol, AStartRow, AEndCol, AEndRow: Integer) of object;
TGridPrnManualPageBreakEvent = procedure (Sender: TObject; AGrid: TCustomGrid;
IsCol: Boolean; AColRowIndex: Integer; var NewPage: Boolean) of object;
TGridPrnHeaderFooterSection = (hfsLeft, hfsCenter, hfsRight);
TGridPrnOption = (gpoCenterHor, gpoCenterVert,
gpoHorGridLines, gpoVertGridLines,
gpoFixedHorGridLines, gpoFixedVertGridLines,
gpoHeaderBorderLines, gpoOuterBorderLines
);
TGridPrnOptions = set of TGridPrnOption;
const
DEFAULT_GRIDPRNOPTIONS = [gpoHorGridLines, gpoVertGridLines,
gpoFixedHorGridLines, gpoFixedVertGridLines, gpoHeaderBorderLines,
gpoOuterBorderLines];
type
TGridPrnOrder = (poRowsFirst, poColsFirst);
TGridPrnOutputDevice = (odPrinter, odPreview);
TGridPrnScalingMode = (smManual, smFitToWidth, smFitToHeight, smFitAll);
TGridPrnMargins = class(TPersistent)
private
FMargins: array[0..5] of Double;
FOwner: TGridPrinter;
function GetMargin(AIndex: Integer): Double;
function IsStoredMargin(AIndex: Integer): Boolean;
procedure SetMargin(AIndex: Integer; AValue: Double);
protected
procedure Changed;
public
constructor Create(AOwner: TGridPrinter);
published
property Left: Double index 0 read GetMargin write SetMargin stored IsStoredMargin;
property Top: Double index 1 read GetMargin write SetMargin stored IsStoredMargin;
property Right: Double index 2 read GetMargin write SetMargin stored IsStoredMargin;
property Bottom: Double index 3 read GetMargin write SetMargin stored IsStoredMargin;
property Header: Double index 4 read GetMargin write SetMargin stored IsStoredMargin;
property Footer: Double index 5 read GetMargin write SetMargin stored IsStoredMargin;
end;
TGridPrnHeaderFooter = class(TPersistent)
private
FFont: TFont;
FFontSize: Integer;
FLineColor: TColor;
FLineWidth: Double;
FShowLine: Boolean;
FOwner: TGridPrinter;
FSectionSeparator: String;
FSectionText: array[TGridPrnHeaderFooterSection] of string;
FVisible: Boolean;
function GetProcessedText(AIndex: TGridPrnHeaderFooterSection): String;
function GetSectionText(AIndex: TGridPrnHeaderFooterSection): String;
function GetText: String;
function IsLineWidthStored: Boolean;
function IsSectionSepStored: Boolean;
function IsTextStored: Boolean;
procedure SetFont(AValue: TFont);
procedure SetLineColor(AValue: TColor);
procedure SetLineWidth(AValue: Double);
procedure SetSectionText(AIndex: TGridPrnHeaderFooterSection; AValue: String);
procedure SetShowLine(AValue: Boolean);
procedure SetText(AValue: String);
procedure SetVisible(AValue: Boolean);
protected
procedure Changed(Sender: TObject);
procedure DefineProperties(Filer: TFiler); override;
procedure ReadFontSize(Reader: TReader);
procedure WriteFontSize(Writer: TWriter);
public
constructor Create(AOwner: TGridPrinter);
destructor Destroy; override;
function IsShown: Boolean;
function IsTextEmpty: Boolean;
function RealLineColor: TColor;
function RealLineWidth: Integer;
property FontSize: Integer read FFontSize write FFontSize;
property ProcessedText[AIndex: TGridPrnHeaderFooterSection]: String read GetProcessedText;
property SectionText[AIndex: TGridPrnHeaderFooterSection]: String read GetSectionText;
published
property Font: TFont read FFont write SetFont;
property LineColor: TColor read FLineColor write SetLineColor default clDefault;
property LineWidth: Double read FLineWidth write SetLineWidth stored IsLineWidthStored;
property SectionSeparator: String read FSectionSeparator write FSectionSeparator stored IsSectionSepStored;
property ShowLine: Boolean read FShowLine write SetShowLine default true;
property Text: String read GetText write SetText stored IsTextStored;
property Visible: Boolean read FVisible write SetVisible default true;
end;
{ TGridPrinter }
TGridPrinter = class(TComponent)
private
FBorderLineColor: Integer;
FBorderLineWidth: Double;
FFixedLineColor: TColor;
FFixedLineWidth: Double;
FFromPage: Integer;
FGrid: TCustomGrid;
FGridLineColor: TColor;
FGridLineWidth: Double;
FHeader: TGridPrnHeaderFooter;
FFileName: String; // to be used by header/footer
FFooter: TGridPrnHeaderFooter;
FMargins: TGridPrnMargins;
FMonochrome: Boolean;
FOptions: TGridPrnOptions;
FPadding: Integer;
FPageHeight: Integer;
FPageWidth: Integer;
FPreviewPercent: Integer; // Scaling factor for preview bitmap
FPrintDateTime: TDateTime;
FPrintOrder: TGridPrnOrder;
FPrintScaleFactor: Double; // Scaling factor for printing
FPrintScaleToNumHorPages: Integer;
FPrintScaleToNumVertPages: Integer;
FPrintScalingMode: TGridPrnScalingMode;
FShowPrintDialog: TGridPrnDialog;
FToPage: Integer;
FOnAfterPrint: TNotifyEvent;
FOnBeforePrint: TNotifyEvent;
FOnGetCellText: TGridPrnGetCellTextEvent;
FOnGetColCount: TGridPrnGetColCountEvent;
FOnGetRowCount: TGridPrnGetRowCountEvent;
FOnLinePrinted: TGridPrnLinePrintedEvent;
FOnManualPageBreak: TGridPrnManualPageBreakEvent;
FOnNewLine: TGridPrnNewLineEvent;
FOnNewPage: TGridPrnNewPageEvent;
FOnPrepareCanvas: TOnPrepareCanvasEvent;
FOnPrintCell: TGridPrnPrintCellEvent;
FOnUpdatePreview: TNotifyEvent;
function GetBorderLineWidthHor: Integer;
function GetBorderLineWidthVert: Integer;
function GetCanvas: TCanvas;
function GetColWidth(AIndex: Integer): Double;
function GetFixedLineWidthHor: Integer;
function GetFixedLineWidthVert: Integer;
function GetGridLineWidthHor: Integer;
function GetGridLineWidthVert: Integer;
function GetOrientation: TPrinterOrientation;
function GetPageCount: Integer;
function GetPageNumber: Integer;
function GetRowHeight(AIndex: Integer): Double;
function IsBorderLineWidthStored: Boolean;
function IsFixedLineWidthStored: Boolean;
function IsGridLineWidthStored: Boolean;
function IsOrientationStored: Boolean;
function IsPrintScaleFactorStored: Boolean;
procedure SetBorderLineColor(AValue: TColor);
procedure SetBorderLineWidth(AValue: Double);
procedure SetFileName(AValue: String);
procedure SetFixedLineColor(AValue: TColor);
procedure SetFixedLineWidth(AValue: Double);
procedure SetGrid(AValue: TCustomGrid);
procedure SetGridLineColor(AValue: TColor);
procedure SetGridLineWidth(AValue: Double);
procedure SetOptions(AValue: TGridPrnOptions);
procedure SetOrientation(AValue: TPrinterOrientation);
protected
FFactorX: Double; // Multiply to convert screen to printer/preview pixels
FFactorY: Double;
FLeftMargin: Integer; // Scaled page margins
FTopMargin: Integer;
FRightMargin: Integer;
FBottomMargin: Integer;
FHeaderMargin: Integer;
FFooterMargin: Integer;
FColWidths: array of Double; // Array of scaled grid column widts
FRowHeights: array of Double; // Array of scaled grid row heights
FFixedColPos: Integer; // Scaled right end of the fixed cols
FFixedRowPos: Integer; // Scaled bottom end of the fixed rows
FOutputDevice: TGridPrnOutputDevice;
FPageBreakRows: array of Integer; // Indices of first row on new page
FPageBreakCols: array of Integer; // Indices of first columns on new page
FPageNumber: Integer;
FPageCount: Integer;
FPageRect: TRect; // Bounds of printable rectangle
FPixelsPerInchX: Integer;
FPixelsPerInchY: Integer;
FPreviewBitmap: TBitmap; // Bitmap to which the preview image is printed
FPreviewPage: Integer; // Page request for the preview bitmap
FColCount: Integer;
FRowCount: Integer;
FFixedCols: Integer;
FFixedRows: Integer;
FPrinting: Boolean;
procedure CalcFixedColPos(AStartCol, AEndCol: Integer; var ALeft, ARight: Integer);
procedure CalcFixedRowPos(AStartRow, AEndRow: Integer; var ATop, ABottom: Integer);
procedure DoLinePrinted(ARow, ALastCol: Integer); virtual;
function DoManualPageBreak(IsCol: Boolean; AColRow: Integer): Boolean; virtual;
procedure DoNewLine(ARow: Integer); virtual;
procedure DoNewPage(AStartCol, AStartRow, AEndCol, AEndRow: Integer); virtual;
procedure DoPrepareCanvas(ACol, ARow: Integer); virtual;
procedure DoPrintCell(ACanvas: TCanvas; ACol, ARow: Integer; ARect: TRect;
var Done: boolean); virtual;
procedure DoUpdatePreview; virtual;
procedure Execute(ACanvas: TCanvas);
function GetBrushColor(AColor: TColor): TColor;
function GetFontColor(AColor: TColor): TColor;
function GetPenColor(AColor: TCOlor): TColor;
procedure LayoutPageBreaks;
procedure Loaded; override;
procedure Measure(APageWidth, APageHeight, XDpi, YDpi: Integer);
procedure NewPage;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Prepare;
procedure PrepareCanvas(ACanvas: TCanvas; ACol, ARow: Integer); virtual;
procedure PrintByCols(ACanvas: TCanvas);
procedure PrintByRows(ACanvas: TCanvas);
procedure PrintCell(ACanvas: TCanvas; ACol, ARow: Integer; ARect: TRect); virtual;
procedure PrintCheckbox(ACanvas: TCanvas; {%H-}ACol, {%H-}ARow: Integer; ARect: TRect;
ACheckState: TCheckboxstate); virtual;
procedure PrintColHeaders(ACanvas: TCanvas; ACol1, ACol2, Y: Integer);
procedure PrintFooter(ACanvas: TCanvas);
procedure PrintHeader(ACanvas: TCanvas);
procedure PrintHeaderFooter(ACanvas: TCanvas; HF: TGridPrnHeaderFooter);
procedure PrintGridLines(ACanvas: TCanvas; AStartCol, AStartRow, AEndCol, AEndRow, XEnd, YEnd: Integer);
procedure PrintPage(ACanvas: TCanvas; AStartCol, AStartRow, AEndCol, AEndRow: Integer);
procedure PrintRowHeader(ACanvas: TCanvas; ARow: Integer; X, Y: Double);
procedure ScaleColWidths(AFactor: Double);
procedure ScaleRowHeights(AFactor: Double);
procedure SelectFont(ACanvas: TCanvas; AFont: TFont; AScaleFactor: Double = 1.0);
property OutputDevice: TGridPrnOutputDevice read FOutputDevice;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function CreatePreviewBitmap(APageNo, APercentage: Integer): TBitmap;
function GetCellText(ACol, ARow: Integer): String; virtual;
procedure Print;
procedure ScaleToPages(NumHor, NumVert: Integer);
function ScaleX(AValue: Integer): Integer; inline;
function ScaleY(AValue: Integer): Integer; inline;
procedure UpdatePreview;
property Canvas: TCanvas read GetCanvas;
property ColCount: Integer read FColCount;
property ColWidth[AIndex: Integer]: Double read GetColWidth;
property FooterMargin: Integer read FFooterMargin;
property HeaderMargin: Integer read FHeaderMargin;
property PageHeight: Integer read FPageHeight;
property PageWidth: Integer read FPageWidth;
property PageRect: TRect read FPageRect;
property PixelsPerInchX: Integer read FPixelsPerInchX;
property PixelsPerInchY: Integer read FPixelsPerInchY;
property Padding: Integer read FPadding;
property PageCount: Integer read GetPageCount;
property PageNumber: Integer read FPageNumber;
property PrintDateTime: TDateTime read FPrintDateTime;
property PrintScaleToNumHorPages: Integer read FPrintScaleToNumHorPages write FPrintScaleToNumHorPages;
property PrintScaleToNumVertPages: Integer read FPrintScaleToNumVertPages write FPrintScaleToNumVertPages;
property PrintScalingMode: TGridPrnScalingMode read FPrintScalingMode write FPrintScalingMode;
property RowCount: Integer read FRowCount;
property RowHeight[AIndex: Integer]: Double read GetRowHeight;
published
property Grid: TCustomGrid read FGrid write SetGrid;
property BorderLineColor: TColor read FBorderLineColor write SetBorderLineColor default clDefault;
property BorderLineWidth: Double read FBorderLineWidth write SetBorderLineWidth stored IsBorderLineWidthStored;
property FileName: String read FFileName write SetFileName;
property FixedLineColor: TColor read FFixedLineColor write SetFixedLineColor default clDefault;
property FixedLineWidth: Double read FFixedLineWidth write SetFixedLineWidth stored IsFixedLineWidthStored;
property Footer: TGridPrnHeaderFooter read FFooter write FFooter;
property FromPage: Integer read FFromPage write FFromPage default 0;
property GridLineColor: TColor read FGridLineColor write SetGridLineColor default clDefault;
property GridLineWidth: Double read FGridLineWidth write SetGridLineWidth stored IsGridLineWidthStored;
property Header: TGridPrnHeaderFooter read FHeader write FHeader;
property Margins: TGridPrnMargins read FMargins write FMargins;
property Monochrome: Boolean read FMonochrome write FMonochrome default false;
property Options: TGridPrnOptions read FOptions write SetOptions default DEFAULT_GRIDPRNOPTIONS;
property Orientation: TPrinterOrientation read GetOrientation write SetOrientation stored IsOrientationStored;
property PrintOrder: TGridPrnOrder read FPrintOrder write FPrintOrder default poRowsFirst;
property PrintScaleFactor: Double read FPrintScaleFactor write FPrintScaleFactor stored IsPrintScaleFactorStored;
property ShowPrintDialog: TGridPrnDialog read FShowPrintDialog write FShowPrintDialog default gpdNone;
property ToPage: Integer read FToPage write FToPage default 0;
property OnAfterPrint: TNotifyEvent read FOnAfterPrint write FOnAfterPrint;
property OnBeforePrint: TNotifyEvent read FOnBeforePrint write FOnBeforePrint;
property OnGetCellText: TGridPrnGetCellTextEvent read FOnGetCellText write FOnGetCellText;
property OnGetRowCount: TGridPrnGetRowCountEvent read FOnGetRowCount write FOnGetRowCount;
property OnGetColCount: TGridPrnGetColCountEvent read FOnGetColCount write FOnGetColCount;
property OnLinePrinted: TGridPrnLinePrintedEvent read FOnLinePrinted write FOnLinePrinted; // Finished printing a line
property OnManualPageBreak: TGridPrnManualPageBreakEvent read FOnManualPageBreak write FOnManualPageBreak;
property OnNewLine: TGridPrnNewLineEvent read FOnNewLine write FOnNewLine; // Started printing a new row of cells.
property OnNewPage: TGridPrnNewPageEvent read FOnNewPage write FOnNewPage; // Started printing a new page
property OnPrepareCanvas: TOnPrepareCanvasEvent read FOnPrepareCanvas write FOnPrepareCanvas;
property OnPrintCell: TGridPrnPrintCellEvent read FOnPrintCell write FOnPrintCell;
property OnUpdatePreview: TNotifyEvent read FOnUpdatePreview write FOnUpdatePreview;
end;
function mm2px(mm: Double; dpi: Integer): Integer;
function px2mm(px: Integer; dpi: Integer): Double;
implementation
uses
Dialogs, OSPrinters, Themes, Math;
type
TGridAccess = class(TCustomGrid);
const
INCH = 25.4; // 1" = 25.4 mm
DefaultTextStyle: TTextStyle = (
Alignment: taLeftJustify;
Layout: tlCenter;
SingleLine: true;
Clipping: true;
ExpandTabs: false;
ShowPrefix: false;
WordBreak: false;
Opaque: false;
SystemFont: false;
RightToLeft: false;
EndEllipsis: false
);
function IfThen(cond: Boolean; a, b: Integer): Integer;
begin
if cond then Result := a else Result := b;
end;
function IfThen(cond: Boolean; a, b: TColor): TColor;
begin
if cond then Result := a else Result := b;
end;
function DefaultFontSize(AFont: TFont): Integer;
var
fontData: TFontData;
begin
fontData := GetFontData(AFont.Reference.Handle);
Result := abs(fontData.Height) * 72 div ScreenInfo.PixelsPerInchY;
end;
procedure FixFontSize(AFont: TFont);
begin
if AFont.Size = 0 then
AFont.Size := DefaultFontSize(AFont);
end;
function mm2px(mm: Double; dpi: Integer): Integer;
begin
Result := round(mm/INCH * dpi);
end;
function px2mm(px: Integer; dpi: Integer): Double;
begin
Result := px * INCH / dpi;
end;
{ TGridPrnMargins }
constructor TGridPrnMargins.Create(AOwner: TGridPrinter);
var
i: Integer;
begin
inherited Create;
FOwner := AOwner;
for i := 0 to 3 do FMargins[i] := 20.0;
for i := 4 to 5 do FMargins[i] := 10.0;
end;
procedure TGridPrnMargins.Changed;
begin
if (FOwner <> nil) then
FOwner.UpdatePreview;
end;
function TGridPrnMargins.GetMargin(AIndex: Integer): Double;
begin
Result := FMargins[AIndex];
end;
function TGridPrnMargins.IsStoredMargin(AIndex: Integer): Boolean;
begin
case AIndex of
0..3: Result := FMargins[AIndex] <> 20.0;
4..5: Result := FMargins[AIndex] <> 10.0;
end;
end;
procedure TGridPrnMargins.SetMargin(AIndex: Integer; AValue: Double);
begin
if FMargins[AIndex] <> AValue then
begin
FMargins[AIndex] := AValue;
Changed;
end;
end;
{ TGridPrnHeaderFooter }
constructor TGridPrnHeaderFooter.Create(AOwner: TGridPrinter);
begin
inherited Create;
FOwner := AOwner;
FSectionSeparator := '|';
FFont := TFont.Create;
FixFontSize(FFont);
FFont.Size := FFont.Size - 1;
FFont.OnChange := @Changed;
FLineColor := clDefault;
FLineWidth := 0;
FShowLine := true;
FVisible := true;
end;
destructor TGridPrnHeaderFooter.Destroy;
begin
FFont.Free;
inherited;
end;
procedure TGridPrnHeaderFooter.Changed(Sender: TObject);
begin
if (FOwner <> nil) then
FOwner.UpdatePreview;
end;
{ Since TGridPrinter does not descend from TControl it does not react on
LCLScaling. The problem is that the header/footer font size does not
scale correctly because the PixelsPerInch are always applied without scaling
the height. A workaround is to store the FontSize separately so that it is
not affected by the changed PPI, and to apply it to the Font.Size in the
GridPrinter's Loaded procedure. }
procedure TGridPrnHeaderFooter.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('FontSize', @ReadFontSize, @WriteFontSize, true);
end;
function TGridPrnHeaderFooter.GetProcessedText(AIndex: TGridPrnHeaderFooterSection): String;
const
UNKNOWN = '<unknown>';
procedure Replace(AParam: String);
var
s: String;
begin
if FOwner <> nil then
case AParam of
'$PAGECOUNT': s := IntToStr(FOwner.PageCount);
'$PAGE': s := IntToStr(FOwner.PageNumber);
'$FULL_FILENAME': s := ExpandFileName(FOwner.FileName);
'$FILENAME': s := ExtractFileName(FOwner.FileName);
'$PATH': s := ExtractFilePath(ExpandFileName(FOwner.FileName));
end
else
s := UNKNOWN;
Result := StringReplace(Result, AParam, s, [rfReplaceAll, rfIgnoreCase]);
end;
begin
Result := FSectionText[AIndex];
Result := StringReplace(Result, '$DATE', DateToStr(FOwner.PrintDateTime), [rfReplaceAll, rfIgnoreCase]);
Result := StringReplace(Result, '$TIME', TimeToStr(FOwner.PrintDateTime), [rfReplaceAll, rfIgnoreCase]);
Replace('$PAGECOUNT');
Replace('$PAGE');
Replace('$FULL_FILENAME');
Replace('$FILENAME');
Replace('$PATH');
end;
function TGridPrnHeaderFooter.GetSectionText(AIndex: TGridPrnHeaderFooterSection): String;
begin
Result := FSectionText[AIndex];
end;
function TGridPrnHeaderFooter.GetText: String;
begin
Result :=
FSectionText[hfsLeft] + FSectionSeparator +
FSectionText[hfsCenter] + FSectionSeparator +
FSectionText[hfsRight];
end;
function TGridPrnHeaderFooter.IsLineWidthStored: Boolean;
begin
Result := FLineWidth > 0;
end;
function TGridPrnHeaderFooter.IsSectionSepStored: Boolean;
begin
Result := FSectionSeparator <> '|';
end;
function TGridPrnHeaderFooter.IsShown: Boolean;
begin
Result := FVisible and not IsTextEmpty;
end;
function TGridPrnHeaderFooter.IsTextEmpty: Boolean;
begin
Result :=
(FSectionText[hfsLeft] = '') and
(FSectionText[hfsCenter] = '') and
(FSectionText[hfsRight] = '');
end;
function TGridPrnHeaderFooter.IsTextStored: Boolean;
begin
Result := not IsTextEmpty;
end;
procedure TGridPrnHeaderFooter.ReadFontSize(Reader: TReader);
begin
FFontSize := Reader.ReadInteger;
end;
function TGridPrnHeaderFooter.RealLineColor: TColor;
begin
if ((FOwner <> nil) and FOwner.Monochrome) or (FLineColor = clDefault) then
Result := clBlack
else
Result := FLineColor;
end;
function TGridPrnHeaderFooter.RealLineWidth: Integer;
begin
if FLineWidth <= 0 then
Result := FOwner{%H-}.ScaleY(1)
else
Result := mm2px(FLineWidth/FOwner.PrintScaleFactor, FOwner.PixelsPerInchY);
end;
procedure TGridPrnHeaderFooter.SetFont(AValue: TFont);
begin
FFont.Assign(AValue);
Changed(nil);
end;
procedure TGridPrnHeaderFooter.SetLineColor(AValue: TColor);
begin
if FLineColor <> AValue then
begin
FLineColor := AValue;
Changed(nil);
end;
end;
procedure TGridPrnHeaderFooter.SetLineWidth(AValue: Double);
begin
if FLineWidth <> AValue then
begin
FLineWidth := AValue;
Changed(nil);
end;
end;
procedure TGridPrnHeaderFooter.SetSectionText(AIndex: TGridPrnHeaderFooterSection;
AValue: String);
begin
if FSectionText[AIndex] <> AValue then
begin
FSectionText[AIndex] := AValue;
Changed(nil);
end;
end;
procedure TGridPrnHeaderFooter.SetShowLine(AValue: Boolean);
begin
if FShowLine <> AValue then
begin
FShowLine := AValue;
Changed(nil);
end;
end;
procedure TGridPrnHeaderFooter.SetText(AValue: String);
var
sa: TStringArray;
begin
if GetText = AValue then
exit;
sa := AValue.Split([FSectionSeparator]);
if Length(sa) > 0 then FSectionText[hfsLeft] := sa[0] else FSectionText[hfsLeft] := '';
if Length(sa) > 1 then FSectionText[hfsCenter] := sa[1] else FSectionText[hfsCenter] := '';
if Length(sa) > 2 then FSectionText[hfsRight] := sa[2] else FSectionText[hfsRight] := '';
Changed(self);
end;
procedure TGridPrnHeaderFooter.SetVisible(AValue: Boolean);
begin
if FVisible <> AValue then
begin
FVisible := AValue;
Changed(self);
end;
end;
procedure TGridPrnHeaderFooter.WriteFontSize(Writer: TWriter);
begin
FFontSize := FFont.Size;
Writer.WriteInteger(FFontSize);
end;
{ TGridPrinter }
constructor TGridPrinter.Create(AOwner: TComponent);
begin
inherited;
FMargins := TGridPrnMargins.Create(Self);
FHeader := TGridPrnHeaderFooter.Create(Self);
FFooter := TGridPrnHeaderFooter.Create(Self);
FOptions := DEFAULT_GRIDPRNOPTIONS;
FPrintOrder := poRowsFirst;
FPrintScaleFactor := 1.0;
FPrintScaleToNumHorPages := 1;
FPrintScaleToNumVertPages := 1;
FBorderLineColor := clDefault;
FFixedLineColor := clDefault;
FGridLineColor := clDefault;
FBorderLineWidth := -1;
FFixedLineWidth := -1;
FGridLineWidth := -1;
end;
destructor TGridPrinter.Destroy;
begin
FHeader.Free;
FFooter.Free;
FMargins.Free;
inherited;
end;
{ Calculates the extent (in printer/preview pixels) of the fixed ccolumns
(left edge of first and right edge of last fixed column).
Takes care of the optional horizontal centering of the grid. }
procedure TGridPrinter.CalcFixedColPos(AStartCol, AEndCol: Integer;
var ALeft, ARight: Integer);
var
col: Integer;
w: Double;
fixedColsWidth: Integer;
begin
if (gpoCenterHor in FOptions) then
begin
// Total width of all fixed columns
fixedColsWidth := FFixedColPos - FLeftMargin;
w := fixedColsWidth;
for col := AStartCol to AEndCol do
w := w + FColWidths[col];
// w is total column width on this page
ALeft := FLeftMargin + round((FPageRect.Width - w) / 2);
ARight := ALeft + fixedColsWidth;
end else
begin
ALeft := FLeftMargin;
ARight := FFixedColPos;
end;
end;
{ Calculates the extent (in printer/preview pixels) of the fixed rows
(top edge of first and bottom edge of last fixed row).
Takes care of the optional vertical centering of the grid. }
procedure TGridPrinter.CalcFixedRowPos(AStartRow, AEndRow: Integer;
var ATop, ABottom: Integer);
var
row: Integer;
h: Double;
fixedRowsHeight: Integer;
begin
if (gpoCenterVert in FOptions) then
begin
// Total height of all fixed rows
fixedRowsheight := FFixedRowPos - FTopMargin;
h := fixedRowsHeight;
for row := AStartRow to AEndRow do
h := h + FRowHeights[row];
// h is total row height on this page
ATop := FTopMargin + round((FPageRect.Height - h) / 2);
ABottom := ATop + fixedRowsHeight;
end else
begin
ATop := FTopMargin;
ABottom := FFixedRowPos;
end;
end;
function TGridPrinter.CreatePreviewBitmap(APageNo, APercentage: Integer): TBitmap;
begin
if FGrid = nil then
begin
Result := nil;
exit;
end;
FOutputDevice := odPreview;
FPreviewPercent := APercentage;
FPreviewPage := APageNo; // out-of-range values are handled by Prepare
SetGrid(FGrid);
Prepare;
FPreviewBitmap := TBitmap.Create;
FPreviewBitmap.SetSize(FPageWidth, FPageHeight);
FPreviewBitmap.Canvas.Brush.Color := clWhite;
FPreviewBitmap.Canvas.FillRect(0, 0, FPageWidth, FPageHeight);
Execute(FPreviewBitmap.Canvas);
Result := FPreviewBitmap;
end;
procedure TGridPrinter.DoLinePrinted(ARow, ALastCol: Integer);
begin
if Assigned(FOnLinePrinted) then
FOnLinePrinted(Self, FGrid, ARow, ALastCol);
end;
function TGridPrinter.DoManualPageBreak(IsCol: Boolean; AColRow: Integer): Boolean;
begin
Result := false;
if Assigned(OnManualPageBreak) then
OnManualPageBreak(Self, FGrid, IsCol, AColRow, Result);
end;
procedure TGridPrinter.DoNewLine(ARow: Integer);
begin
if Assigned(FOnNewLine) then
FOnNewLine(Self, FGrid, ARow);
end;
procedure TGridPrinter.DoNewPage(AStartCol, AStartRow, AEndCol, AEndRow: Integer);
begin
if Assigned(FOnNewPage) then
FOnNewPage(Self, FGrid, FPageNumber, AStartCol, AStartRow, AEndCol, AEndRow);
end;
procedure TGridPrinter.DoPrepareCanvas(ACol, ARow: Integer);
begin
if Assigned(FOnPrepareCanvas) then
FOnPrepareCanvas(Self, ACol, ARow, []);
end;
procedure TGridPrinter.DoPrintCell(ACanvas: TCanvas; ACol, ARow: Integer;
ARect: TRect; var Done: Boolean);
begin
if Assigned(FOnPrintCell) then
begin
FOnPrintCell(Self, FGrid, ACanvas, ACol, ARow, ARect);
Done := true;
end else
Done := false;
end;
procedure TGridPrinter.DoUpdatePreview;
begin
if Assigned(FOnUpdatePreview) and (FOutputDevice = odPreview) then
FOnUpdatePreview(Self);
end;
procedure TGridPrinter.Execute(ACanvas: TCanvas);
begin
FPrinting := true;
if Assigned(FOnBeforePrint) then
FOnBeforePrint(Self);
case FPrintOrder of
poRowsFirst: PrintByRows(ACanvas);
poColsFirst: PrintByCols(ACanvas);
end;
if Assigned(FOnAfterPrint) then
FOnAfterPrint(Self);
FPrinting := false;
end;
function TGridPrinter.GetBorderLineWidthHor: Integer;
begin
if FBorderLineWidth < 0.0 then
Result := {%H-}ScaleY(2)
else
Result := mm2px(FBorderLineWidth, FPixelsPerInchY);
end;
function TGridPrinter.GetBorderLineWidthVert: Integer;
begin
if FBorderLineWidth < 0.0 then
Result := {%H-}ScaleX(2)
else
Result := mm2px(FBorderLineWidth, FPixelsPerInchX);
end;
// Returns a bright brush even in dark mode
function TGridPrinter.GetBrushColor(AColor: TColor): TColor;
begin
if (AColor = clDefault) or (AColor = clWindow) or FMonochrome then
Result := clWhite
else
Result := ColorToRGB(AColor);
end;
// Returns a dark pen, even in dark mode
function TGridPrinter.GetFontColor(AColor: TColor): TColor;
begin
if (AColor = clDefault) or (AColor = clWindowText) or FMonochrome then
Result := clBlack
else
Result := ColorToRGB(AColor);
end;
// Returns a dark font, even in dark mode
function TGridPrinter.GetPenColor(AColor: TCOlor): TColor;
begin
if (AColor = clDefault) or (AColor = clWindowText) or FMonochrome then
Result := clBlack
else
Result := ColorToRGB(AColor);
end;
function TGridPrinter.GetCanvas: TCanvas;
begin
if FPrinting then
case FOutputDevice of
odPrinter: Result := Printer.Canvas;
odPreview: Result := FPreviewBitmap.Canvas;
end
else
Result := nil;
end;
function TGridPrinter.GetColWidth(AIndex: Integer): Double;
begin
Result := FColWidths[AIndex];
end;
function TGridPrinter.GetCellText(ACol, ARow: Integer): String;
var
col: TGridColumn;
lGrid: TGridAccess;
begin
Result := '';
if FGrid = nil then
exit;
lGrid := TGridAccess(FGrid);
if (ACol = 0) and (FFixedCols > 0) and (ARow >= FFixedRows) and (goFixedRowNumbering in lGrid.Options) then
begin
Result := IntToStr(ARow - FFixedRows + 1);
exit;
end;
if lGrid.Columns.Enabled and (ACol >= FFixedCols) and (ARow = 0) and (FFixedRows > 0) then
begin
col := lGrid.Columns[ACol - FFixedCols];
Result := col.Title.Caption;
exit;
end;
if Assigned(FOnGetCellText) then
FOnGetCellText(self, FGrid, ACol, ARow, Result)
else
Result := lGrid.GetCells(Acol, ARow);
end;
function TGridPrinter.GetFixedLineWidthHor: Integer;
begin
if FFixedLineWidth < 0.0 then
Result := {%H-}ScaleY(TGridAccess(FGrid).GridLineWidth)
else
Result := mm2px(FFixedLineWidth, FPixelsPerInchY);
end;
function TGridPrinter.GetFixedLineWidthVert: Integer;
begin
if FFixedLineWidth < 0.0 then
Result := {%H-}ScaleX(TGridAccess(FGrid).GridLineWidth)
else
Result := mm2px(FFixedLineWidth, FPixelsPerInchX);
end;
function TGridPrinter.GetGridLineWidthHor: Integer;
begin
if FGridLineWidth < 0.0 then
Result := {%H-}ScaleY(TGridAccess(FGrid).GridLineWidth)
else
Result := mm2px(FGridLineWidth, FPixelsPerInchY);
end;
function TGridPrinter.GetGridLineWidthVert: Integer;
begin
if FGridLineWidth < 0.0 then
Result := {%H-}ScaleX(TGridAccess(FGrid).GridLineWidth)
else
Result := mm2px(FGridLineWidth, FPixelsPerInchX);
end;
function TGridPrinter.GetOrientation: TPrinterOrientation;
begin
Result := Printer.Orientation;
end;
function TGridPrinter.GetPageCount: Integer;
begin
if FPageCount = 0 then
Prepare;
Result := FPageCount;
end;
function TGridPrinter.GetPageNumber: Integer;
begin
if FPageNumber <= 0 then
Prepare;
Result := FPageNumber;
end;
function TGridPrinter.GetRowHeight(AIndex: Integer): Double;
begin
Result := FRowHeights[AIndex];
end;
function TGridPrinter.IsBorderLineWidthStored: Boolean;
begin
Result := FBorderLineWidth >= 0.0;
end;
function TGridPrinter.IsFixedLineWidthStored: Boolean;
begin
Result := FFixedLineWidth >= 0.0;
end;
function TGridPrinter.IsGridLineWidthStored: Boolean;
begin
Result := FGridLineWidth >= 0.0;
end;
function TGridPrinter.IsOrientationStored: Boolean;
begin
Result := GetOrientation <> poPortrait;
end;
function TGridPrinter.IsPrintScaleFactorStored: Boolean;
begin
Result := FPrintScaleFactor <> 1.0;
end;
{ Find the column and row indices before which page breaks are occuring.
Store them in the arrays FPageBreakCols and FPageBreakRows.
Note that the indices do not contain the fixed columns/rows. }
procedure TGridPrinter.LayoutPageBreaks;
var
col, row: Integer;
n: Integer;
totalWidth, totalHeight: Double;
begin
// Scanning horizontally --> get page break column indices
SetLength(FPageBreakCols, FColCount);
n := 0;
totalWidth := FFixedColPos;
FPageBreakCols[0] := FFixedCols;
for col := FFixedCols to FColCount-1 do
begin
totalWidth := totalWidth + FColWidths[col];
if ((totalWidth - FPageRect.Right) >= 1) or DoManualPageBreak(true, col) then // allow 1 pixel for rounding error
begin
inc(n);
FPageBreakCols[n] := col;
totalWidth := FFixedColPos + FColWidths[col];
end;
end;
SetLength(FPageBreakCols, n+1);
// Scanning vertically --> get page break row indices
SetLength(FPageBreakRows, FRowCount);
n := 0;
totalHeight := FFixedRowPos;
FPageBreakRows[0] := FFixedRows;
for row := FFixedRows to FRowCount-1 do
begin
totalHeight := totalHeight + FRowHeights[row];
if (totalHeight > FPageRect.Bottom) or DoManualPageBreak(false, row) then
begin
inc(n);
FPageBreakRows[n] := row;
totalHeight := FFixedRowPos + FRowHeights[row];
end;
end;
SetLength(FPageBreakRows, n+1);
FPageCount := Length(FPageBreakCols) * Length(FPageBreakRows);
end;
procedure TGridPrinter.Loaded;
begin
inherited;
// The next lines override the change of Font.Size because LCLScaling does
// not apply here.
FHeader.Font.Size := FHeader.FontSize;
FFooter.Font.Size := FFooter.FontSize;
end;
{ Converts length properties to the specified pixel density. }
procedure TGridPrinter.Measure(APageWidth, APageHeight, XDpi, YDpi: Integer);
begin
// Multiplication factor needed by ScaleX and ScaleY
FFactorX := XDpi / ScreenInfo.PixelsPerInchX * FPrintScaleFactor;
FFactorY := YDpi / ScreenInfo.PixelsPerInchY * FPrintScaleFactor;
// Margins in the new pixel density units.
FLeftMargin := mm2px(FMargins.Left, XDpi);
FTopMargin := mm2px(FMargins.Top, YDpi);
FRightMargin := mm2px(FMargins.Right, XDpi);
FBottomMargin := mm2px(FMargins.Bottom, YDpi);
FHeaderMargin := mm2px(FMargins.Header, YDpi);
FFooterMargin := mm2px(FMargins.Footer, YDpi);
FPageRect := Rect(FLeftMargin, FTopMargin, APageWidth - FRightMargin, APageHeight - FBottomMargin);
FPadding := {%H-}ScaleX(varCellPadding);
// Calculates column widths and row heights in the new pixel density units
ScaleColWidths(FFactorX);
ScaleRowHeights(FFactorY);
end;
procedure TGridPrinter.NewPage;
begin
if FOutputDevice = odPrinter then
Printer.NewPage;
end;
procedure TGridPrinter.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if Operation = opRemove then
begin
if AComponent = FGrid then
FGrid := nil;
end;
end;
procedure TGridPrinter.Prepare;
begin
// Calculate grid indices at which page breaks occur. Since the font size is
// an integer, the zoomed preview may have slightly different values - which
// is not desired. Therefore, we calculate this for the printer resolution.
Measure(Printer.PageWidth, Printer.PageHeight, Printer.XDPI, Printer.YDPI);
LayoutPagebreaks;
case FOutputDevice of
odPrinter:
begin
FPixelsPerInchX := Printer.XDPI;
FPixelsPerInchY := Printer.YDPI;
FPageWidth := Printer.PageWidth;
FPageHeight := Printer.PageHeight;
end;
odPreview:
begin
if FPreviewPercent = 0 then
exit;
FPixelsPerInchX := ScreenInfo.PixelsPerInchX * FPreviewPercent div 100;
FPixelsPerInchY := ScreenInfo.PixelsPerInchY * FPreviewPercent div 100;
FPageWidth := round(Printer.PageWidth * FPixelsPerInchX / Printer.XDPI);
FPageHeight := round(Printer.PageHeight * FPixelsPerInchY / Printer.YDPI);
// Recalculates page dimensions and col/row sizes, now based on
// the "real" ppi of the preview.
Measure(FPageWidth, FPageHeight, FPixelsPerInchX, FPixelsPerInchY);
end;
end;
// Stores the current date/time so that all pages have the same date/time
// in the header/footer.
FPrintDateTime := Now();
end;
procedure TGridPrinter.PrepareCanvas(ACanvas: TCanvas; ACol, ARow: Integer);
var
lGrid: TGridAccess;
col: TGridColumn;
textStyle: TTextStyle;
font: TFont;
begin
lGrid := TGridAccess(FGrid);
// Background color
ACanvas.Brush.Style := bsSolid;
if (ACol < FFixedCols) or (ARow < FFixedRows) then
ACanvas.Brush.Color := GetBrushColor(IfThen(lGrid.FixedColor = clBtnFace, $E0E0E0, lGrid.FixedColor))
else
if lGrid.Columns.Enabled and (ACol >= FFixedCols) then
begin
col := lGrid.Columns[ACol - FFixedCols];
ACanvas.Brush.Color := GetBrushColor(col.Color);
end else
begin
if Odd(ARow) then
ACanvas.Brush.Color := GetBrushColor(lGrid.Color)
else
ACanvas.Brush.Color := GetBrushColor(lGrid.AlternateColor);
end;
// Font
if lGrid.Columns.Enabled and (ACol >= FFixedCols) then
begin
col := lGrid.Columns[ACol - FFixedCols];
if (ARow < FFixedRows) then
font := col.Title.Font
else
font := col.Font;
SelectFont(ACanvas, font, FPrintScaleFactor);
ACanvas.Font.Color := GetFontColor(font.Color);
end else
begin
SelectFont(ACanvas, lGrid.Font, FPrintScaleFactor);
ACanvas.Font.Color := GetFontColor(lGrid.Font.Color);
end;
FixFontSize(ACanvas.Font);
// Text style
textStyle := DefaultTextStyle;
if lGrid.Columns.Enabled and (ACol >= FFixedCols) then
begin
col := lGrid.Columns[ACol - FFixedCols];
if (ARow < FFixedRows) then
begin
textStyle.Alignment := col.Title.Alignment;
textStyle.Layout := col.Title.Layout;
if col.Title.MultiLine then
begin
textStyle.Wordbreak := true;
textStyle.SingleLine := false;
textStyle.EndEllipsis := false;
end;
end else
begin
textStyle.Alignment := col.Alignment;
textStyle.Layout := col.Layout;
end;
end;
if (goCellEllipsis in lGrid.Options) then
textStyle.EndEllipsis := true;
ACanvas.TextStyle := textStyle;
// Fire the event OnPrepareCanvas
DoPrepareCanvas(ACol, ARow);
end;
procedure TGridPrinter.Print;
var
pageDlg: TPageSetupDialog;
printDlg: TPrintDialog;
prnSetupDlg: TPrinterSetupDialog;
begin
if FGrid = nil then
exit;
if Printer.Printers.Count = 0 then
raise EPrinter.Create('No printer defined.');
SetGrid(FGrid);
case FShowPrintDialog of
gpdNone:
;
gpdPageSetup:
begin
pageDlg := TPageSetupDialog.Create(nil);
try
pageDlg.Units := pmMillimeters;
pageDlg.MarginLeft := round(FMargins.Left*100);
pageDlg.MarginTop := round(FMargins.Top*100);
pageDlg.MarginRight := round(FMargins.Right*100);
pageDlg.MarginBottom := round(FMargins.Bottom*100);
if pageDlg.Execute then
begin
FMargins.FMargins[0] := pageDlg.MarginLeft*0.01;
FMargins.FMargins[1] := pageDlg.MarginTop*0.01;
FMargins.FMargins[2] := pageDlg.MarginRight*0.01;
FMargins.FMargins[3] := pageDlg.MarginBottom*0.01;
FFromPage := 0; // all pages
FToPage := 0;
end else
exit;
finally
pageDlg.Free;
end;
end;
gpdPrintDialog:
begin
printDlg := TPrintDialog.Create(nil);
try
printDlg.MinPage := 1;
printDlg.MaxPage := PageCount;
printDlg.Options := printDlg.Options + [poPageNums];
if printDlg.Execute then
begin
Printer.Copies := printDlg.Copies;
if printDlg.PrintRange = prAllPages then
begin
FFromPage := 0; // all pages
FToPage := 0;
end else
begin
FFromPage := printDlg.FromPage;
FToPage := printDlg.ToPage;
end;
end else
exit;
finally
printDlg.Free;
end;
end;
gpdPrinterSetup:
begin
prnSetupDlg := TPrinterSetupDialog.Create(nil);
try
if prnSetupDlg.Execute then
begin
//
end else
exit;
finally
prnSetupDlg.Free;
end;
end;
end;
FOutputDevice := odPrinter;
Prepare;
Printer.BeginDoc;
try
Execute(Printer.Canvas);
finally
Printer.EndDoc;
end;
end;
{ Advances first along rows when handling page-breaks. }
procedure TGridPrinter.PrintByCols(ACanvas: TCanvas);
var
vertPage, horPage: Integer;
col1, col2: Integer;
row1, row2, row: Integer;
firstPrintPage, lastPrintPage: Integer;
printThisPage: Boolean;
begin
firstPrintPage := IfThen((FFromPage < 1) or (FFromPage > FPageCount), 1, FFromPage);
lastPrintPage := IfThen((FToPage < 1) or (FToPage > FPageCount), FPageCount, FToPage);
SelectFont(ACanvas, FGrid.Font, FPrintScaleFactor);
FPageNumber := 1;
for horPage := 0 to High(FPageBreakCols) do
begin
col1 := FPageBreakCols[horPage];
if horPage < High(FPageBreakCols) then
col2 := FPageBreakCols[horPage+1] - 1
else
col2 := FColCount-1;
for vertPage := 0 to High(FPageBreakRows) do
begin
row1 := FPageBreakRows[vertPage];
if vertPage < High(FPageBreakRows) then
row2 := FPageBreakRows[vertPage+1] - 1
else
row2 := FRowCount-1;
// Print page beginning at col1/row1
case FOutputDevice of
odPrinter: // Render all requested pages
printThisPage := (FPageNumber >= firstPrintPage) and (FPageNumber <= lastPrintPage);
odPreview: // Preview can render only a single page
printThisPage := (FPageNumber = FPreviewPage);
else
raise Exception.Create('[TGridPrinter.PrintByCols] Unknown output device.');
end;
DoNewPage(col1, row1, col2, row2);
if printThisPage then
PrintPage(ACanvas, col1, row1, col2, row2)
else
for row := row1 to row2 do
DoLinePrinted(row, col2);
inc(FPageNumber);
end;
end;
end;
{ Advances first along columns when handling page-breaks. }
procedure TGridPrinter.PrintByRows(ACanvas: TCanvas);
var
vertPage, horPage: Integer;
col1, col2: Integer;
row1, row2, row: Integer;
firstPrintPage, lastPrintPage: Integer;
printThisPage: Boolean;
begin
firstPrintPage := IfThen((FFromPage < 1) or (FFromPage > FPageCount), 1, FFromPage);
lastPrintPage := IfThen((FToPage < 1) or (FToPage > FPageCount), FPageCount, FToPage);
SelectFont(ACanvas, FGrid.Font, FPrintScaleFactor);
FPageNumber := 1;
for vertPage := 0 to High(FPageBreakRows) do
begin
row1 := FPageBreakRows[vertPage];
if vertPage < High(FPageBreakRows) then
row2 := FPageBreakRows[vertPage+1] - 1
else
row2 := FRowCount-1;
for horPage := 0 to High(FPageBreakCols) do
begin
col1 := FPageBreakCols[horPage];
if horPage < High(FPageBreakCols) then
col2 := FPageBreakCols[horPage+1] - 1
else
col2 := FColCount-1;
// Print the page beginning at col1/row1
case FOutputDevice of
odPrinter: // Render all requested pages
printThisPage := (FPageNumber >= firstPrintPage) and (FPageNumber <= lastPrintPage);
odPreview: // Preview can render only a single page
printThisPage := (FPageNumber = FPreviewPage);
else
raise Exception.Create('[TGridPrinter.PrintByRows] Unknown output device.');
end;
DoNewPage(col1, row1, col2, row2);
if printThisPage then
PrintPage(ACanvas, col1, row1, col2, row2)
else
for row := row1 to row2 do
DoLinePrinted(row, col2);
inc(FPageNumber);
end;
end;
end;
{ Prints the cell at ACol/ARow. The cell will appear in the given rectangle. }
procedure TGridPrinter.PrintCell(ACanvas: TCanvas; ACol, ARow: Integer;
ARect: TRect);
var
s: String;
col: TGridColumn;
lGrid: TGridAccess;
checkedState: TCheckboxState;
done: Boolean = false;
begin
DoPrintCell(ACanvas, ACol, ARow, ARect, done);
if done then
exit;
lGrid := TGridAccess(FGrid);
PrepareCanvas(ACanvas, ACol, ARow);
if not FMonochrome then
ACanvas.FillRect(ARect);
s := GetCellText(ACol, ARow);
InflateRect(ARect, -FPadding, 0);
// Handle checkbox columns
if lGrid.Columns.Enabled and (ACol >= FFixedCols) and (ARow >= FFixedRows) then
begin
col := lGrid.Columns[ACol - FFixedCols];
if col.Buttonstyle = cbsCheckboxColumn
then begin
if s = col.ValueChecked then
checkedState := cbChecked
else
if s = col.ValueUnChecked then
checkedState := cbUnchecked
else
checkedState := cbGrayed;
PrintCheckbox(ACanvas, ACol, ARow, ARect, checkedState);
exit;
end;
end;
// Normal text output
ACanvas.TextRect(ARect, ARect.Left, ARect.Top, s);
end;
procedure TGridPrinter.PrintCheckbox(ACanvas: TCanvas; ACol, ARow: Integer;
ARect: TRect; ACheckState: TCheckboxstate);
const
arrtb:array[TCheckboxState] of TThemedButton =
(tbCheckBoxUncheckedNormal, tbCheckBoxCheckedNormal, tbCheckBoxMixedNormal);
var
details: TThemedElementDetails;
cSize: TSize;
R: TRect;
P: Array[0..2] of TPoint;
begin
// Determine size of checkbox
details := ThemeServices.GetElementDetails(arrtb[ACheckState]);
{$IF LCL_FullVersion >= 2030000}
cSize := ThemeServices.GetDetailSizeForPPI(Details, ScreenInfo.PixelsPerInchX);
{$ELSE}
cSize := ThemeServices.GetDetailSize(Details);
{$IFEND}
cSize.cx := {%H-}ScaleX(cSize.cx);
cSize.cy := {%H-}ScaleY(cSize.cy);
// Position the checkbox within the given rectangle, ARect.
case ACanvas.TextStyle.Alignment of
taLeftJustify: R.Left := ARect.Left + FPadding;
taCenter: R.Left := (ARect.Left + ARect.Right - cSize.cx) div 2;
taRightJustify: R.Left := ARect.Right - cSize.cx - FPadding;
end;
case ACanvas.TextStyle.Layout of
tlTop: R.Top := ARect.Top + FPadding;
tlCenter: R.Top := (ARect.Top + ARect.Bottom - cSize.cy) div 2;
tlBottom: R.Top := ARect.Bottom - cSize.cy - FPadding;
end;
R.BottomRight := Point(R.Left + cSize.cx, R.Top + cSize.cy);
// Prepare pen and brush
ACanvas.Pen.Width := ScaleX(1){%H-};
ACanvas.Pen.Color := clBlack;
ACanvas.Pen.Style := psSolid;
if ACheckState = cbGrayed then
ACanvas.Brush.Color := clSilver
else
ACanvas.Brush.Color := clWhite;
ACanvas.Brush.Style := bsSolid;
// Draw checkbox border (= unchecked state)
InflateRect(R, -ACanvas.Pen.Width div 2, -ACanvas.Pen.Width div 2);
ACanvas.Rectangle(R);
InflateRect(R, -ACanvas.Pen.Width div 2, -ACanvas.Pen.Width div 2);
// Draw checkmark if checked or grayed
if ACheckState in [cbChecked, cbGrayed] then
begin
if ACheckState = cbGrayed then ACanvas.Pen.Color := clGray;
ACanvas.Pen.Width := ScaleX(2){%H-};
P[0] := Point(R.Left + cSize.cx div 6, R.Top + cSize.cy div 2);
P[1] := Point(R.Left + cSize.cx div 3, R.Bottom - cSize.cy div 6);
P[2] := Point(R.Right - cSize.cx div 6, R.Top + cSize.cy div 6);
ACanvas.PolyLine(P);
end;
end;
{ Prints the column headers: at first the fixed column headers, then the
headers between ACol1 and ACol2. }
procedure TGridPrinter.PrintColHeaders(ACanvas: TCanvas; ACol1, ACol2, Y: Integer);
var
R: TRect;
col, row: Integer;
x, x2, y1, y2: Double;
fixedColsLeft: Integer = 0;
fixedColsRight: Integer = 0;
begin
CalcFixedColPos(ACol1, ACol2, fixedColsLeft, fixedColsRight);
x := fixedColsLeft;
y1 := Y;
for row := 0 to FFixedRows-1 do
begin
y2 := Y + FRowHeights[row];
for col := 0 to FFixedCols-1 do
begin
x2 := x + FColWidths[col];
R := Rect(round(x), round(y1), round(x2), round(y2));
PrintCell(ACanvas, col, row, R);
x := x2;
end;
for col := ACol1 to ACol2 do
begin
x2 := x + FColWidths[col];
R := Rect(round(x), round(y1), round(x2), round(y2));
PrintCell(ACanvas, col, row, R);
x := x2;
end;
y1 := y2;
end;
end;
procedure TGridPrinter.PrintFooter(ACanvas: TCanvas);
begin
PrintHeaderFooter(ACanvas, FFooter);
end;
procedure TGridPrinter.PrintGridLines(ACanvas: TCanvas;
AStartCol, AStartRow, AEndCol, AEndRow, XEnd, YEnd: Integer);
var
x, y: Double;
xr, yr: Integer; // x, y rounded to integer
col, row: Integer;
lGrid: TGridAccess;
fixedColsLeft: Integer = 0;
fixedColsRight: Integer = 0;
fixedRowsTop: Integer = 0;
fixedRowsBottom: Integer = 0;
begin
lGrid := TGridAccess(FGrid);
CalcFixedColPos(AStartCol, AEndCol, fixedColsLeft, fixedColsRight);
CalcFixedRowPos(AStartRow, AEndRow, fixedRowsTop, fixedRowsBottom);
// Print inner grid lines
ACanvas.Pen.EndCap := pecFlat;
ACanvas.Pen.Style := lGrid.GridLineStyle;
ACanvas.Pen.Color := GetPenColor(IfThen(FGridLineColor = clDefault, lGrid.GridLineColor, FGridLineColor));
// ... vertical fixed cell lines
if (goFixedVertLine in lGrid.Options) and (gpoFixedVertGridLines in FOptions) then
begin
ACanvas.Pen.Width := GetGridLineWidthVert;
col := 1;
x := fixedColsLeft;
while col < lGrid.FixedCols do
begin
x := x + FColWidths[col-1];
xr := round(x);
ACanvas.Line(xr, fixedRowsTop, xr, YEnd);
inc(col);
end;
col := AStartCol;
x := fixedColsRight;
xr := round(x);
while (xr < XEnd) and (col < lGrid.ColCount) do
begin
x := x + FColWidths[col];
xr := round(x);
ACanvas.Line(xr, fixedRowsTop, xr, fixedRowsBottom);
inc(col);
end;
end;
// ... vertical grid lines
if (goVertLine in lGrid.Options) and (gpoVertGridLines in FOptions) then
begin
ACanvas.Pen.Width := GetGridLineWidthVert;
col := AStartCol;
x := fixedColsRight;
xr := round(x);
while (xr < XEnd) and (col < FColCount) do
begin
x := x + FColWidths[col];
xr := round(x);
ACanvas.Line(xr, fixedRowsBottom, xr, YEnd);
inc(col);
end;
end;
// ... horizontal fixed cell lines
if (goFixedHorzLine in lGrid.Options) and (gpoFixedHorGridLines in FOptions) then
begin
ACanvas.Pen.Width := GetGridLineWidthHor;
row := 1;
y := fixedRowsTop;
yr := round(y);
while row < lGrid.FixedRows do
begin
y := y + FRowHeights[row];
yr := round(y);
ACanvas.Line(fixedColsLeft, yr, XEnd, yr);
inc(row);
end;
row := AStartRow;
y := fixedRowsBottom;
yr := round(y);
while (yr < YEnd) and (row < FRowCount) do
begin
y := y + FRowHeights[row];
yr := round(y);
ACanvas.Line(fixedColsLeft, yr, fixedColsRight, yr);
inc(row);
end;
end;
// ... horizontal grid lines
if (goHorzLine in lGrid.Options) and (gpoHorGridLines in FOptions) then
begin
ACanvas.Pen.Width := GetGridLineWidthHor;
row := AStartRow;
y := fixedRowsBottom;
yr := round(y);
while (yr < YEnd) and (row < FRowCount) do
begin
y := y + FRowHeights[row];
yr := round(y);
ACanvas.Line(fixedColsRight, yr, XEnd, yR);
inc(row);
end;
end;
// Print header border lines between fixed and normal cells
// ... horizontal
if gpoHeaderBorderLines in FOptions then
begin
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Color := GetPenColor(FFixedLineColor);
ACanvas.Pen.Width := GetFixedLineWidthHor;
ACanvas.Line(fixedColsLeft, fixedRowsBottom, XEnd, fixedRowsBottom);
// ... vertical
ACanvas.Pen.Width := GetFixedLineWidthVert;
ACanvas.Line(fixedColsRight, fixedRowsTop, fixedColsRight, YEnd);
end;
if gpoOuterBorderLines in FOptions then
begin
// Print outer border lines
ACanvas.Pen.EndCap := pecRound;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Color := GetPenColor(FBorderLineColor);
// ... horizontal
ACanvas.Pen.Width := GetBorderLineWidthHor;
ACanvas.Line(fixedColsLeft, fixedRowsTop, XEnd, fixedRowsTop);
ACanvas.Line(fixedColsLeft, YEnd, XEnd, YEnd);
// ... vertical
ACanvas.Pen.Width := GetBorderLineWidthVert;
ACanvas.Line(fixedColsLeft, fixedRowsTop, fixedColsLeft, YEnd);
ACanvas.Line(XEnd, fixedRowsTop, XEnd, YEnd);
end;
end;
procedure TGridPrinter.PrintHeader(ACanvas: TCanvas);
begin
PrintHeaderFooter(ACanvas, FHeader);
end;
procedure TGridPrinter.PrintHeaderFooter(ACanvas: TCanvas;
HF: TGridPrnHeaderFooter);
var
Widths: array[TGridPrnHeaderFooterSection] of Integer = (0, 0, 0);
Heights: array[TGridPrnHeaderFooterSection] of Integer = (0, 0, 0);
Flags: array[TGridPrnHeaderFooterSection] of Integer;
TextRects: array[TGridPrnHeaderFooterSection] of TRect;
printableWidth: Integer;
hfs: TGridPrnHeaderFooterSection;
y: Integer;
s: String;
begin
if not HF.IsShown then
exit;
SelectFont(ACanvas, HF.Font, 1.0);
ACanvas.Font.Color := GetFontColor(HF.Font.Color);
printableWidth := FPageRect.Width;
if (HF.SectionText[hfsLeft] <> '') and (HF.SectionText[hfsCenter] = '') and (HF.SectionText[hfsRight] = '') then
Widths[hfsLeft] := printableWidth
else
if (HF.SectionText[hfsLeft] = '') and (HF.SectionText[hfsCenter] <> '') and (HF.SectionText[hfsRight] = '') then
Widths[hfsCenter] := printableWidth
else
if (HF.SectionText[hfsLeft] <> '') and (HF.SectionText[hfsCenter] = '') and (HF.SectionText[hfsRight] <> '') then
begin
Widths[hfsLeft] := printableWidth div 2;
Widths[hfsRight] := printableWidth div 2;
end else
if (HF.SectionText[hfsLeft] = '') and (HF.SectionText[hfsCenter] = '') and (HF.SectionText[hfsRight] <> '') then
Widths[hfsRight] := printableWidth
else begin
for hfs in TGridPrnHeaderFooterSection do
Widths[hfs] := printableWidth div 3;
end;
// Measure sections
if HF.SectionText[hfsLeft] <> '' then
begin
s := HF.ProcessedText[hfsLeft];
TextRects[hfsLeft] := Rect(0, 0, Widths[hfsLeft], 0);
Flags[hfsLeft] := DT_LEFT or DT_TOP or DT_WORDBREAK;
DrawText(ACanvas.Handle, PChar(s), Length(s), TextRects[hfsLeft], Flags[hfsLeft] or DT_CALCRECT);
Heights[hfsLeft] := TextRects[hfsLeft].Bottom;
end;
if HF.SectionText[hfsCenter] <> '' then
begin
s := HF.ProcessedText[hfsCenter];
TextRects[hfsCenter] := Rect(0, 0, Widths[hfsCenter], 0);
Flags[hfsCenter] := DT_CENTER or DT_TOP or DT_WORDBREAK;
DrawText(ACanvas.Handle, PChar(s), Length(s), TextRects[hfsCenter], Flags[hfsCenter] or DT_CALCRECT);
Heights[hfsCenter] := TextRects[hfsCenter].Bottom;
end;
if HF.SectionText[hfsRight] <> '' then
begin
s := HF.ProcessedText[hfsRight];
TextRects[hfsRight] := Rect(0, 0, Widths[hfsRight], 0);
Flags[hfsRight] := DT_RIGHT or DT_TOP or DT_WORDBREAK;
DrawText(ACanvas.Handle, PChar(s), Length(s), TextRects[hfsRight], Flags[hfsRight] or DT_CALCRECT);
Heights[hfsRight] := TextRects[hfsRight].Bottom;
end;
if HF = FHeader then
y := FHeaderMargin
else
y := FPageHeight - FFooterMargin - MaxValue(Heights);
// Prepare print rect
OffsetRect(TextRects[hfsLeft], FLeftMargin, y);
OffsetRect(TextRects[hfsCenter], (FPageRect.Left + FPageRect.Right - TextRects[hfsCenter].Right) div 2, y);
OffsetRect(TextRects[hfsRight], FPageRect.Right - TextRects[hfsRight].Width, y);
// Print header/footer text
for hfs in TGridPrnHeaderFooterSection do
if HF.SectionText[hfs] <> '' then
begin
s := HF.ProcessedText[hfs];
DrawText(ACanvas.Handle, PChar(s), Length(s), TextRects[hfs], Flags[hfs]);
end;
// Draw header/footer line
if FHeader.ShowLine then
begin
ACanvas.Pen.Color := FHeader.RealLineColor;
ACanvas.Pen.Width := FHeader.RealLineWidth;
ACanvas.Pen.Style := psSolid;
if HF = FHeader then
inc(y, {%H-}MaxValue(Heights) + (ACanvas.Pen.Width+1) div 2)
else
dec(y, (ACanvas.Pen.Width+1) div 2);
ACanvas.Line(FPageRect.Left, y, FPageRect.Right, y);
end;
end;
procedure TGridPrinter.PrintPage(ACanvas: TCanvas;
AStartCol, AStartRow, AEndCol, AEndRow: Integer);
var
x, y: Double;
x2, y2: Double;
row, col: Integer;
fixedColsLeft: Integer = 0;
fixedColsRight: Integer = 0;
fixedRowsTop: Integer = 0;
fixedRowsBottom: Integer = 0;
lastPagePrinted: Boolean;
R: TRect;
begin
CalcFixedColPos(AStartCol, AEndCol, fixedColsLeft, fixedColsRight);
CalcFixedRowPos(AStartRow, AEndRow, fixedRowsTop, fixedRowsBottom);
// Print column headers
PrintColHeaders(ACanvas, AStartCol, AEndCol, fixedRowsTop);
// Print grid cells
y := fixedRowsBottom;
for row := AStartRow to AEndRow do
begin
DoNewLine(row);
y2 := y + FRowHeights[row];
PrintRowHeader(ACanvas, row, fixedColsLeft, y);
x := fixedColsRight;
for col := AStartCol to AEndCol do
begin
x2 := x + FColWidths[col];
R := Rect(round(x), round(y), round(x2), round(y2));
PrintCell(ACanvas, col, row, R);
x := x2;
end;
DoLinePrinted(row, AEndCol);
y := y2;
end;
// Print cell grid lines
PrintGridLines(ACanvas, AStartCol, AStartRow, AEndCol, AEndRow, round(x2), round(y2));
// Print header and footer
PrintHeader(ACanvas);
PrintFooter(ACanvas);
// Unless we printed the last cell we must send a pagebreak to the printer.
lastPagePrinted := (AEndCol = FColCount-1) and (AEndRow = FRowCount-1);
if not lastPagePrinted then
NewPage;
end;
{ Prints the row headers of the specified row. Row headers are the cells in the
FixedCols of that row. The row is positioned at the given y coordinate on
the canvas. X is the position of the left edge of the grid. }
procedure TGridPrinter.PrintRowHeader(ACanvas: TCanvas; ARow: Integer;
X, Y: Double);
var
R: TRect;
col: Integer;
y1, y2: Integer;
x2: Double;
begin
y1 := round(Y); // upper edge of the row
y2 := round(Y + FRowHeights[ARow]); // lower edge of the row
for col := 0 to FFixedCols-1 do
begin
x2 := X + FColWidths[col];
R := Rect(round(X), y1, round(x2), y2);
PrintCell(ACanvas, col, ARow, R);
X := x2;
end;
end;
procedure TGridPrinter.ScaleColWidths(AFactor: Double);
var
i: Integer;
w: Double;
fixed: Double;
begin
fixed := FLeftMargin;
SetLength(FColWidths, FColCount);
for i := 0 to FColCount-1 do
begin
w := AFactor * TGridAccess(FGrid).ColWidths[i];
FColWidths[i] := w;
if i < FFixedCols then
fixed := fixed + w;
end;
FFixedColPos := round(fixed);
end;
procedure TGridPrinter.ScaleRowHeights(AFactor: Double);
var
i: Integer;
h: Double;
fixed: Double;
begin
fixed := FTopMargin;
SetLength(FRowHeights, FRowCount);
for i := 0 to FRowCount-1 do
begin
h := AFactor * TGridAccess(FGrid).RowHeights[i];
FRowHeights[i] := h;
if i < FFixedRows then
fixed := fixed + h;
end;
FFixedRowPos := round(fixed);
end;
procedure TGridPrinter.ScaleToPages(NumHor, NumVert: Integer);
var
i: Integer;
hFixed, wFixed: Double;
hTotal, wTotal: Double;
hFactor, wFactor: Double;
begin
if (FGrid = nil) or (Printer = nil) or (Printer.Printers.Count = 0) then
exit;
FPrintScaleFactor := 1.0;
Measure(Printer.PageWidth, Printer.PageHeight, Printer.XDPI, Printer.YDPI);
if NumHor > 0 then
begin
FPrintScaleToNumHorPages := NumHor;
wFixed := FFixedColPos - FLeftmargin;
wTotal := NumHor * wFixed;
for i := FFixedCols to FColCount-1 do
wTotal := wTotal + FColWidths[i];
wFactor := (NumHor * FPageRect.Width) / wTotal;
end else
begin
wFactor := 1.0;
FPrintScaleToNumHorPages := -1;
end;
if NumVert > 0 then
begin
FPrintScaleToNumVertPages := NumVert;
hFixed := FFixedRowPos - FTopMargin;
hTotal := NumVert * hFixed;
for i := FFixedRows to FRowCount-1 do
hTotal := hTotal + FRowHeights[i];
hFactor := (NumVert * FPageRect.Height) / hTotal;
end else
begin
hFactor := 1.0;
FPrintScaleToNumVertPages := -1;
end;
if (NumHor > 0) and (NumVert > 0) then
FPrintScalingMode := smFitAll
else if (NumHor > 0) then
FPrintScalingMode := smFitToWidth
else if (NumVert > 0) then
FPrintScalingMode := smFitToHeight
else
FPrintScalingMode := smManual;
FPrintScaleFactor := Min(wFactor, hFactor);
if FPrintScaleFactor > 1.0 then
FPrintScalefactor := 1.0; // do not magnify
end;
function TGridPrinter.ScaleX(AValue: Integer): Integer;
begin
Result := Round(FFactorX * AValue);
end;
function TGridPrinter.ScaleY(AValue: Integer): Integer;
begin
Result := Round(FFactorY * AValue);
end;
procedure TGridPrinter.SelectFont(ACanvas: TCanvas; AFont: TFont;
AScaleFactor: Double = 1.0);
var
fd: TFontData;
fontSize: Integer;
begin
ACanvas.Font.Assign(AFont);
ACanvas.Font.PixelsPerInch := FPixelsPerInchY;
if AFont.Size = 0 then
begin
fd := GetFontData(AFont.Reference.Handle);
fontSize := round(abs(fd.Height) * 72 / ScreenInfo.PixelsPerInchY * AScaleFactor);
end else
fontSize := round(ACanvas.Font.Size * AScaleFactor);
if fontSize < 3 then fontSize := 3;
ACanvas.Font.Size := fontSize;
end;
procedure TGridPrinter.SetBorderLineColor(AValue: TColor);
begin
if FBorderLineColor <> AValue then
begin
FBorderLineColor := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetBorderLineWidth(AValue: Double);
begin
if FBorderLineWidth <> AValue then
begin
FBorderLineWidth := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetFileName(AValue: String);
begin
if FFileName <> AValue then
begin
FFileName := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetFixedLineColor(AValue: TColor);
begin
if FFixedLineColor <> AValue then
begin
FFixedLineColor := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetFixedLineWidth(AValue: Double);
begin
if FFixedLineWidth <> AValue then
begin
FFixedLineWidth := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetGrid(AValue: TCustomGrid);
begin
FGrid := AValue;
if FGrid <> nil then
begin
FColCount := TGridAccess(FGrid).ColCount;
FRowCount := TGridAccess(FGrid).RowCount;
FFixedCols := TGridAccess(FGrid).FixedCols;
FFixedRows := TGridAccess(FGrid).FixedRows;
if Assigned(FOnGetColCount) then
FOnGetColCount(Self, FGrid, FColCount);
if Assigned(FOnGetRowCount) then
FOnGetRowCount(self, FGrid, FRowCount);
end else
begin
FColCount := 0;
FRowCount := 0;
FFixedCols := 0;
FFixedRows := 0;
end;
FPageNumber := 0;
FPageCount := 0;
end;
procedure TGridPrinter.SetGridLineColor(AValue: TColor);
begin
if FGridLineColor <> AValue then
begin
FGridLineColor := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetGridLineWidth(AValue: Double);
begin
if FGridLineWidth <> AValue then
begin
FGridLineWidth := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetOptions(AValue: TGridPrnOptions);
begin
if FOptions <> AValue then
begin
FOptions := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.SetOrientation(AValue: TPrinterOrientation);
begin
if GetOrientation <> AValue then
begin
Printer.Orientation := AValue;
UpdatePreview;
end;
end;
procedure TGridPrinter.UpdatePreview;
begin
if FOutputDevice = odPreview then
DoUpdatePreview;
end;
end.