unit KOLPrinters;
{* Replaces VCL TPrinter functionality.
|
Author : Bogusław Brandys,
|
|Version 1.4
|
|History :
|
| 17-09-2002 [+] Added property Assigned which should always be checked before first access
to TKOLPrinter. If is FALSE then there is no printer in system. (Warning: if You
assign incorrect info to Assign procedure this could lead Your application to
crash rather then return Assigned = FALSE)
|
[+] Changed Write to WriteLn and improved.Now always print a line of text with
carrage return #10#13 even there is no one at the end of text.Also should not break
word on bottom-right corner of page and working good when text does not fit on page
(NextPage invoked)
|
|
| 15-09-2002 [-] Fix access violation when there is no printer in system (caused
by DefPrinter function and Assign procedure).
|
|Example:
! with Printer^ do
! begin
! Assign(nil); //default printer (actually not needed as default printer is assigned on start)
! if not Assigned then begin
! MsgBox('There is no default printer in system!',mb_iconexclamation);
! Exit;
! end;
! Title := 'Printing test...';
! Canvas.Font.Assign(Memo1.Font);
! BeginDoc;
! for i:=0 to Memo1.Count-1 do WriteLn(Memo1.Items[i]); //or just WriteLn(Memo1.Text);
! EndDoc;
! end;
|
|One more note:
|
use psdWarning and pdWarning in PageSetup/Print dialogs to let
user know that there is no printer in system (or no default).
When these options are not used PrintDialog appear empty but PageSetup dialog never
appears.
|
Notes:
|
When output is redirected to a file and You want to know his name , check Output property
but always after sucessful Execute and before EndDoc (becouse EndDoc clears Output property)
Margins are supported but experimental (if You have time and paper please examine
if it working and let me know ;-) - especially if units for margins are properly computed.
Beside let me know what is still missing...
|
Still missing (I suppose):
|
- printing text as continuation of current printed line (in the middle of the line)
(this was a nightmare for me , if You know how to do it contact me)
|
- printing of selected pages only (must compute pages count)
|
- collate and printing more than one page when printer do not support multiple pages and collation
(well, should not be very difficult, maybe just check if this is supported and if no just print many times
the same)
|
- Printers property (list of printers in system),PrinterIndex and Fonts property
|
- print preview
|
- more tests}
interface
uses Windows, Messages, KOL, KOLPrintCommon;
type
TPrinterState = (psNeedHandle, psHandle, psOtherHandle);
TPrinterOrientation = (poPortrait, poLandscape);
{* Paper orientation}
TMarginOption = (mgInches, mgMillimeters);
{* Margin option}
PPrinter = ^TPrinter;
TKOLPrinter = PPrinter;
TPrinter = object(TObj)
{*}
private
{ Private declarations }
fDevice, fDriver, fPort: string;
fDevMode: THandle;
fDeviceMode: PDeviceMode;
fCanvas: PCanvas; // KOL canvas
fTitle: string;
fState: TPrinterState; // DC is allocated or need new DC becouse params were changed
fAborted: Boolean;
fPrinting: Boolean;
fPageNumber: Integer;
fOutput: string;
PrinterInfo: TPrinterInfo;
fRec: TRect;
fMargins: TRect; //Margins (in pixels)
fAssigned: Boolean; //if TRUE ,there is a printer with correctly assigned information
protected
function GetHandle: HDC;
procedure SetHandle(Value: HDC);
function GetCanvas: PCanvas;
function GetCopies: Integer;
procedure SetCopies(const Value: Integer);
function GetOrientation: TPrinterOrientation;
procedure SetOrientation(const Value: TPrinterOrientation);
function GetPageHeight: Integer;
function GetPageWidth: Integer;
function Scale: Integer;
procedure Prepare;
procedure DefPrinter;
public
{ Public declarations }
destructor Destroy; virtual;
procedure Abort;
{* Abort print process}
procedure BeginDoc;
{* Begin print process}
procedure EndDoc;
{* End print process end send it to print spooler}
procedure NewPage;
{* Request new page}
procedure Assign(Source: PPrinterInfo);
{* Assign information about selected printer for example from Print/Page dialogs}
procedure AssignMargins(cMargins: TRect; Option: TMarginOption);
{* Assign information about paper margins for example from TKOLPageSetupDialog
(in thousands of inches scale)}
procedure WriteLn(const Text: string);
{* Print tekst with TKOLPrinter selected font.Note: can be invoked more than once, but currently
only for text ended with #10#13 (other is not properly wraped around right page corner ;-( )}
procedure RE_Print(RichEdit: PControl);
{* Print content of TKOLRichEdit (if Rich is not TKOLRichEdit nothing happens)
with full formating of course :-)}
property Assigned: Boolean read fAssigned;
{* If TRUE, there is a default or assigned previoulsy printer (by Assign).Always check
this property to avoid access violation when there is no printer in system}
property Title: string read fTitle write fTitle;
{* Title of print process in print manager window}
function Info: PPrinterInfo;
{* Returns info of selected print}
property Output: string read fOutput write fOutput;
{* Let print to the file.Assign file path to this property.}
property Handle: HDC read GetHandle write SetHandle;
{*}
property Canvas: PCanvas read GetCanvas;
{*}
property Copies: Integer read GetCopies write SetCopies;
{* Number of copies}
property Orientation: TPrinterOrientation read GetOrientation write SetOrientation;
{* Page orientation}
property Margins: TRect read fMargins write fMargins;
{* Page margins (in pixels)}
property PageHeight: Integer read GetPageHeight;
{* Page height in logical pixels}
property PageWidth: Integer read GetPageWidth;
{* Page width in logical pixels}
property PageNumber: Integer read fPageNumber;
{* Currently printed page number}
property Printing: Boolean read fPrinting;
{* Indicate printing process}
property Aborted: Boolean read fAborted;
{* Indicate abort of printing process}
end;
function Printer: PPrinter;
{* Returns pointer to global TKOLPrinter object}
procedure RecreatePrinter;
{* Recreates global Printer pbject }
function NewPrinter(PrinterInfo: PPrinterInfo): PPrinter;
{* Global function for creating TKOLPrinter instance.Usually not needed, becouse
inluding KOLPrinters causes creating of global TKOLPrinter instance.}
implementation
uses
RichEdit;
type
PtagPD = ^tagPD;
tagPD = packed record
lStructSize: DWORD;
hWndOwner: HWND;
hDevMode: HGLOBAL;
hDevNames: HGLOBAL;
hDC: HDC;
Flags: DWORD;
nFromPage: Word;
nToPage: Word;
nMinPage: Word;
nMaxPage: Word;
nCopies: Word;
hInstance: HINST;
lCustData: LPARAM;
lpfnPrintHook: function(Wnd: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;
lpfnSetupHook: function(Wnd: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;
lpPrintTemplateName: PAnsiChar;
lpSetupTemplateName: PAnsiChar;
hPrintTemplate: HGLOBAL;
hSetupTemplate: HGLOBAL;
end;
const
PD_RETURNDC = $00000100;
PD_RETURNDEFAULT = $00000400;
var
FPrinter : PPrinter = nil;
function PrintDlg(var PrintDlg: tagPD): BOOL; stdcall; external 'comdlg32.dll' name 'PrintDlgA';
function AbortProc(Handle: HDC; Error: Integer): Bool; stdcall;
begin
Result := not fPrinter.Aborted;
end;
function NewPrinter(PrinterInfo: PPrinterInfo): PPrinter;
begin
New(Result, Create);
Result.fTitle := '';
Result.fOutput := '';
Result.fAborted := False;
Result.fPrinting := False;
Result.fPageNumber := 0;
Result.fCanvas := nil;
Result.fMargins.Top := 10;
Result.fMargins.Left := 10;
Result.fMargins.Bottom := 10;
Result.fMargins.Right := 10;
FillChar(Result.fRec, sizeof(Result.fRec), 0);
if PrinterInfo = nil then
Result.DefPrinter
else
Result.Assign(PrinterInfo);
end;
function Printer: PPrinter;
begin
if FPrinter = nil then
FPrinter := NewPrinter(nil);
Result := FPrinter;
end;
procedure RecreatePrinter;
begin
Free_And_Nil(FPrinter);
FPrinter := NewPrinter(nil);
end;
destructor TPrinter.Destroy;
begin
Prepare;
fTitle := '';
fDevice := '';
fDriver := '';
fPort := '';
fOutput := '';
inherited; {+++}
FPrinter := nil;
end;
procedure TPrinter.Prepare;
begin
{ Free previously used resources }
if (fState <> psOtherHandle) and (fCanvas <> nil) then begin
fCanvas.Free;
fCanvas := nil; {+++}
end;
if fDevMode <> 0 then begin
GlobalUnlock(fDevMode);
GlobalFree(fDevMode);
end;
end;
function TPrinter.Scale: Integer;
var
DC : HDC;
ScreenH, PrinterH : Integer;
begin
DC := GetDC(0);
ScreenH := GetDeviceCaps(DC, LOGPIXELSY);
PrinterH := GetDeviceCaps(fCanvas.Handle, LOGPIXELSY);
ReleaseDC(0, DC);
Result := PrinterH div ScreenH;
end;
procedure TPrinter.WriteLn(const Text: string);
var
OldFontSize, PageH, Size, Len: Integer;
pC : PChar;
Rect : TRect;
Metrics : TTextMetric;
NewText : string;
procedure ComputeRect;
{ Start from new line.Rect is the rest of page from current new line to the bottom. First probe
how many characters do not fit on this rect.}
begin
Len := 1;
while Windows.DrawText(fCanvas.Handle, pC, Len, Rect, DT_CALCRECT + DT_WORDBREAK + DT_NOCLIP + DT_NOPREFIX + DT_EXPANDTABS) < PageH do begin
Rect.Right := fRec.Right; //must be, becouse DrawText shorten right corner
Len := Len + 100;
if Len > Size then begin
Len := Size;
Break;
end;
end;
{ Next : Count backwards to find exact characters which fit on required page rect.}
while Windows.DrawText(fCanvas.Handle, pC, Len, Rect, DT_CALCRECT + DT_WORDBREAK + DT_NOCLIP + DT_NOPREFIX + DT_EXPANDTABS) > PageH do
Len := Len - 1;
{ Find position of last space or line end (#13#10) to not break word
(if possible) on bottom-right corner of the page.Do it only for multipage text (Len<>Size) }
{
if (Len <> Size) and (Len > 0) then begin
Test := Len;
while ((NewText[Test] <> #32) and (NewText[Test]<> #10)) and (Test > 0) do Test := Test -1 ;
if Test > 0 then Len := Test;
end;
}
{ Finally draw it!}
Windows.DrawText(fCanvas.Handle, pC, Len, Rect, DT_WORDBREAK + DT_NOCLIP + DT_NOPREFIX + DT_EXPANDTABS);
end;
begin
if Length(Text) <= 0 then Exit;
if Text[Length(Text)] <> #10 then NewText := Text + #13#10
else
NewText := Text;
pC := PChar(NewText);
Size := Length(NewText);
SetMapMode(fCanvas.Handle, MM_TEXT);
OldFontSize := fCanvas.Font.FontHeight;
fCanvas.Font.FontHeight := fCanvas.Font.FontHeight * Scale;
SelectObject(fCanvas.Handle, fCanvas.Font.Handle);
PageH := GetPageHeight - fMargins.Bottom;
GetTextMetrics(fCanvas.Handle, Metrics);
while Size > 0 do begin
Rect := fRec;
ComputeRect;
Inc(pC, Len + 1);
Dec(Size, Len + 1);
if (Size > 0) and (fRec.Left <= fMargins.Left) then NewPage;
end;
if (Rect.Bottom > PageH) then begin
NewPage;
Rect.Bottom := 0;
end;
fRec.Top := Rect.Bottom - Metrics.tmHeight;
fRec.Left := fMargins.Left;
fRec.Bottom := PageH;
fCanvas.Font.FontHeight := OldFontSize;
NewText := '';
end;
procedure TPrinter.DefPrinter;
var
ftagPD : tagPD;
DevNames : PDevNames;
begin
fAssigned := false;
fState := psHandle;
Prepare;
{ Get DC of default printer }
FillChar(ftagPD, sizeof(tagPD), 0);
ftagPD.Flags := PD_RETURNDC + PD_RETURNDEFAULT;
ftagPD.lStructSize := sizeof(ftagPD);
if not PrintDlg(ftagPD) then Exit;
fAssigned := true;
DevNames := PDevNames(GlobalLock(ftagPD.hDevNames));
fDevMode := ftagPD.hDevMode;
fDeviceMode := PDevMode(GlobalLock(fDevMode));
try
fDriver := string(PChar(DevNames) + DevNames^.wDriverOffset);
fDevice := string(PChar(DevNames) + DevNames^.wDeviceOffset);
fPort := string(PChar(DevNames) + DevNames^.wOutputOffset);
finally
GlobalUnlock(ftagPD.hDevNames);
GlobalFree(ftagPD.hDevNames);
end;
fCanvas := NewCanvas(ftagPD.hDC);
end;
procedure TPrinter.Assign(Source: PPrinterInfo);
var
Size : Integer;
DevMode : PDevMode;
fhDC : HDC;
begin
fAssigned := false;
if (Source = nil) or
(Source^.ADriver = nil) and
(Source^.ADevice = nil) and
(Source^.APort = nil) and
(Source^.ADevMode = 0) then DefPrinter
else begin
Prepare;
fDriver := string(Source^.ADriver);
fDevice := string(Source^.ADevice);
fPort := string(Source^.APort);
DevMode := PDevMode(GlobalLock(Source^.ADevMode));
try
Size := sizeof(DevMode^);
fDevMode := GlobalAlloc(GHND, Size);
fDeviceMode := PDevMode(GlobalLock(fDevMode));
CopyMemory(fDeviceMode, DevMode, Size);
fhDC := CreateDC(PChar(fDriver), PChar(fDevice), PChar(fPort), fDeviceMode);
finally
GlobalUnlock(Source^.ADevMode);
end;
fCanvas := NewCanvas(fhDC);
fAssigned := true;
end;
end;
procedure TPrinter.AssignMargins(cMargins: TRect; Option: TMarginOption);
var
PH, PW : Integer;
begin
PH := GetDeviceCaps(fCanvas.Handle, LOGPIXELSY);
PW := GetDeviceCaps(fCanvas.Handle, LOGPIXELSX);
case Option of
mgInches: begin
fMargins.Top := round((cMargins.Top * PH) / 1000);
fMargins.Left := round((cMargins.Left * PW) / 1000);
fMargins.Bottom := round((cMargins.Bottom * PH) / 1000);
fMargins.Right := round((cMargins.Right * PW) / 1000);
end;
mgMillimeters: begin
fMargins.Top := round((cMargins.Top * PH) / 2540);
fMargins.Left := round((cMargins.Left * PW) / 2540);
fMargins.Bottom := round((cMargins.Bottom * PH) / 2540);
fMargins.Right := round((cMargins.Right * PW) / 2540);
end;
end;
end;
procedure TPrinter.Abort;
begin
AbortDoc(fCanvas.Handle);
fAborted := True;
EndDoc;
end;
procedure TPrinter.BeginDoc;
var
doc : DOCINFOA;
begin
fRec.Top := fMargins.Top;
fRec.Left := fMargins.Left;
fRec.Right := GetPageWidth - fMargins.Right;
fRec.Bottom := GetPageHeight - fMargins.Bottom;
fAborted := False;
fPageNumber := 1;
fPrinting := True;
FillChar(doc, sizeof(DOCINFOA), 0);
doc.lpszDocName := PChar(fTitle);
if (fOutput <> '') then doc.lpszOutput := PChar(fOutput);
doc.cbSize := sizeof(doc);
SetAbortProc(fCanvas.Handle, AbortProc);
StartDoc(fCanvas.Handle, doc);
StartPage(fCanvas.Handle);
end;
procedure TPrinter.EndDoc;
begin
EndPage(fCanvas.Handle);
if not fAborted then Windows.EndDoc(fCanvas.Handle);
fAborted := False;
fPageNumber := 0;
fOutPut := '';
fPrinting := False;
end;
function TPrinter.GetHandle: HDC;
var
fhDC : HDC;
begin
if (fState = psNeedHandle) and (fCanvas <> nil) then begin
fCanvas.Free;
fhDC := CreateDC(PChar(fDriver), PChar(fDevice), PChar(fPort), fDeviceMode);
fCanvas := NewCanvas(fhDC);
fState := psHandle;
end;
Result := fCanvas.Handle;
end;
procedure TPrinter.SetHandle(Value: HDC);
begin
if Value <> fCanvas.Handle then begin
if fCanvas <> nil then fCanvas.Free;
fCanvas := NewCanvas(Value);
fState := psOtherHandle;
end;
end;
function TPrinter.GetCanvas: PCanvas;
begin
GetHandle;
Result := fCanvas;
end;
function TPrinter.Info: PPrinterInfo;
begin
with PrinterInfo do begin
ADevice := PChar(fDevice);
ADriver := PChar(fDriver);
APort := PChar(fPort);
ADevMode := fDevMode;
end;
Result := @PrinterInfo;
end;
function TPrinter.GetCopies: Integer;
begin
Result := fDeviceMode^.dmCopies;
end;
procedure TPrinter.SetCopies(const Value: Integer);
begin
fDeviceMode^.dmCopies := Value;
end;
function TPrinter.GetOrientation: TPrinterOrientation;
begin
if System.Assigned(fDeviceMode) and (fDeviceMode^.dmOrientation = DMORIENT_PORTRAIT) then
Result := poPortrait
else
Result := poLandscape;
end;
procedure TPrinter.SetOrientation(const Value: TPrinterOrientation);
const
Orientations : array[TPrinterOrientation] of Integer = (DMORIENT_PORTRAIT, DMORIENT_LANDSCAPE);
begin
fDeviceMode^.dmOrientation := Orientations[Value];
end;
function TPrinter.GetPageHeight: Integer;
begin
if fCanvas <> nil then
Result := GetDeviceCaps(fCanvas.Handle, VERTRES)
else Result := 0;
end;
function TPrinter.GetPageWidth: Integer;
begin
if fCanvas <> nil then
Result := GetDeviceCaps(fCanvas.Handle, HORZRES)
else Result := 0;
end;
procedure TPrinter.NewPage;
begin
fRec.Top := fMargins.Top;
fRec.Left := fMargins.Left;
fRec.Right := GetPageWidth - fMargins.Right;
fRec.Bottom := GetPageHeight - fMargins.Bottom;
EndPage(fCanvas.Handle);
StartPage(fCanvas.Handle);
SelectObject(fCanvas.Handle, fCanvas.Font.Handle);
Inc(fPageNumber);
end;
procedure TPrinter.RE_Print(RichEdit: PControl);
var
Range : TFormatRange;
LastChar, MaxLen, LogX, LogY, OldMap: Integer;
SaveRect : TRect;
TextLenEx : TGetTextLengthEx;
begin
if IndexOfStr(RichEdit.SubClassName, 'obj_RichEdit') = -1 then Exit;
FillChar(Range, SizeOf(TFormatRange), 0);
with Range do begin
BeginDoc;
hdc := GetHandle;
hdcTarget := hdc;
LogX := GetDeviceCaps(Handle, LOGPIXELSX);
LogY := GetDeviceCaps(Handle, LOGPIXELSY);
rc.Top := fMargins.Top * 1440 div LogY;
rc.Left := fMargins.Left * 1440 div LogX;
rc.Right := (GetPageWidth - fMargins.Right) * 1440 div LogX;
rc.Bottom := (GetPageHeight - fMargins.Bottom) * 1440 div LogY;
rcPage := rc;
SaveRect := rc;
LastChar := 0;
// if RichEdit.Version >= 2 then begin
with TextLenEx do begin
flags := GTL_DEFAULT;
codepage := CP_ACP;
end;
MaxLen := RichEdit.Perform(EM_GETTEXTLENGTHEX, WParam(@TextLenEx), 0);
// end
// else
// MaxLen := Length(RichEdit.RE_Text[ reRTF, True ]);
chrg.cpMax := -1;
OldMap := SetMapMode(hdc, MM_TEXT);
SendMessage(RichEdit.Handle, EM_FORMATRANGE, 0, 0); { flush buffer }
try
repeat
rc := SaveRect;
chrg.cpMin := LastChar;
LastChar := SendMessage(RichEdit.Handle, EM_FORMATRANGE, 1, Longint(@Range));
if (LastChar < MaxLen) and (LastChar <> -1) then NewPage;
until (LastChar >= MaxLen) or (LastChar = -1);
EndDoc;
finally
SendMessage(RichEdit.Handle, EM_FORMATRANGE, 0, 0); { flush buffer }
SetMapMode(hdc, OldMap); { restore previous map mode }
end;
end;
end;
initialization
//FPrinter := NewPrinter(nil);
finalization
Free_And_Nil(FPrinter);
end.