unit Simp_Prn;

interface

uses System.ComponentModel,
     System.Collections,
     System.Drawing,
     System.Drawing.Printing,
     System.Diagnostics;

CONST
   // default page margins, in inches
  DEFAULT_TOPMARGIN = 0.5;
  DEFAULT_LEFTMARGIN = 0.5;
  DEFAULT_RIGHTMARGIN = 0.5;
  DEFAULT_BOTTOMMARGIN = 0.5;
   // most printers do not let you print at
   // the very edge of the page. We set this
   // non-printable gutter size to 3/16"
  DEFAULT_GUTTER_SIZE = 0.1875; // 3/16"

type
  SimplePrintoutException = class(ApplicationException)
  end;

  BandAlignment = (baLeft, baCentered, baRight, baJustified);

  TBand = class;
  UserPrintPageEventArgs = class;
  UserPrintPageEvent = procedure(sender: System.Object; ua: UserPrintPageEventArgs) of object;
  CreatePrintDocumentEvent = procedure(sender: System.Object; pd: PrintDocument) of object;

{$REGION 'SimplePrintout class declaration'}
  SimplePrintout = class(System.ComponentModel.Component)
  {$REGION 'Designer Managed Code'}
  strict private
    /// <summary>
    /// Required designer variable.
    /// </summary>
    Components: System.ComponentModel.Container;
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    procedure InitializeComponent;
  {$ENDREGION}
  strict private
    FPenPosX, FPenPosY: Single;
    FBandQueue: Queue;

    FPageWidth: Single; // in printer pixels
    FPageHeight: Single;
    FPageNo: Integer;

    FTopMargin: Single; // page margins, in printer pixels
    FLeftMargin: Single;
    FRightMargin: Single;
    FBottomMargin: Single;
    FNonPrintableX: Single;
    FNonPrintableY: Single;

    FInnerBorderPixels: Integer; // inside the margins
    FOuterBorderPixels: Integer; // outside the margins
    FInnerBorderColor: Color;
    FOuterBorderColor: Color;
    FInnerColor: Color; // page background inside the margins
    FOuterColor: Color; // page background outside the margins

    FGutterInches: Single; // page margins, in inches
    FTopMarginInches: Single;
    FLeftMarginInches: Single;
    FRightMarginInches: Single;
    FBottomMarginInches: Single;

    FBand: TBand;    // current band
    FHeader: TBand;  // header band
    FFooter: TBand;  // footer band

    FStarted: Boolean;
    FPrintPage: UserPrintPageEvent;
    FCreatePrintDocument: CreatePrintDocumentEvent;

    procedure InitializeSimplePrintout;
    procedure SetPenPos(p: PointF); overload;
    procedure SetPenPos(x: System.Single; y: System.Single); overload;
    procedure UpdatePageMetrics(r: Rectangle; DpiX: System.Single; DpiY: System.Single);
    procedure PrintHeader(g: Graphics);
    procedure PrintFooter(g: Graphics);
    procedure PrintBorders(g: Graphics);
    procedure PaintInchGrid(g: Graphics);
    procedure pd_PrintPage(sender: System.Object; ev: PrintPageEventArgs);

  strict protected
    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    procedure Dispose(Disposing: Boolean); override;

  public
    constructor Create; overload;
    constructor Create(Container: System.ComponentModel.IContainer); overload;

    procedure WriteString; overload;
    procedure WriteString(s: String); overload;
    procedure PageBreak;
    procedure AbortPrintout;
    procedure BeginPrintout;
    function  EndPrintout: Integer;

    function  get_FooterText: string;
    function  get_HeaderText: string;

    procedure set_NonPrintableGutter(Value: System.Single);
    procedure set_TopMargin(Value: System.Single);
    procedure set_LeftMargin(Value: System.Single);
    procedure set_RightMargin(Value: System.Single);
    procedure set_BottomMargin(Value: System.Single);
    procedure set_FooterText(Value: string);
    procedure set_HeaderText(Value: string);
    procedure set_InnerBorderPixels(Value: Integer);
    procedure set_OuterBorderPixels(Value: Integer);

   {$REGION 'SimplePrintout public properties'}
    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Non printable gutter size, in inches')]
    property NonPrintableGutter: System.Single read FGutterInches write set_NonPrintableGutter;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Top margin including the gutter, in inches')]
    property TopMargin: System.Single read FTopMarginInches write set_TopMargin;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Left margin including the gutter, in inches')]
    property LeftMargin: System.Single read FLeftMarginInches write set_LeftMargin;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Right margin including the gutter, in inches')]
    property RightMargin: System.Single read FRightMarginInches write set_RightMargin;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Bottom margin including the gutter, in inches')]
    property BottomMargin: System.Single read FBottomMarginInches write set_BottomMargin;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Footer text')]
    property FooterText: string read get_FooterText write set_FooterText;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Header text')]
    property HeaderText: string read get_HeaderText write set_HeaderText;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Thickness of the border inside the margins, in pixels')]
    property InnerBorderPixels: Integer read FInnerBorderPixels write set_InnerBorderPixels;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Thickness of the border outside the margins, in pixels')]
    property OuterBorderPixels: Integer read FOuterBorderPixels write set_OuterBorderPixels;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Color of the border inside the margins')]
    property InnerBorderColor: Color read FInnerBorderColor write FInnerBorderColor;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Color of the border outside the margins')]
    property OuterBorderColor: Color read FOuterBorderColor write FOuterBorderColor;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Background color of the page inside the margins')]
    property InnerBackColor: Color read FInnerColor write FInnerColor;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageSettings')]
    [DescriptionAttribute('Background color of the page outside the margins')]
    property OuterBackColor: Color read FOuterColor write FOuterColor;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageParts')]
    [DescriptionAttribute('Page header')]
    property Header: TBand read FHeader write FHeader;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageParts')]
    [DescriptionAttribute('Current page band (paragraph)')]
    property Band: TBand read FBand write FBand;

    [BrowsableAttribute(false)]
    [CategoryAttribute('PageParts')]
    [DescriptionAttribute('Page footer')]
    property Footer: TBand read FFooter write FFooter;

    [BrowsableAttribute(false)]
    [CategoryAttribute('Notification')]
    [DescriptionAttribute('User PrintPage event handler')]
    property PrintPage: UserPrintPageEvent read FPrintPage write FPrintPage;

    [BrowsableAttribute(false)]
    [CategoryAttribute('Notification')]
    [DescriptionAttribute('User CreatePrintDocument event handler')]
    property CreatePrintDocument: CreatePrintDocumentEvent read FCreatePrintDocument write FCreatePrintDocument;
   {$ENDREGION}
  end;
{$ENDREGION}

