home *** CD-ROM | disk | FTP | other *** search
/ Delphi Anthology / aDELPHI.iso / Runimage / Delphi50 / Source / Vcl / printers.pas < prev    next >
Pascal/Delphi Source File  |  1999-08-11  |  25KB  |  920 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Borland Delphi Visual Component Library         }
  5. {                                                       }
  6. {       Copyright (c) 1995,99 Inprise Corporation       }
  7. {                                                       }
  8. {*******************************************************}
  9.  
  10. unit Printers;
  11.  
  12. {$R-,T-,X+,H+}
  13.  
  14. interface
  15.  
  16. uses Windows, WinSpool, SysUtils, Classes, Graphics, Forms;
  17.  
  18. type
  19.   EPrinter = class(Exception);
  20.  
  21.   { TPrinter }
  22.  
  23.   { The printer object encapsulates the printer interface of Windows.  A print
  24.     job is started whenever any redering is done either through a Text variable
  25.     or the printers canvas.  This job will stay open until EndDoc is called or
  26.     the Text variable is closed.  The title displayed in the Print Manager (and
  27.     on network header pages) is determined by the Title property.
  28.  
  29.     EndDoc - Terminates the print job (and closes the currently open Text).
  30.       The print job will being printing on the printer after a call to EndDoc.
  31.     NewPage - Starts a new page and increments the PageNumber property.  The
  32.       pen position of the Canvas is put back at (0, 0).
  33.     Canvas - Represents the surface of the currently printing page.  Note that
  34.       some printer do not support drawing pictures and the Draw, StretchDraw,
  35.       and CopyRect methods might fail.
  36.     Fonts - The list of fonts supported by the printer.  Note that TrueType
  37.       fonts appear in this list even if the font is not supported natively on
  38.       the printer since GDI can render them accurately for the printer.
  39.     PageHeight - The height, in pixels, of the page.
  40.     PageWidth - The width, in pixels, of the page.
  41.     PageNumber - The current page number being printed.  This is incremented
  42.       when ever the NewPage method is called.  (Note: This property can also be
  43.       incremented when a Text variable is written, a CR is encounted on the
  44.       last line of the page).
  45.     PrinterIndex - Specifies which printer in the TPrinters list that is
  46.       currently selected for printing.  Setting this property to -1 will cause
  47.       the default printer to be selected.  If this value is changed EndDoc is
  48.       called automatically.
  49.     Printers - A list of the printers installed in Windows.
  50.     Title - The title used by Windows in the Print Manager and for network
  51.       title pages. }
  52.  
  53.   TPrinterState = (psNoHandle, psHandleIC, psHandleDC);
  54.   TPrinterOrientation = (poPortrait, poLandscape);
  55.   TPrinterCapability = (pcCopies, pcOrientation, pcCollation);
  56.   TPrinterCapabilities = set of TPrinterCapability;
  57.  
  58.   TPrinter = class(TObject)
  59.   private
  60.     FCanvas: TCanvas;
  61.     FFonts: TStrings;
  62.     FPageNumber: Integer;
  63.     FPrinters: TStrings;
  64.     FPrinterIndex: Integer;
  65.     FTitle: string;
  66.     FPrinting: Boolean;
  67.     FAborted: Boolean;
  68.     FCapabilities: TPrinterCapabilities;
  69.     State: TPrinterState;
  70.     DC: HDC;
  71.     DevMode: PDeviceMode;
  72.     DeviceMode: THandle;
  73.     FPrinterHandle: THandle;
  74.     procedure SetState(Value: TPrinterState);
  75.     function GetCanvas: TCanvas;
  76.     function GetNumCopies: Integer;
  77.     function GetFonts: TStrings;
  78.     function GetHandle: HDC;
  79.     function GetOrientation: TPrinterOrientation;
  80.     function GetPageHeight: Integer;
  81.     function GetPageWidth: Integer;
  82.     function GetPrinterIndex: Integer;
  83.     procedure SetPrinterCapabilities(Value: Integer);
  84.     procedure SetPrinterIndex(Value: Integer);
  85.     function GetPrinters: TStrings;
  86.     procedure SetNumCopies(Value: Integer);
  87.     procedure SetOrientation(Value: TPrinterOrientation);
  88.     procedure SetToDefaultPrinter;
  89.     procedure CheckPrinting(Value: Boolean);
  90.     procedure FreePrinters;
  91.     procedure FreeFonts;
  92.   public
  93.     constructor Create;
  94.     destructor Destroy; override;
  95.     procedure Abort;
  96.     procedure BeginDoc;
  97.     procedure EndDoc;
  98.     procedure NewPage;
  99.     procedure GetPrinter(ADevice, ADriver, APort: PChar; var ADeviceMode: THandle);
  100.     procedure SetPrinter(ADevice, ADriver, APort: PChar; ADeviceMode: THandle);
  101.     procedure Refresh;
  102.     property Aborted: Boolean read FAborted;
  103.     property Canvas: TCanvas read GetCanvas;
  104.     property Capabilities: TPrinterCapabilities read FCapabilities;
  105.     property Copies: Integer read GetNumCopies write SetNumCopies;
  106.     property Fonts: TStrings read GetFonts;
  107.     property Handle: HDC read GetHandle;
  108.     property Orientation: TPrinterOrientation read GetOrientation write SetOrientation;
  109.     property PageHeight: Integer read GetPageHeight;
  110.     property PageWidth: Integer read GetPageWidth;
  111.     property PageNumber: Integer read FPageNumber;
  112.     property PrinterIndex: Integer read GetPrinterIndex write SetPrinterIndex;
  113.     property Printing: Boolean read FPrinting;
  114.     property Printers: TStrings read GetPrinters;
  115.     property Title: string read FTitle write FTitle;
  116.   end;
  117.  
  118. { Printer function - Replaces the Printer global variable of previous versions,
  119.   to improve smart linking (reduce exe size by 2.5k in projects that don't use
  120.   the printer).  Code which assigned to the Printer global variable
  121.   must call SetPrinter instead.  SetPrinter returns current printer object
  122.   and makes the new printer object the current printer.  It is the caller's
  123.   responsibility to free the old printer, if appropriate.  (This allows
  124.   toggling between different printer objects without destroying configuration
  125.   settings.) }
  126.  
  127. function Printer: TPrinter;
  128. function SetPrinter(NewPrinter: TPrinter): TPrinter;
  129.  
  130. { AssignPrn - Assigns a Text variable to the currently selected printer.  Any
  131.   Write or Writeln's going to that file variable will be written on the
  132.   printer using the Canvas property's font.  A new page is automatically
  133.   started if a CR is encountered on (or a Writeln is written to) the last
  134.   line on the page.  Closing the text file will imply a call to the
  135.   Printer.EndDoc method. Note: only one Text variable can be open on the
  136.   printer at a time.  Opening a second will cause an exception.}
  137.  
  138. procedure AssignPrn(var F: Text);
  139.  
  140. implementation
  141.  
  142. uses Consts;
  143.  
  144. var
  145.   FPrinter: TPrinter = nil;
  146.  
  147. function FetchStr(var Str: PChar): PChar;
  148. var
  149.   P: PChar;
  150. begin
  151.   Result := Str;
  152.   if Str = nil then Exit;
  153.   P := Str;
  154.   while P^ = ' ' do Inc(P);
  155.   Result := P;
  156.   while (P^ <> #0) and (P^ <> ',') do Inc(P);
  157.   if P^ = ',' then
  158.   begin
  159.     P^ := #0;
  160.     Inc(P);
  161.   end;
  162.   Str := P;
  163. end;
  164.  
  165. procedure RaiseError(const Msg: string);
  166. begin
  167.   raise EPrinter.Create(Msg);
  168. end;
  169.  
  170. function AbortProc(Prn: HDC; Error: Integer): Bool; stdcall;
  171. begin
  172.   Application.ProcessMessages;
  173.   Result := not FPrinter.Aborted;
  174. end;
  175.  
  176. type
  177.   PrnRec = record
  178.     case Integer of
  179.       1: (
  180.         Cur: TPoint;
  181.         Finish: TPoint;         { End of the printable area }
  182.         Height: Integer);       { Height of the current line }
  183.       2: (
  184.         Tmp: array[1..32] of Char);
  185.   end;
  186.  
  187. procedure NewPage(var Prn: PrnRec);
  188. begin
  189.   with Prn do
  190.   begin
  191.     Cur.X := 0;
  192.     Cur.Y := 0;
  193.     FPrinter.NewPage;
  194.   end;
  195. end;
  196.  
  197. { Start a new line on the current page, if no more lines left start a new
  198.   page. }
  199. procedure NewLine(var Prn: PrnRec);
  200.  
  201.   function CharHeight: Word;
  202.   var
  203.     Metrics: TTextMetric;
  204.   begin
  205.     GetTextMetrics(FPrinter.Canvas.Handle, Metrics);
  206.     Result := Metrics.tmHeight;
  207.   end;
  208.  
  209. begin
  210.   with Prn do
  211.   begin
  212.     Cur.X := 0;
  213.     if Height = 0 then
  214.       Inc(Cur.Y, CharHeight) else
  215.       Inc(Cur.Y, Height);
  216.     if Cur.Y > (Finish.Y - (Height * 2)) then NewPage(Prn);
  217.     Height := 0;
  218.   end;
  219. end;
  220.  
  221. { Print a string to the printer without regard to special characters.  These
  222.   should handled by the caller. }
  223. procedure PrnOutStr(var Prn: PrnRec; Text: PChar; Len: Integer);
  224. var
  225.   Extent: TSize;
  226.   L: Integer;
  227. begin
  228.   with Prn, FPrinter.Canvas do
  229.   begin
  230.     while Len > 0 do
  231.     begin
  232.       L := Len;
  233.       GetTextExtentPoint(Handle, Text, L, Extent);
  234.  
  235.       while (L > 0) and (Extent.cX + Cur.X > Finish.X) do
  236.       begin
  237.         L := CharPrev(Text, Text+L) - Text;
  238.         GetTextExtentPoint(Handle, Text, L, Extent);
  239.       end;
  240.  
  241.       if Extent.cY > Height then Height := Extent.cY + 2;
  242.       Windows.TextOut(Handle, Cur.X, Cur.Y, Text, L);
  243.       Dec(Len, L);
  244.       Inc(Text, L);
  245.       if Len > 0 then NewLine(Prn)
  246.       else Inc(Cur.X, Extent.cX);
  247.     end;
  248.   end;
  249. end;
  250.  
  251. { Print a string to the printer handling special characters. }
  252. procedure PrnString(var Prn: PrnRec; Text: PChar; Len: Integer);
  253. var
  254.   L: Integer;
  255.   TabWidth: Word;
  256.  
  257.   procedure Flush;
  258.   begin
  259.     if L <> 0 then PrnOutStr(Prn, Text, L);
  260.     Inc(Text, L + 1);
  261.     Dec(Len, L + 1);
  262.     L := 0;
  263.   end;
  264.  
  265.   function AvgCharWidth: Word;
  266.   var
  267.     Metrics: TTextMetric;
  268.   begin
  269.     GetTextMetrics(FPrinter.Canvas.Handle, Metrics);
  270.     Result := Metrics.tmAveCharWidth;
  271.   end;
  272.  
  273. begin
  274.   L := 0;
  275.   with Prn do
  276.   begin
  277.     while L < Len do
  278.     begin
  279.       case Text[L] of
  280.         #9:
  281.           begin
  282.             Flush;
  283.             TabWidth := AvgCharWidth * 8;
  284.             Inc(Cur.X, TabWidth - ((Cur.X + TabWidth + 1)
  285.               mod TabWidth) + 1);
  286.             if Cur.X > Finish.X then NewLine(Prn);
  287.           end;
  288.         #13: Flush;
  289.         #10:
  290.           begin
  291.             Flush;
  292.             NewLine(Prn);
  293.           end;
  294.         ^L:
  295.           begin
  296.             Flush;
  297.             NewPage(Prn);
  298.           end;
  299.       else
  300.         Inc(L);
  301.       end;
  302.     end;
  303.   end;
  304.   Flush;
  305. end;
  306.  
  307. { Called when a Read or Readln is applied to a printer file. Since reading is
  308.   illegal this routine tells the I/O system that no characters where read, which
  309.   generates a runtime error. }
  310. function PrnInput(var F: TTextRec): Integer;
  311. begin
  312.   with F do
  313.   begin
  314.     BufPos := 0;
  315.     BufEnd := 0;
  316.   end;
  317.   Result := 0;
  318. end;
  319.  
  320. { Called when a Write or Writeln is applied to a printer file. The calls
  321.   PrnString to write the text in the buffer to the printer. }
  322. function PrnOutput(var F: TTextRec): Integer;
  323. begin
  324.   with F do
  325.   begin
  326.     PrnString(PrnRec(UserData), PChar(BufPtr), BufPos);
  327.     BufPos := 0;
  328.     Result := 0;
  329.   end;
  330. end;
  331.  
  332. { Will ignore certain requests by the I/O system such as flush while doing an
  333.   input. }
  334. function PrnIgnore(var F: TTextRec): Integer;
  335. begin
  336.   Result := 0;
  337. end;
  338.  
  339. { Deallocates the resources allocated to the printer file. }
  340. function PrnClose(var F: TTextRec): Integer;
  341. begin
  342.   with PrnRec(F.UserData) do
  343.   begin
  344.     FPrinter.EndDoc;
  345.     Result := 0;
  346.   end;
  347. end;
  348.  
  349. { Called to open I/O on a printer file.  Sets up the TTextFile to point to
  350.   printer I/O functions. }
  351. function PrnOpen(var F: TTextRec): Integer;
  352. const
  353.   Blank: array[0..0] of Char = '';
  354. begin
  355.   with F, PrnRec(UserData) do
  356.   begin
  357.     if Mode = fmInput then
  358.     begin
  359.       InOutFunc := @PrnInput;
  360.       FlushFunc := @PrnIgnore;
  361.       CloseFunc := @PrnIgnore;
  362.     end else
  363.     begin
  364.       Mode := fmOutput;
  365.       InOutFunc := @PrnOutput;
  366.       FlushFunc := @PrnOutput;
  367.       CloseFunc := @PrnClose;
  368.       FPrinter.BeginDoc;
  369.  
  370.       Cur.X := 0;
  371.       Cur.Y := 0;
  372.       Finish.X := FPrinter.PageWidth;
  373.       Finish.Y := FPrinter.PageHeight;
  374.       Height := 0;
  375.     end;
  376.     Result := 0;
  377.   end;
  378. end;
  379.  
  380. procedure AssignPrn(var F: Text);
  381. begin
  382.   with TTextRec(F), PrnRec(UserData) do
  383.   begin
  384.     Printer;
  385.     FillChar(F, SizeOf(F), 0);
  386.     Mode := fmClosed;
  387.     BufSize := SizeOf(Buffer);
  388.     BufPtr := @Buffer;
  389.     OpenFunc := @PrnOpen;
  390.   end;
  391. end;
  392.  
  393. { TPrinterDevice }
  394.  
  395. type
  396.   TPrinterDevice = class
  397.     Driver, Device, Port: String;
  398.     constructor Create(ADriver, ADevice, APort: PChar);
  399.     function IsEqual(ADriver, ADevice, APort: PChar): Boolean;
  400.   end;
  401.  
  402. constructor TPrinterDevice.Create(ADriver, ADevice, APort: PChar);
  403. begin
  404.   inherited Create;
  405.   Driver := ADriver;
  406.   Device := ADevice;
  407.   Port := APort;
  408. end;
  409.  
  410. function TPrinterDevice.IsEqual(ADriver, ADevice, APort: PChar): Boolean;
  411. begin
  412.   Result := (Device = ADevice) and ((Port = '') or (Port = APort));
  413. end;
  414.  
  415. { TPrinterCanvas }
  416.  
  417. type
  418.   TPrinterCanvas = class(TCanvas)
  419.     Printer: TPrinter;
  420.     constructor Create(APrinter: TPrinter);
  421.     procedure CreateHandle; override;
  422.     procedure Changing; override;
  423.     procedure UpdateFont;
  424.   end;
  425.  
  426. constructor TPrinterCanvas.Create(APrinter: TPrinter);
  427. begin
  428.   inherited Create;
  429.   Printer := APrinter;
  430. end;
  431.  
  432. procedure TPrinterCanvas.CreateHandle;
  433. begin
  434.   Printer.SetState(psHandleIC);
  435.   UpdateFont;
  436.   Handle:= Printer.DC;
  437. end;
  438.  
  439. procedure TPrinterCanvas.Changing;
  440. begin
  441.   Printer.CheckPrinting(True);
  442.   inherited Changing;
  443.   UpdateFont;
  444. end;
  445.  
  446. procedure TPrinterCanvas.UpdateFont;
  447. var
  448.   FontSize: Integer;
  449. begin
  450.   if GetDeviceCaps(Printer.DC, LOGPIXELSY) <> Font.PixelsPerInch then
  451.   begin
  452.     FontSize := Font.Size;
  453.     Font.PixelsPerInch := GetDeviceCaps(Printer.DC, LOGPIXELSY);
  454.     Font.Size := FontSize;
  455.   end;
  456. end;
  457.  
  458. { TPrinter }
  459.  
  460. constructor TPrinter.Create;
  461. begin
  462.   inherited Create;
  463.   FPrinterIndex := -1;
  464. end;
  465.  
  466. destructor TPrinter.Destroy;
  467. begin
  468.   if Printing then EndDoc;
  469.   SetState(psNoHandle);
  470.   FreePrinters;
  471.   FreeFonts;
  472.   FCanvas.Free;
  473.   if FPrinterHandle <> 0 then ClosePrinter(FPrinterHandle);
  474.   if DeviceMode <> 0 then
  475.   begin
  476.     GlobalUnlock(DeviceMode);
  477.     GlobalFree(DeviceMode);
  478.     DeviceMode := 0;
  479.   end;
  480.   inherited Destroy;
  481. end;
  482.  
  483. procedure TPrinter.SetState(Value: TPrinterState);
  484. type
  485.   TCreateHandleFunc = function (DriverName, DeviceName, Output: PChar;
  486.     InitData: PDeviceMode): HDC stdcall;
  487. var
  488.   CreateHandleFunc: TCreateHandleFunc;
  489. begin
  490.   if Value <> State then
  491.   begin
  492.     CreateHandleFunc := nil;
  493.     case Value of
  494.       psNoHandle:
  495.         begin
  496.           CheckPrinting(False);
  497.           if Assigned(FCanvas) then FCanvas.Handle := 0;
  498.           DeleteDC(DC);
  499.           DC := 0;
  500.         end;
  501.       psHandleIC:
  502.         if State <> psHandleDC then CreateHandleFunc := CreateIC
  503.         else Exit;
  504.       psHandleDC:
  505.         begin
  506.           if FCanvas <> nil then FCanvas.Handle := 0;
  507.           if DC <> 0 then DeleteDC(DC);
  508.           CreateHandleFunc := CreateDC;
  509.         end;
  510.     end;
  511.     if Assigned(CreateHandleFunc) then
  512.       with TPrinterDevice(Printers.Objects[PrinterIndex]) do
  513.       begin
  514.         DC := CreateHandleFunc(PChar(Driver), PChar(Device), PChar(Port), DevMode);
  515.         if DC = 0 then RaiseError(SInvalidPrinter);
  516.         if FCanvas <> nil then FCanvas.Handle := DC;
  517.       end;
  518.     State := Value;
  519.   end;
  520. end;
  521.  
  522. procedure TPrinter.CheckPrinting(Value: Boolean);
  523. begin
  524.   if Printing <> Value then
  525.     if Value then RaiseError(SNotPrinting)
  526.     else RaiseError(SPrinting);
  527. end;
  528.  
  529. procedure TPrinter.Abort;
  530. begin
  531.   CheckPrinting(True);
  532.   AbortDoc(Canvas.Handle);
  533.   FAborted := True;
  534.   EndDoc;
  535. end;
  536.  
  537. procedure TPrinter.BeginDoc;
  538. var
  539.   DocInfo: TDocInfo;
  540. begin
  541.   CheckPrinting(False);
  542.   SetState(psHandleDC);
  543.   Canvas.Refresh;
  544.   TPrinterCanvas(Canvas).UpdateFont;
  545.   FPrinting := True;
  546.   FAborted := False;
  547.   FPageNumber := 1;
  548.   FillChar(DocInfo, SizeOf(DocInfo), 0);
  549.   with DocInfo do
  550.   begin
  551.     cbSize := SizeOf(DocInfo);
  552.     lpszDocName := PChar(Title);
  553.   end;
  554.   SetAbortProc(DC, AbortProc);
  555.   StartDoc(DC, DocInfo);
  556.   StartPage(DC);
  557. end;
  558.  
  559. procedure TPrinter.EndDoc;
  560. begin
  561.   CheckPrinting(True);
  562.   EndPage(DC);
  563.   if not Aborted then Windows.EndDoc(DC);
  564.   FPrinting := False;
  565.   FAborted := False;
  566.   FPageNumber := 0;
  567. end;
  568.  
  569. procedure TPrinter.NewPage;
  570. begin
  571.   CheckPrinting(True);
  572.   EndPage(DC);
  573.   StartPage(DC);
  574.   Inc(FPageNumber);
  575.   Canvas.Refresh;
  576. end;
  577.  
  578. procedure TPrinter.GetPrinter(ADevice, ADriver, APort: PChar; var ADeviceMode: THandle);
  579. begin
  580.   with TPrinterDevice(Printers.Objects[PrinterIndex]) do
  581.   begin
  582.     StrCopy(ADevice, PChar(Device));
  583.     StrCopy(ADriver, PChar(Driver));
  584.     StrCopy(APort, PChar(Port));
  585.   end;
  586.   ADeviceMode := DeviceMode;
  587. end;
  588.  
  589. procedure TPrinter.SetPrinterCapabilities(Value: Integer);
  590. begin
  591.   FCapabilities := [];
  592.   if (Value and DM_ORIENTATION) <> 0 then
  593.     Include(FCapabilities, pcOrientation);
  594.   if (Value and DM_COPIES) <> 0 then
  595.     Include(FCapabilities, pcCopies);
  596.   if (Value and DM_COLLATE) <> 0 then
  597.     Include(FCapabilities, pcCollation);
  598. end;
  599.  
  600. procedure TPrinter.SetPrinter(ADevice, ADriver, APort: PChar; ADeviceMode: THandle);
  601. var
  602.   I, J: Integer;
  603.   StubDevMode: TDeviceMode;
  604. begin
  605.   CheckPrinting(False);
  606.   if ADeviceMode <> DeviceMode then
  607.   begin  // free the devmode block we have, and take the one we're given
  608.     if DeviceMode <> 0 then
  609.     begin
  610.       GlobalUnlock(DeviceMode);
  611.       GlobalFree(DeviceMode);
  612.     end;
  613.     DeviceMode := ADeviceMode;
  614.   end;
  615.   if DeviceMode <> 0 then
  616.   begin
  617.     DevMode := GlobalLock(DeviceMode);
  618.     SetPrinterCapabilities(DevMode.dmFields);
  619.   end;
  620.   FreeFonts;
  621.   if FPrinterHandle <> 0 then
  622.   begin
  623.     ClosePrinter(FPrinterHandle);
  624.     FPrinterHandle := 0;
  625.   end;
  626.   SetState(psNoHandle);
  627.   J := -1;
  628.   with Printers do   // <- this rebuilds the FPrinters list
  629.     for I := 0 to Count - 1 do
  630.     begin
  631.       if TPrinterDevice(Objects[I]).IsEqual(ADriver, ADevice, APort) then
  632.       begin
  633.         TPrinterDevice(Objects[I]).Port := APort;
  634.         J := I;
  635.         Break;
  636.       end;
  637.     end;
  638.   if J = -1 then
  639.   begin
  640.     J := FPrinters.Count;
  641.     FPrinters.AddObject(Format(SDeviceOnPort, [ADevice, APort]),
  642.       TPrinterDevice.Create(ADriver, ADevice, APort));
  643.   end;
  644.   FPrinterIndex := J;
  645.   if OpenPrinter(ADevice, FPrinterHandle, nil) then
  646.   begin
  647.     if DeviceMode = 0 then  // alloc new device mode block if one was not passed in
  648.     begin
  649.       DeviceMode := GlobalAlloc(GHND,
  650.         DocumentProperties(0, FPrinterHandle, ADevice, StubDevMode,
  651.         StubDevMode, 0));
  652.       if DeviceMode <> 0 then
  653.       begin
  654.         DevMode := GlobalLock(DeviceMode);
  655.         if DocumentProperties(0, FPrinterHandle, ADevice, DevMode^,
  656.           DevMode^, DM_OUT_BUFFER) < 0 then
  657.         begin
  658.           GlobalUnlock(DeviceMode);
  659.           GlobalFree(DeviceMode);
  660.           DeviceMode := 0;
  661.         end
  662.       end;
  663.     end;
  664.     if DeviceMode <> 0 then
  665.       SetPrinterCapabilities(DevMode^.dmFields);
  666.   end;
  667. end;
  668.  
  669. function TPrinter.GetCanvas: TCanvas;
  670. begin
  671.   if FCanvas = nil then FCanvas := TPrinterCanvas.Create(Self);
  672.   Result := FCanvas;
  673. end;
  674.  
  675. function EnumFontsProc(var LogFont: TLogFont; var TextMetric: TTextMetric;
  676.   FontType: Integer; Data: Pointer): Integer; stdcall;
  677. begin
  678.   TStrings(Data).Add(LogFont.lfFaceName);
  679.   Result := 1;
  680. end;
  681.  
  682. function TPrinter.GetFonts: TStrings;
  683. begin
  684.   if FFonts = nil then
  685.   try
  686.     SetState(psHandleIC);
  687.     FFonts := TStringList.Create;
  688.     EnumFonts(DC, nil, @EnumFontsProc, Pointer(FFonts));
  689.   except
  690.     FreeAndNil(FFonts);
  691.     raise;
  692.   end;
  693.   Result := FFonts;
  694. end;
  695.  
  696. function TPrinter.GetHandle: HDC;
  697. begin
  698.   SetState(psHandleIC);
  699.   Result := DC;
  700. end;
  701.  
  702. function TPrinter.GetNumCopies: Integer;
  703. begin
  704.   GetPrinterIndex;
  705.   if DeviceMode = 0 then RaiseError(SInvalidPrinterOp);
  706.   Result := DevMode^.dmCopies;
  707. end;
  708.  
  709. procedure TPrinter.SetNumCopies(Value: Integer);
  710. begin
  711.   CheckPrinting(False);
  712.   GetPrinterIndex;
  713.   if DeviceMode = 0 then RaiseError(SInvalidPrinterOp);
  714.   SetState(psNoHandle);
  715.   DevMode^.dmCopies := Value;
  716. end;
  717.  
  718. function TPrinter.GetOrientation: TPrinterOrientation;
  719. begin
  720.   GetPrinterIndex;
  721.   if DeviceMode = 0 then RaiseError(SInvalidPrinterOp);
  722.   if DevMode^.dmOrientation = DMORIENT_PORTRAIT then Result := poPortrait
  723.   else Result := poLandscape;
  724. end;
  725.  
  726. procedure TPrinter.SetOrientation(Value: TPrinterOrientation);
  727. const
  728.   Orientations: array [TPrinterOrientation] of Integer = (
  729.     DMORIENT_PORTRAIT, DMORIENT_LANDSCAPE);
  730. begin
  731.   CheckPrinting(False);
  732.   GetPrinterIndex;
  733.   if DeviceMode = 0 then RaiseError(SInvalidPrinterOp);
  734.   SetState(psNoHandle);
  735.   DevMode^.dmOrientation := Orientations[Value];
  736. end;
  737.  
  738. function TPrinter.GetPageHeight: Integer;
  739. begin
  740.   SetState(psHandleIC);
  741.   Result := GetDeviceCaps(DC, VertRes);
  742. end;
  743.  
  744. function TPrinter.GetPageWidth: Integer;
  745. begin
  746.   SetState(psHandleIC);
  747.   Result := GetDeviceCaps(DC, HorzRes);
  748. end;
  749.  
  750. function TPrinter.GetPrinterIndex: Integer;
  751. begin
  752.   if FPrinterIndex = -1 then SetToDefaultPrinter;
  753.   Result := FPrinterIndex;
  754. end;
  755.  
  756. procedure TPrinter.SetPrinterIndex(Value: Integer);
  757. begin
  758.   CheckPrinting(False);
  759.   if (Value = -1) or (PrinterIndex = -1) then SetToDefaultPrinter
  760.   else if (Value < 0) or (Value >= Printers.Count) then RaiseError(SPrinterIndexError);
  761.   FPrinterIndex := Value;
  762.   FreeFonts;
  763.   SetState(psNoHandle);
  764. end;
  765.  
  766. function TPrinter.GetPrinters: TStrings;
  767. var
  768.   LineCur, Port: PChar;
  769.   Buffer, PrinterInfo: PChar;
  770.   Flags, Count, NumInfo: DWORD;
  771.   I: Integer;
  772.   Level: Byte;
  773. begin
  774.   if FPrinters = nil then
  775.   begin
  776.     FPrinters := TStringList.Create;
  777.     Result := FPrinters;
  778.     try
  779.       if Win32Platform = VER_PLATFORM_WIN32_NT then
  780.       begin
  781.         Flags := PRINTER_ENUM_CONNECTIONS or PRINTER_ENUM_LOCAL;
  782.         Level := 4;
  783.       end
  784.       else
  785.       begin
  786.         Flags := PRINTER_ENUM_LOCAL;
  787.         Level := 5;
  788.       end;
  789.       Count := 0;
  790.       EnumPrinters(Flags, nil, Level, nil, 0, Count, NumInfo);
  791.       if Count = 0 then Exit;
  792.       GetMem(Buffer, Count);
  793.       try
  794.         if not EnumPrinters(Flags, nil, Level, PByte(Buffer), Count, Count, NumInfo) then
  795.           Exit;
  796.         PrinterInfo := Buffer;
  797.         for I := 0 to NumInfo - 1 do
  798.         begin
  799.           if Level = 4 then
  800.             with PPrinterInfo4(PrinterInfo)^ do
  801.             begin
  802.               FPrinters.AddObject(pPrinterName,
  803.                 TPrinterDevice.Create(nil, pPrinterName, nil));
  804.               Inc(PrinterInfo, sizeof(TPrinterInfo4));
  805.             end
  806.           else
  807.             with PPrinterInfo5(PrinterInfo)^ do
  808.             begin
  809.               LineCur := pPortName;
  810.               Port := FetchStr(LineCur);
  811.               while Port^ <> #0 do
  812.               begin
  813.                 FPrinters.AddObject(Format(SDeviceOnPort, [pPrinterName, Port]),
  814.                   TPrinterDevice.Create(nil, pPrinterName, Port));
  815.                 Port := FetchStr(LineCur);
  816.               end;
  817.               Inc(PrinterInfo, sizeof(TPrinterInfo5));
  818.             end;
  819.         end;
  820.       finally
  821.         FreeMem(Buffer, Count);
  822.       end;
  823.     except
  824.       FPrinters.Free;
  825.       FPrinters := nil;
  826.       raise;
  827.     end;
  828.   end;
  829.   Result := FPrinters;
  830. end;
  831.  
  832. procedure TPrinter.SetToDefaultPrinter;
  833. var
  834.   I: Integer;
  835.   ByteCnt, StructCnt: DWORD;
  836.   DefaultPrinter: array[0..79] of Char;
  837.   Cur, Device: PChar;
  838.   PrinterInfo: PPrinterInfo5;
  839. begin
  840.   ByteCnt := 0;
  841.   StructCnt := 0;
  842.   if not EnumPrinters(PRINTER_ENUM_DEFAULT, nil, 5, nil, 0, ByteCnt,
  843.     StructCnt) and (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
  844.   begin
  845.     // With no printers installed, Win95/98 fails above with "Invalid filename".
  846.     // NT succeeds and returns a StructCnt of zero.
  847.     if GetLastError = ERROR_INVALID_NAME then
  848.       RaiseError(SNoDefaultPrinter)
  849.     else
  850.       RaiseLastWin32Error;
  851.   end;
  852.   PrinterInfo := AllocMem(ByteCnt);
  853.   try
  854.     EnumPrinters(PRINTER_ENUM_DEFAULT, nil, 5, PrinterInfo, ByteCnt, ByteCnt,
  855.       StructCnt);
  856.     if StructCnt > 0 then
  857.       Device := PrinterInfo.pPrinterName
  858.     else begin
  859.       GetProfileString('windows', 'device', '', DefaultPrinter,
  860.         SizeOf(DefaultPrinter) - 1);
  861.       Cur := DefaultPrinter;
  862.       Device := FetchStr(Cur);
  863.     end;
  864.     with Printers do
  865.       for I := 0 to Count-1 do
  866.       begin
  867.         if TPrinterDevice(Objects[I]).Device = Device then
  868.         begin
  869.           with TPrinterDevice(Objects[I]) do
  870.             SetPrinter(PChar(Device), PChar(Driver), PChar(Port), 0);
  871.           Exit;
  872.         end;
  873.       end;
  874.   finally
  875.     FreeMem(PrinterInfo);
  876.   end;
  877.   RaiseError(SNoDefaultPrinter);
  878. end;
  879.  
  880. procedure TPrinter.FreePrinters;
  881. var
  882.   I: Integer;
  883. begin
  884.   if FPrinters <> nil then
  885.   begin
  886.     for I := 0 to FPrinters.Count - 1 do
  887.       FPrinters.Objects[I].Free;
  888.     FreeAndNil(FPrinters);
  889.   end;
  890. end;
  891.  
  892. procedure TPrinter.FreeFonts;
  893. begin
  894.   FreeAndNil(FFonts);
  895. end;
  896.  
  897. function Printer: TPrinter;
  898. begin
  899.   if FPrinter = nil then FPrinter := TPrinter.Create;
  900.   Result := FPrinter;
  901. end;
  902.  
  903. function SetPrinter(NewPrinter: TPrinter): TPrinter;
  904. begin
  905.   Result := FPrinter;
  906.   FPrinter := NewPrinter;
  907. end;
  908.  
  909. procedure TPrinter.Refresh;
  910. begin
  911.   FreeFonts;
  912.   FreePrinters;
  913. end;
  914.  
  915. initialization
  916.  
  917. finalization
  918.   FPrinter.Free;
  919. end.
  920.