{$REGION 'UserPrintPageEventArgs class'}
  // This class is used in a callback
  //
  UserPrintPageEventArgs = class(EventArgs)
    PageParms: PrintPageEventArgs;
    PageNo: Integer;
    PrintoutLastPage: Boolean;
    BackgroundHandledByUser: Boolean;
    PageHandledByUser: Boolean;
    HeaderHandledByUser: Boolean;
    FooterHandledByUser: Boolean;
  end;
{$ENDREGION}

{$REGION 'TBand class declaration'}
  TBand = class

  strict private

    // these variables are used for prining only
    //
    FLine: string;
    FGraphics: Graphics;
    FSimulatedPrint: Boolean;
    //
    // these all are measured in PIXELs
    //
    FPenPosX, FPenPosY: System.Single; // pen position
    FLeft, FTop, FWidth, FHeight: System.Single; // print band area
    FTopI, FLeftI, FRightI, FBottomI: System.Single; // paragraph indents
    FTopTI, FLeftTI, FRightTI, FBottomTI: System.Single; // text indents

    // general purpose variables
    //
    FTextFont: Font;
    FTextAlignment: BandAlignment;
    FBackColor: Color;
    FForeColor: Color;
    FBorderColor: Color;
    FBorderPixelSize: Integer;
    FTransparent: Boolean;
    FEjectPageOnly: Boolean;
    FText: String;

    FIndentLeft: System.Single;// measured in inches
    FIndentRight: System.Single;      // in inches
    FIndentTop: System.Single;        // in inches
    FIndentBottom: System.Single;     // in inches
    FTextIndentLeft: System.Single;   // in inches
    FTextIndentRight: System.Single;  // in inches
    FTextIndentTop: System.Single;    // in inches
    FTextIndentBottom: System.Single; // in inches

    procedure Init;
    function  Clone(s: String; ejectOnly: Boolean): TBand; overload;
    function  GetTextHeight: System.Single;
    function  GetTextWidth(s: String): System.Single;
    function  DsPadding(s: String): System.Single;
    function  AddWord(s: String): Boolean;
    function  PrintJustified(line: String; align: BandAlignment): Boolean;
    procedure PrintLine(line: String; align: BandAlignment);
    function  NewLine: Boolean;
    procedure InchesToPixels(DpiX: System.Single; DpiY: System.Single);
    procedure HandleTopIndent(b: SolidBrush);
    procedure PaintTextBackground(b: SolidBrush);
    procedure PaintTextForeground(s: String; b: SolidBrush; X, Y: System.Single);
    procedure HandleBottomIndent(b: SolidBrush);
    procedure SetPenPos(x: System.Single; y: System.Single);
    function  IndexOfNewLine(s: String; var nl_len: Integer): Integer;
    function  PrintBand(s: String): Boolean;

  public
    constructor Create; overload;
    constructor Create(fontName: String; fontSize: System.Single); overload;

    function Clone(s: String): TBand; overload;
    function PageBreakClone: TBand;
    function Print(g: Graphics; r: RectangleF): Boolean;
    function GetPrintedHeight(g: Graphics; width: System.Single): System.Single;
    function GetPenPos: PointF;

    procedure set_BorderPixelSize(Value: Integer);
    procedure set_IndentLeft(Value: System.Single);
    procedure set_IndentTop(Value: System.Single);
    procedure set_IndentRight(Value: System.Single);
    procedure set_IndentBottom(Value: System.Single);
    procedure set_TextIndentLeft(Value: System.Single);
    procedure set_TextIndentRight(Value: System.Single);
    procedure set_TextIndentTop(Value: System.Single);
    procedure set_TextIndentBottom(Value: System.Single);

   {$REGION 'TBand public properties'}
    [BrowsableAttribute(False)]
    property EjectPageOnly: Boolean read FEjectPageOnly;

    [BrowsableAttribute(False)]
    property Text: String read FText write FText;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Text alignment')]
    property TextAlignment: BandAlignment read FTextAlignment write FTextAlignment;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Text font')]
    property TextFont: Font read FTextFont write FTextFont;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Border size, in pixels')]
    property BorderPixelSize: Integer read FBorderPixelSize write set_BorderPixelSize;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Border color')]
    property BorderColor: Color read FBorderColor write FBorderColor;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Font color')]
    property ForeColor: Color read FForeColor write FForeColor;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Background color')]
    property BackColor: Color read FBackColor write FBackColor;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Disables background, makes the band transparent')]
    property Transparent: Boolean read FTransparent write FTransparent;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Left indent, in inches')]
    property IndentLeft: System.Single read FIndentLeft write set_IndentLeft;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Top indent, in inches')]
    property IndentTop: System.Single read FIndentTop write set_IndentTop;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Right indent, in inches')]
    property IndentRight: System.Single read FIndentRight write set_IndentRight;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Bottom indent, in inches')]
    property IndentBottom: System.Single read FIndentBottom write set_IndentBottom;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Left TEXT indent, in inches')]
    property TextIndentLeft: System.Single read FTextIndentLeft write set_TextIndentLeft;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Right TEXT indent, in inches')]
    property TextIndentRight: System.Single read FTextIndentRight write set_TextIndentRight;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Top TEXT indent, in inches')]
    property TextIndentTop: System.Single read FTextIndentTop write set_TextIndentTop;

    [BrowsableAttribute(false)]
    [DescriptionAttribute('Bottom TEXT indent, in inches')]
    property TextIndentBottom: System.Single read FTextIndentBottom write set_TextIndentBottom;
  end;
 {$ENDREGION}
{$ENDREGION}

implementation

{$REGION 'SimplePrintout class'}

{$REGION 'Windows Form Designer generated code'}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
procedure SimplePrintout.InitializeComponent;
begin
  Self.Components := System.ComponentModel.Container.Create;
end;
{$ENDREGION}

constructor SimplePrintout.Create;
begin
  inherited Create;
  //
  // InitializeComponent required
  // for Windows Form Designer support
  //
  InitializeComponent;
  InitializeSimplePrintout;
end;

constructor SimplePrintout.Create(Container: System.ComponentModel.IContainer);
begin
  inherited Create;
  //
  // Required for Windows Form Designer support
  //
  Container.Add(Self);
  InitializeComponent;
  InitializeSimplePrintout;
end;

procedure SimplePrintout.Dispose(Disposing: Boolean);
begin
  if Disposing then
  begin
    if Components <> nil then
      Components.Dispose();
  end;
  inherited Dispose(Disposing);
end;

procedure SimplePrintout.InitializeSimplePrintout;
begin
  FBandQueue := Queue.Create(10, 10);
  FBand := TBand.Create('Arial', 10);
  FHeader := TBand.Create('Arial', 8);
  FFooter := TBand.Create('Arial', 8);
  FHeader.TextAlignment := BandAlignment.baCentered;
  FFooter.TextAlignment := BandAlignment.baCentered;

  FGutterInches := DEFAULT_GUTTER_SIZE;
  FTopMarginInches := DEFAULT_TOPMARGIN;
  FLeftMarginInches := DEFAULT_LEFTMARGIN;
  FRightMarginInches := DEFAULT_RIGHTMARGIN;
  FBottomMarginInches := DEFAULT_BOTTOMMARGIN;

  FInnerBorderColor := Color.Black;
  FOuterBorderColor := Color.Black;
  FInnerColor := Color.White;
  FOuterColor := Color.White;
end;

procedure SimplePrintout.SetPenPos(p: PointF);
begin
  FPenPosX := p.X;
  FPenPosY := p.Y;
end;

procedure SimplePrintout.SetPenPos(x: System.Single; y: System.Single);
begin
  FPenPosX := x;
  FPenPosY := y;
end;

procedure SimplePrintout.UpdatePageMetrics(r: Rectangle; DpiX: Single; DpiY: Single);
var x: Single;
begin
  // Most printers cannot print at the very edge of the page.
  // Non-Printable gutter is in inches. Scale Inches to Pixels.
  //
  FNonPrintableX := NonPrintableGutter * DpiX;
  FNonPrintableY := NonPrintableGutter * DpiY;

  x := r.Height; // for some reason PageBounds is in 1/100"
  x := x * DpiY / 100; // so we have to scale it to pixels
  FPageHeight := x - (FNonPrintableY * 2);

  x := r.Width;
  x := x * (DpiX / 100);
  FPageWidth := x - (FNonPrintableX * 2);

  // Update page margins...
  FTopMargin := (TopMargin * DpiY);
  FLeftMargin := (LeftMargin * DpiX);
  FRightMargin := (RightMargin * DpiX);
  FBottomMargin := (BottomMargin * DpiY);

  if ((FTopMargin - FNonPrintableY) > 0) then
    FTopMargin := FTopMargin - FNonPrintableY
  else
    FTopMargin := 0;

  if ((FLeftMargin - FNonPrintableX) > 0) then
    FLeftMargin := FLeftMargin - FNonPrintableX
  else
    FLeftMargin := 0;

  if ((FRightMargin - FNonPrintableX) > 0) then
    FRightMargin := FRightMargin - FNonPrintableX
  else
    FRightMargin := FNonPrintableX;

  if ((FBottomMargin - FNonPrintableY) > 0) then
    FBottomMargin := FBottomMargin - FNonPrintableY
  else
    FBottomMargin := FNonPrintableY;
end;

procedure SimplePrintout.PrintHeader(g: Graphics);
begin
  if ((FHeader.Text <> nil) AND (FHeader.Text <> '')) then
    FHeader.Print(g, RectangleF.Create(0, 0, FPageWidth, FPageHeight));
end;

procedure SimplePrintout.PrintFooter(g: Graphics);
var h: System.Single;
begin
  if ((FFooter.Text <> nil) AND (FFooter.Text <> '')) then
  begin
    h := FFooter.GetPrintedHeight(g, FPageWidth);
    FFooter.Print(g, RectangleF.Create(0,(FPageHeight - h), FPageWidth, h));
  end;
end;

procedure SimplePrintout.PrintBorders(g: Graphics);
var
  p: Pen;
  h: System.Single;
  w: System.Single;
  y: System.Single;
  x: System.Single;
  b: SolidBrush;
begin
  b := SolidBrush.Create(FOuterColor);
  g.FillRectangle(b, 0, 0, FPageWidth, FPageHeight);
  b.Color := FInnerColor;
  g.FillRectangle(b, FLeftMargin,
                     FTopMargin,
                     FPageWidth - FLeftMargin - FRightMargin,
                     FPageHeight - FTopMargin - FBottomMargin);

  if (FOuterBorderPixels > 0) then
  begin
    p := Pen.Create(FOuterBorderColor, FOuterBorderPixels);
    g.DrawRectangle(p, 0, 0, FPageWidth, FPageHeight);
  end;

  if (FInnerBorderPixels > 0) then
  begin
    x := FLeftMargin - FInnerBorderPixels;
    y := FTopMargin - FInnerBorderPixels;
    w := FPageWidth - FLeftMargin - FRightMargin + 2 * FInnerBorderPixels;
    h := FPageHeight - FTopMargin - FBottomMargin + 2 * FInnerBorderPixels;

    if (x < 0) then   x := 0;
    if (y < 0) then   y := 0;
    if (w < 0) then   w := 0;
    if (h < 0) then   h := 0;

    p := Pen.Create(FInnerBorderColor, FInnerBorderPixels);
    g.DrawRectangle(p, x, y, w, h);
  end;
end;

procedure SimplePrintout.PaintInchGrid(g: Graphics);
{$ifdef DEBUG_MODE}
var i: Integer;
{$endif}
begin
{$ifdef DEBUG_MODE}
  // This function is handy for debugging;
  // it simply prints a 1" grid on the page
  //
  for i := 0 to 10 do
  begin
    g.DrawLine(Pens.Black, (i*g.DpiX), 0, (i*g.DpiX), (11 * g.DpiY));
    g.DrawLine(Pens.Black, 0, (i * g.DpiY), (11 * g.DpiX), (i * g.DpiY));
  end;
  g.DrawLine(Pens.Black, 0, 0, (3 * g.DpiX), (3 * g.DpiY));
{$endif}
end;

procedure SimplePrintout.pd_PrintPage(sender: System.Object; ev: PrintPageEventArgs);
var
  r: RectangleF;
  ua: UserPrintPageEventArgs;
  pageBreak: Boolean;
  band: TBand;
  g: Graphics;
begin
  if (NOT FStarted) then Exit; // printout aborted

  ua := UserPrintPageEventArgs.Create;
  ua.PageParms := ev;
  ua.PageNo    := FPageNo;

  if Assigned(FPrintPage) then  FPrintPage(Self, ua);

  if ua.PrintoutLastPage then FStarted := FALSE;

  g := ev.Graphics;
  g.PageUnit := GraphicsUnit.Pixel;
  UpdatePageMetrics(ev.PageBounds, g.DpiX, g.DpiY);

  if NOT (ua.PageHandledByUser OR ua.BackgroundHandledByUser) then
    PrintBorders(g);

  if NOT ua.HeaderHandledByUser then
    PrintHeader(g);

  if NOT ua.PageHandledByUser then
  begin
    SetPenPos(FLeftMargin, FTopMargin);

    pageBreak := False;
    while NOT pageBreak do
    begin
      band := (FBandQueue.Peek() as TBand);

      if band.EjectPageOnly then
      begin
        (FBandQueue.Dequeue() as TBand).Free;
        pageBreak := True;
      end
      else begin
        r := RectangleF.Create(FLeftMargin,
                               FPenPosY,
                               (FPageWidth - FLeftMargin) - FRightMargin,
                               (FPageHeight - FPenPosY) - FBottomMargin);
        pageBreak := band.Print(g, r);
        if NOT pageBreak then
        begin
          SetPenPos(band.GetPenPos); // get the updated pen pos
          (FBandQueue.Dequeue() as TBand).Free;
          pageBreak := (FBandQueue.Count < 1);
        end;
      end;
    end;
  end;
  PaintInchGrid(g); // for debugging only

  if NOT ua.FooterHandledByUser then  PrintFooter(g);
  Inc(FPageNo);

  ev.HasMorePages := (FBandQueue.Count > 0) AND FStarted;
end;

procedure SimplePrintout.WriteString;
begin
  if NOT FStarted then
    raise SimplePrintoutException.Create('You must call BeginPrintout() before WriteLine()');
  FBandQueue.Enqueue(FBand.Clone(''));
end;

procedure SimplePrintout.WriteString(s: String);
begin
  if NOT FStarted then
    raise SimplePrintoutException.Create('You must call BeginPrintout() before WriteLine()');
  FBandQueue.Enqueue(FBand.Clone(s));
end;

procedure SimplePrintout.PageBreak;
begin
  if NOT FStarted then
    raise SimplePrintoutException.Create('You must call BeginPrintout() before PageBreak()');
  FBandQueue.Enqueue(FBand.PageBreakClone);
end;

procedure SimplePrintout.AbortPrintout;
begin
  FStarted := False;
  while (FBandQueue.Count > 0) do
    (FBandQueue.Dequeue() as TBand).Free;
end;

procedure SimplePrintout.BeginPrintout;
begin
  FStarted := True;
  FPageNo := 1;
end;

function SimplePrintout.EndPrintout: Integer; // returns page count
var MyPrinter: PrintDocument;
begin
  Result := 0;
  if (FBandQueue.Count < 1) then Exit;
  MyPrinter := PrintDocument.Create;

  try
    if Assigned(FCreatePrintDocument) then
      FCreatePrintDocument(Self, MyPrinter);

    Include(MyPrinter.PrintPage, pd_PrintPage);
    MyPrinter.DefaultPageSettings.Margins := Margins.Create(0,0,0,0);
    if (NOT MyPrinter.PrinterSettings.IsValid) then
      raise SimplePrintoutException.Create('Invalid printer name specified');
    MyPrinter.Print();
  finally
    AbortPrintout(); // simple clean up
    MyPrinter.Dispose();
  end;
  Result := FPageNo - 1;
end;

function SimplePrintout.get_FooterText: string;
begin
  Result := FFooter.Text;
end;

function SimplePrintout.get_HeaderText: string;
begin
  Result := FHeader.Text;
end;

procedure SimplePrintout.set_NonPrintableGutter(Value: System.Single);
begin
  if (value > 0) then
    Self.FGutterInches := value
  else
    Self.FGutterInches := 0
end;

procedure SimplePrintout.set_TopMargin(Value: System.Single);
begin
  if (value > 0) then
    Self.FTopMarginInches := value
  else
    Self.FTopMarginInches := 0;
end;

procedure SimplePrintout.set_LeftMargin(Value: System.Single);
begin
  if (value > 0) then
    Self.FLeftMarginInches := value
  else
    Self.FLeftMarginInches := 0;
end;

procedure SimplePrintout.set_RightMargin(Value: System.Single);
begin
  if (value > 0) then
    Self.FRightMarginInches := value
  else
    Self.FRightMarginInches := 0;
end;

procedure SimplePrintout.set_BottomMargin(Value: System.Single);
begin
  if (value > 0) then
    Self.FBottomMarginInches := value
  else
    Self.FBottomMarginInches := 0;
end;

procedure SimplePrintout.set_FooterText(Value: string);
begin
  FFooter.Text := value;
end;

procedure SimplePrintout.set_HeaderText(Value: string);
begin
  FHeader.Text := value;
end;

procedure SimplePrintout.set_InnerBorderPixels(Value: Integer);
begin
  if (value > 0) then
    Self.FInnerBorderPixels := value
  else
    Self.FInnerBorderPixels := 0;
end;

procedure SimplePrintout.set_OuterBorderPixels(Value: Integer);
begin
  if (value > 0) then
    Self.FOuterBorderPixels := value
  else
    Self.FOuterBorderPixels := 0;
end;
{$ENDREGION}

{$REGION 'TBand class'}
constructor TBand.Create;
begin
  inherited Create;
  Init;
  Self.FTextFont := nil;
end;

constructor TBand.Create(fontName: String; fontSize: System.Single);
begin
  inherited Create;
  Init;
  Self.FTextFont := Font.Create(fontName, fontSize);
end;

procedure TBand.Init;
begin
  Self.FTextAlignment := BandAlignment.baLeft;
  Self.FForeColor := Color.Black;
  Self.FBackColor := Color.White;
  Self.FBorderColor := Color.Black;
  Self.FTextIndentTop := 0.05;
  Self.FTextIndentLeft := 0.05;
  Self.FTextIndentRight := 0.05;
  Self.FTextIndentBottom := 0.05;
end;

function TBand.Clone(s: String; ejectOnly: Boolean): TBand;
VAR b: TBand;
begin
  b := TBand.Create;
  if ejectOnly then
    b.FEjectPageOnly := True
  else begin
    b.FText := s;
    b.FEjectPageOnly := Self.FEjectPageOnly;
    b.FTextFont := (Font(Self.FTextFont.Clone));
    b.FTextAlignment := Self.FTextAlignment;
    b.FBackColor := Self.FBackColor;
    b.FForeColor := Self.FForeColor;
    b.FBorderColor := Self.FBorderColor;
    b.FBorderPixelSize := Self.FBorderPixelSize;
    b.FTransparent := Self.FTransparent;

    b.FIndentLeft := Self.FIndentLeft;
    b.FIndentRight := Self.FIndentRight;
    b.FIndentTop := Self.FIndentTop;
    b.FIndentBottom := Self.FIndentBottom;
    b.FTextIndentLeft := Self.FTextIndentLeft;
    b.FTextIndentRight := Self.FTextIndentRight;
    b.FTextIndentTop := Self.FTextIndentTop;
    b.FTextIndentBottom := Self.FTextIndentBottom;
  end;
  Result := b;
end;

function TBand.GetTextHeight: System.Single;
begin
  Result := Self.FTextFont.GetHeight(Self.FGraphics);
end;

function TBand.GetTextWidth(s: String): System.Single;
var
  r: array of Region;
  sf: StringFormat;
  cr: CharacterRange;
begin
  // MeasureString() does not give us
  // the true pixel width of the string,
  // so we have to use a magic trick here...

  Result := 0;

  if (s.Length < 1) then Exit;

  cr := CharacterRange.Create(0, s.Length);
  sf := StringFormat.Create;
  sf.SetMeasurableCharacterRanges([cr]);
  r := Self.FGraphics.MeasureCharacterRanges(s, Self.FTextFont,
                      RectangleF.Create(0, 0, (2*Self.FWidth), (2 * Self.FHeight)), sf);
  Result := r[0].GetBounds(Self.FGraphics).Width;
end;

function TBand.DsPadding(s: String): System.Single;
var
  units2: System.Single;
  units: System.Single;
begin
  // Just as MeasureString() does not give us
  // the true pixel width of the string,
  // DrawString() also pads the string, adding
  // white space before and after the actual characters
  // so we are trying to remove this padding...

  Result := 0;
  if (s.Length > 0) then
  begin
    units := Self.FGraphics.MeasureString(s, Self.FTextFont).Width;
    units2 := Self.FGraphics.MeasureString((s + s), Self.FTextFont).Width;
    units := ((2 * (units - units2)) / 2);
    if (units > 0) then  Result := units;
  end;
end;

function TBand.AddWord(s: String): Boolean;
var
  p: PointF;
  h: System.Single;
  w: System.Single;
begin
  Result := True;
  if (s = '') then Exit;

  w := GetTextWidth((Self.FLine + s));
  h := GetTextHeight;
  p := GetPenPos;
  h := p.Y - Self.FTop + h;
  if (w > Self.FWidth) then
  begin
    if (Self.FLine.Trim = '') then
    begin
      // this is one long word... It does not fit
      // in a line, so we simply chop it off
      //
      w := Self.FWidth;
      while ((s.Length > 0) and (GetTextWidth((Self.FLine + s)) > w)) do
        s := s.Remove((s.Length - 1), 1);
    end
    else
      NewLine;
  end;
  Self.FLine := Self.FLine + s;
  Result := (h <= Self.FHeight);
end;

function CreateSplit(s: String): System.Array;
var
  split: System.Array;
  delimStr: string;
  i: Integer;
  trailing: String;
  leading: String;
begin
  leading := '';
  trailing := '';

  i := 1;
  while (Length(s) >= i) AND (s[i] = ' ') do
  begin
    leading := leading + ' ';
    Inc(i);
  end;

  i := Length(s);
  while (i > 0) AND (s[i] = ' ') do
  begin
    trailing := trailing + ' ';
    Dec(i);
  end;

  delimStr := ' ';
  split := s.Split(delimStr.ToCharArray);
  i := split.Length;
  if (i > 0) then
  begin
    leading := leading + String(split.GetValue(0));
    split.SetValue(leading, 0);
    trailing := String(split.GetValue(i-1)) + trailing;
    split.SetValue(trailing, i-1);
  end;
  Result := split;
end;

function TBand.PrintJustified(line: String; align: BandAlignment): Boolean;
var
  b: SolidBrush;
  i: Integer;
  w: System.Single;
  p: PointF;
  s: string;
  x: System.Array;
begin
  Result := False;

  if (align <> BandAlignment.baJustified) then Exit;
  if (line.Trim = '') then Exit;

  x := CreateSplit(line);
  if (x.Length > 1) then
  begin
    s := '';
    for i := 0 to x.Length - 1 do
      s := s + String(x.GetValue(i));

    w := GetTextWidth(s);
    w := (Self.FWidth - w);
    w := (w / (x.Length - 1));
  end
  else
  begin
    w := GetTextWidth(String(x.GetValue(0)));
    w := (Self.FWidth - w);
  end;
  if (w >= GetTextWidth(' ')) then
  begin
    p := GetPenPos;
    p.X := Self.FLeft;
    b := SolidBrush.Create(Self.FBackColor);
    PaintTextBackground(b);
    b.Color := Self.FForeColor;
    for i := 0 to Pred(x.Length) do
    begin
      s := String(x.GetValue(i));
      PaintTextForeground(s, b, (p.X - DsPadding(s)), p.Y);
      p.X := ((p.X + GetTextWidth(s)) + w);
    end;
    b.Dispose;
  end;
  Result := True;
end;

procedure TBand.PrintLine(line: String; align: BandAlignment);
var
  p: PointF;
  b: SolidBrush;
  textWidth: System.Single;
  w: System.Single;
begin
  p := GetPenPos;
  if (NOT PrintJustified(line, align)) then
  begin
    textWidth := GetTextWidth(line);

    w := FWidth - textWidth;

    case (align) of
      BandAlignment.baRight:
        w := w + FLeft;
      BandAlignment.baCentered:
        begin
          w := w / 2;
          w := w + FLeft;
        end;
      else
        w := 0;
    end;
    if (w < FLeft) then  w := FLeft;

    // draw the background and then output the string
    //
    b := SolidBrush.Create(FBackColor);
    PaintTextBackground(b);
    b.Color := FForeColor;
    PaintTextForeground(line, b, w-DsPadding(line), p.Y);
    b.Dispose();
  end;
end;

function TBand.NewLine: Boolean;
var
  p: PointF;
  l: Integer;
begin
  l := FLine.Length;
  if FLine.EndsWith(' ') then  FLine := FLine.Remove((l - 1), 1);

  PrintLine(Self.FLine, Self.FTextAlignment);
  Self.FLine := '';
  p := GetPenPos;
  p.Y := p.Y + GetTextHeight;
  SetPenPos(Self.FLeft, p.Y);
  Result := ((p.Y - Self.FTop) > Self.FHeight);
end;

procedure TBand.InchesToPixels(DpiX: System.Single; DpiY: System.Single);
begin
  FLeftI    := (DpiX * FIndentLeft);
  FRightI   := (DpiX * FIndentRight);
  FLeftTI   := (DpiX * FTextIndentLeft);
  FRightTI  := (DpiX * FTextIndentRight);

  FTopI     := (DpiY * FIndentTop);
  FBottomI  := (DpiY * FIndentBottom);
  FTopTI    := (DpiY * FTextIndentTop);
  FBottomTI := (DpiY * FTextIndentBottom);
end;

procedure TBand.HandleTopIndent(b: SolidBrush);
begin
  // Paints the background before the lines
  //
  if (NOT (FSimulatedPrint OR FTransparent)) then
    FGraphics.FillRectangle(b, FLeft, FTop, FWidth, FTopTI);

  // change the area accordingly
  FTop    := FTop + FTopTI;
  FLeft   := FLeft + FLeftTI;
  FWidth  := FWidth - (FLeftTI + FRightTI);
  FHeight := FHeight - (FTopTI + FBottomTI);
end;

procedure TBand.PaintTextBackground(b: SolidBrush);
begin
  // Paints the line background for the current line
  //
  if (NOT (FSimulatedPrint OR FTransparent)) then
    FGraphics.FillRectangle(b, FLeft-FLeftTI, GetPenPos().Y,
                               FWidth+FLeftTI+FRightTI,
                               GetTextHeight());
end;

procedure TBand.PaintTextForeground(s: String; b: SolidBrush; X, Y: System.Single);
begin
  if (NOT FSimulatedPrint) then
    FGraphics.DrawString(s, FTextFont, b, X, Y);
end;

procedure TBand.HandleBottomIndent(b: SolidBrush);
var
  h: System.Single;
  y: System.Single;
  x: System.Single;
begin
  // restore the original paint area
  FTop    := FTop - FTopTI;
  FLeft   := FLeft - FLeftTI;
  FWidth  := FWidth + FLeftTI + FRightTI;
  FHeight := FHeight + FTopTI + FBottomTI;

  x := GetPenPos.X;
  y := GetPenPos.Y;
  h := ((y - Self.FTop) + Self.FBottomTI);
  if (h > Self.FHeight) then
    h := (Self.FBottomTI - (h - Self.FHeight))
  else
    h := Self.FBottomTI;

  if (NOT (FSimulatedPrint OR FTransparent)) then
    FGraphics.FillRectangle(b, FLeft, y, FWidth, h);

  SetPenPos(x, (y + Self.FBottomTI));
end;

procedure TBand.SetPenPos(x: System.Single; y: System.Single);
begin
  Self.FPenPosX := x;
  Self.FPenPosY := y;
end;

function TBand.IndexOfNewLine(s: String; var nl_len: Integer): Integer;
var
  nl2: Integer;
  nl1: Integer;
begin
  nl1 := s.IndexOf(Char(13)+Char(10)); // new line: CRLF
  nl2 := s.IndexOf(Char(10));    // new line: LF
  if (nl1 < 0) then
  begin
    nl_len := 1;
    Result := nl2;
    Exit;
  end;
  if (nl2 < 0) then
  begin
    nl_len := 2;
    Result := nl1;
    Exit;
  end;
  if (nl2 < nl1) then
  begin
    nl_len := 1;
    Result := nl2;
    Exit;
  end
  else
  begin
    nl_len := 2;
    Result := nl1;
    Exit;
  end;
end;

function TBand.PrintBand(s: String): Boolean;
var
  b: SolidBrush;
  nl_len: Integer;
  nl: Integer;
  x: Integer;
  pageBreak: Boolean;
begin
  b := SolidBrush.Create(Self.FBackColor);

  Self.FLine := '';
  HandleTopIndent(b);

  SetPenPos(Self.FLeft, Self.FTop);

  // Clean up soft breaks
  x := s.IndexOf(Char(236)+Char(10));
  while (x >= 0) do
  begin
    s := s.Remove(x, 2);
    s := s.Insert(x, ' ');
    x := s.IndexOf(Char(236)+Char(10));
  end;

  nl_len := 1;
  pageBreak := False;
  x := s.IndexOf(' ');
  nl := IndexOfNewLine(s, nl_len);

  // To handle wrapping of text properly,
  // we add one word at the time to a buffer
  // until NewLine() flushes the line to printer
  //
  while ((NOT pageBreak) and ((x >= 0) or (nl >= 0))) do
  begin
    if (nl >= 0) then
      if (x >= 0) then
        if (x < nl) then
          if AddWord(s.Substring(0, (x + 1))) then
            s := s.Remove(0, (x + 1))
          else
            pageBreak := True
        else
          if AddWord((s.Substring(0, nl) + ' ')) then
          begin
            s := s.Remove(0, (nl + nl_len));
            NewLine;
          end
          else
            pageBreak := True
      else
        if AddWord((s.Substring(0, nl) + ' ')) then
        begin
          s := s.Remove(0, (nl + nl_len));
          NewLine;
        end
        else
          pageBreak := True
    else
      if AddWord(s.Substring(0, (x + 1))) then
        s := s.Remove(0, (x + 1))
      else
        pageBreak := True;
    x := s.IndexOf(' ');
    nl := IndexOfNewLine(s, nl_len);
  end;

  // Handle the leftovers (partial lines)
  //
  if (NOT pageBreak) then
  begin
    AddWord(s);
    pageBreak := NewLine();
  end;

  if pageBreak then
  begin // check if the paragraph ends with a CRLF
    if (s = '') then
      pageBreak := False  // if yes, lose the orphan
    else
      Self.FText := s;
  end;

  HandleBottomIndent(b);
  b.Dispose;
  Result := pageBreak;
end;

function TBand.Clone(s: String): TBand;
begin
  Result := Clone(s, False);
end;

function TBand.PageBreakClone: TBand;
begin
  Result := Clone('', True);
end;

function TBand.GetPenPos: PointF;
begin
  Result := PointF.Create(FPenPosX, FPenPosY);
end;

function TBand.Print(g: Graphics; r: RectangleF): Boolean;
var
  p: Pen;
  EndY: System.Single;
  pageBreak: Boolean;
begin

  InchesToPixels(g.DpiX, g.DpiY);
  Self.FGraphics := g;
  Self.FTop := r.Y + Self.FTopI;
  Self.FLeft := r.X + Self.FLeftI;
  Self.FWidth := r.Width - Self.FLeftI - Self.FRightI;
  Self.FHeight := r.Height - Self.FTopI - Self.FBottomI;
  if (GetTextHeight >= (Self.FHeight - Self.FTopTI - Self.FBottomTI)) then
    pageBreak := True
  else begin
    Self.FSimulatedPrint := False;
    pageBreak := PrintBand(Self.FText);
    if (Self.FBorderPixelSize > 0) then
    begin
      EndY := (GetPenPos.Y - Self.FTop);
      if (EndY > Self.FHeight) then
        EndY := Self.FHeight;
      p := Pen.Create(Self.FBorderColor, Self.FBorderPixelSize);
      Self.FGraphics.DrawRectangle(p, Self.FLeft, Self.FTop, Self.FWidth, EndY);
      p.Dispose;
    end;
  end;
  Result := pageBreak;
end;

function TBand.GetPrintedHeight(g: Graphics; width: System.Single): System.Single;
begin
  InchesToPixels(g.DpiX, g.DpiY);
  Self.FGraphics := g;
  Self.FTop := Self.FTopI;
  Self.FLeft := Self.FLeftI;
  Self.FWidth := ((width - Self.FLeftI) - Self.FRightI);
  Self.FHeight := 1000000; // this is a very loong page...
  Self.FSimulatedPrint := True;
  PrintBand(Self.FText);
  Result := GetPenPos.Y;
end;

procedure TBand.set_BorderPixelSize(Value: Integer);
begin
  if (value >= 0) then
    Self.FBorderPixelSize := value
  else
    Self.FBorderPixelSize := 0;
end;

procedure TBand.set_IndentLeft(Value: System.Single);
begin
  if (value >= 0) then
    Self.FIndentLeft := value
  else
    Self.FIndentLeft := 0;
end;

procedure TBand.set_IndentTop(Value: System.Single);
begin
  if (value >= 0) then
    Self.FIndentTop := value
  else
    Self.FIndentTop := 0;
end;

procedure TBand.set_IndentRight(Value: System.Single);
begin
  if (value >= 0) then
    Self.FIndentRight := value
  else
    Self.FIndentRight := 0;
end;

procedure TBand.set_IndentBottom(Value: System.Single);
begin
  if (value >= 0) then
    Self.FIndentBottom := value
  else
    Self.FIndentBottom := 0;
end;

procedure TBand.set_TextIndentLeft(Value: System.Single);
begin
  if (value >= 0) then
    Self.FTextIndentLeft := value
  else
    Self.FTextIndentLeft := 0;
end;

procedure TBand.set_TextIndentRight(Value: System.Single);
begin
  if (value >= 0) then
    Self.FTextIndentRight := value
  else
    Self.FTextIndentRight := 0;
end;

procedure TBand.set_TextIndentTop(Value: System.Single);
begin
  if (value >= 0) then
    Self.FTextIndentTop := value
  else
    Self.FTextIndentTop := 0;
end;

procedure TBand.set_TextIndentBottom(Value: System.Single);
begin
  if (value >= 0) then
    Self.FTextIndentBottom := value
  else
    Self.FTextIndentBottom := 0;
end;
{$ENDREGION}

end.
