context: //
  // DO NOT REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  //
  // @Authors:
  //       timop
  //
  // Copyright 2004-2013 by OM International
  //
  // This file is part of OpenPetra.org.
  //
  // OpenPetra.org is free software: you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  //
  // OpenPetra.org is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with OpenPetra.org.  If not, see <http://www.gnu.org/licenses/>.
  //
  using System;
  using System.IO;
  using Ict.Common.Printing;
  using System.Drawing;
  using System.Drawing.Printing;
  using System.Collections.Generic;
  
  namespace Ict.Common.Printing
  {
      /// <summary>
      /// The TGfxPrinter class helps to print to a System.Drawing.Printing.PrintDocument.
      ///
      /// This means you can use several fonts, etc.
      ///
      /// see also
      /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwinforms/html/printwinforms.asp
      /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01282003.asp
      /// </summary>
      public class TGfxPrinter : TPrinter
      {
          /// <summary>
          /// the graphical document
          /// </summary>
          protected System.Drawing.Printing.PrintDocument FDocument;
  
          /// we have some different behaviour when printing the columns of a report and when printing a form letter
          public enum ePrinterBehaviour
          {
              /// printing the columns of a report, we can make text fit
              eReport,
              /// printing a form letter, we need to print everything, comma is not a whitespace
              eFormLetter
          };
  
          /// <summary>
          /// we have some different behaviour when printing the columns of a report (can make text fit) and when printing a form letter
          /// </summary>
          public ePrinterBehaviour FPrinterBehaviour = ePrinterBehaviour.eReport;
  
          /// printing should be started at this position for each line
          public float FPageXPos;
  
          /// space available for printing
          public float FPageWidthAvailable;
  
          /// todoComment
          public System.Drawing.Font FDefaultFont;
  
          /// todoComment
          public System.Drawing.Font FDefaultBoldFont;
  
          /// todoComment
          public System.Drawing.Font FHeadingFont;
  
          /// fonts for printing barcodes, using Code 128
          public System.Drawing.Font FBarCodeFont;
  
          /// todoComment
          public System.Drawing.Font FSmallPrintFont;
  
          /// todoComment
          public System.Drawing.Font FBiggestLastUsedFont;
          private StringFormat FLeft;
          private StringFormat FRight;
          private StringFormat FCenter;
          private System.Drawing.Pen FBlackPen;
  
          /// <summary>these values are set by PrintPage</summary>
          protected float FLinesPerPage;
  
          /// <summary>todoComment</summary>
          protected PrintPageEventArgs FEv;
  
          /// <summary>the document to print into</summary>
          public PrintDocument Document
          {
              get
              {
                  return FDocument;
              }
          }
  
  
          /// <summary>
          /// sets the orientation of the page
          ///
          /// </summary>
          /// <returns>void</returns>
          public TGfxPrinter(System.Drawing.Printing.PrintDocument ADocument, ePrinterBehaviour APrinterBehaviour) : base()
          {
              FDocument = ADocument;
              FPrinterBehaviour = APrinterBehaviour;
              FSmallPrintFont = new System.Drawing.Font("Arial", 6);
              FDefaultFont = new System.Drawing.Font("Arial", 8);
              FDefaultBoldFont = new System.Drawing.Font("Arial", 8, FontStyle.Bold);
              FHeadingFont = new System.Drawing.Font("Arial", 10, FontStyle.Bold);
  
              // using GPL Font Code 128 from Grand Zebu http://grandzebu.net/
              FBarCodeFont = new System.Drawing.Font("Code 128", 35, FontStyle.Regular);
  
              FBiggestLastUsedFont = FDefaultFont;
              FRight = new StringFormat(StringFormat.GenericDefault);
              FRight.Alignment = StringAlignment.Far;
              FLeft = new StringFormat(StringFormat.GenericDefault);
              FLeft.Alignment = StringAlignment.Near;
              FCenter = new StringFormat(StringFormat.GenericDefault);
              FCenter.Alignment = StringAlignment.Center;
          }
  
          /// <summary>
          /// todoComment
          /// </summary>
          public override void Init(eOrientation AOrientation, TPrinterLayout APrinterLayout, eMarginType AMarginType)
          {
              base.Init(AOrientation, APrinterLayout, AMarginType);
              SetPageSize();
  
              try
              {
                  if (AOrientation == eOrientation.ePortrait)
                  {
                      FDocument.DefaultPageSettings.Margins.Left = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                      FDocument.DefaultPageSettings.Margins.Right = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                      FDocument.DefaultPageSettings.Margins.Top = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                      FDocument.DefaultPageSettings.Margins.Bottom = Convert.ToInt32(Cm2Inch(1) * 100);
                  }
                  else if (AOrientation == eOrientation.eLandscape)
                  {
                      FDocument.DefaultPageSettings.Margins.Left = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                      FDocument.DefaultPageSettings.Margins.Right = Convert.ToInt32(Cm2Inch(1) * 100);
                      FDocument.DefaultPageSettings.Margins.Top = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                      FDocument.DefaultPageSettings.Margins.Bottom = Convert.ToInt32(Cm2Inch(0.5f) * 100);
                  }
              }
              catch (Exception)
              {
              }
              // Associate the eventhandling method with the
              // document's PrintPage event.
              FDocument.PrintPage += new PrintPageEventHandler(this.PrintPage);
              FDocument.BeginPrint += new PrintEventHandler(this.BeginPrint);
              FDocument.EndPrint += new PrintEventHandler(this.EndPrint);
  
              FDocument.DefaultPageSettings.Landscape = (FOrientation == eOrientation.eLandscape);
          }
  
          void BeginPrint(object ASender, PrintEventArgs AEv)
          {
              if ((FNumberOfPages == 0) && (CurrentPageNr != 0))
              {
                  FNumberOfPages = CurrentPageNr;
              }
  
              CurrentPageNr = 0;
              CurrentDocumentNr = 1;
              FPrinterLayout.StartPrintDocument();
          }
  
          /// <summary>
          /// todoComment
          /// </summary>
          /// <param name="ASender"></param>
          /// <param name="AEv"></param>
          private void EndPrint(object ASender, PrintEventArgs AEv)
          {
              if ((CurrentPageNr != 0) && (FNumberOfPages == 0))
              {
                  FNumberOfPages = CurrentPageNr;
              }
          }
  
          private SortedList <string, Font>FFontCache = new SortedList <string, Font>();
  
          /// <summary>
          /// get the font that is associated with the enum value.
          /// this way we do not need to create a new font each time
          /// </summary>
          /// <param name="AFont"></param>
          /// <returns></returns>
          protected System.Drawing.Font GetFont(eFont AFont)
          {
              System.Drawing.Font ReturnValue;
              ReturnValue = FDefaultFont;
  
              switch (AFont)
              {
                  case eFont.eDefaultFont:
                      ReturnValue = FDefaultFont;
                      break;
  
                  case eFont.eDefaultBoldFont:
                      ReturnValue = FDefaultBoldFont;
                      break;
  
                  case eFont.eHeadingFont:
                      ReturnValue = FHeadingFont;
                      break;
  
                  case eFont.eBarCodeFont:
                      ReturnValue = FBarCodeFont;
                      break;
  
                  case eFont.eSmallPrintFont:
                      ReturnValue = FSmallPrintFont;
                      break;
              }
  
              if (CurrentRelativeFontSize != 0)
              {
                  float FontSize = ReturnValue.SizeInPoints + CurrentRelativeFontSize;
  
                  if (FontSize <= 0.0f)
                  {
                      FontSize = 0.5f;
                  }
  
                  string id = ReturnValue.FontFamily.ToString() + FontSize.ToString() + ReturnValue.Style.ToString();
  
                  if (!FFontCache.ContainsKey(id))
                  {
                      FFontCache.Add(id, new Font(ReturnValue.FontFamily, FontSize, ReturnValue.Style));
                  }
  
                  ReturnValue = FFontCache[id];
              }
  
              return ReturnValue;
          }
  
          /// <summary>
          /// update the biggest last used font for the next line feed
          /// </summary>
          /// <param name="AFont"></param>
          protected virtual bool UpdateBiggestLastUsedFont(eFont AFont)
          {
              if (GetFont(AFont).GetHeight(FEv.Graphics) > FBiggestLastUsedFont.GetHeight(FEv.Graphics))
              {
                  FBiggestLastUsedFont = GetFont(AFont);
                  return true;
              }
  
              return false;
          }
  
          private System.Drawing.StringFormat GetStringFormat(eAlignment AAlign)
          {
              System.Drawing.StringFormat ReturnValue;
              ReturnValue = FLeft;
  
              switch (AAlign)
              {
                  case eAlignment.eDefault:
                      ReturnValue = FLeft;
                      break;
  
                  case eAlignment.eLeft:
                      ReturnValue = FLeft;
                      break;
  
                  case eAlignment.eRight:
                      ReturnValue = FRight;
                      break;
  
                  case eAlignment.eCenter:
                      ReturnValue = FCenter;
                      break;
              }
  
              return ReturnValue;
          }
  
          /// <summary>
          /// prints into the current line, aligned x position
          ///
          /// </summary>
          public override Boolean PrintString(String ATxt, eFont AFont, eAlignment AAlign)
          {
              RectangleF rect;
  
              rect = new RectangleF(FLeftMargin, CurrentYPos, FWidth, GetFont(AFont).GetHeight(FEv.Graphics));
  
              if (PrintingMode == ePrintingMode.eDoPrint)
              {
                  ATxt = GetFittedText(ATxt, AFont, rect.Width);
                  FEv.Graphics.DrawString(ATxt, GetFont(AFont), Brushes.Black, rect, GetStringFormat(AAlign));
              }
  
              return (ATxt != null) && (ATxt.Length != 0);
          }
  
          /// <summary>
          /// prints into the current line, absolute x position
          ///
          /// </summary>
          public override Boolean PrintString(String ATxt, eFont AFont, float AXPos)
          {
              if (PrintingMode == ePrintingMode.eDoPrint)
              {
                  FEv.Graphics.DrawString(ATxt, GetFont(AFont), Brushes.Black, AXPos, CurrentYPos);
              }
  
              return (ATxt != null) && (ATxt.Length != 0);
          }
  
          /// <summary>
          /// prints into the current line, absolute x position with width and alignment
          /// </summary>
          /// <returns>true if something was printed</returns>
          public override Boolean PrintString(String ATxt, eFont AFont, float AXPos, float AWidth, eAlignment AAlign)
          {
              RectangleF rect = new RectangleF(AXPos, CurrentYPos, AWidth, GetFont(AFont).GetHeight(FEv.Graphics));
  
              if (PrintingMode == ePrintingMode.eDoPrint)
              {
                  StringFormat f = GetStringFormat(AAlign);
                  f.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
  
                  if (FPrinterBehaviour == ePrinterBehaviour.eReport)
                  {
                      ATxt = GetFittedText(ATxt, AFont, rect.Width);
                  }
  
                  FEv.Graphics.DrawString(ATxt, GetFont(AFont), Brushes.Black, rect, f);
              }
  
              return (ATxt != null) && (ATxt.Length != 0);
          }
  
          /// <summary>
          /// Check if the text will fit into the given width. If yes, the text will be returned.
          /// If no, the text will be shortened and a "..." will be added to indicate that some text is missing.
          /// </summary>
          /// <param name="ATxt">the original text</param>
          /// <param name="AFont">the font</param>
          /// <param name="AWidth">the space available for the text in cm</param>
          /// <returns>The input text. Either unmodified or shortened.</returns>
          protected String GetFittedText(String ATxt, eFont AFont, float AWidth)
          {
              String ReturnValue = "";
  
              if (GetWidthString(ATxt, AFont) <= AWidth)
              {
                  // The whole text fits into the available space
                  ReturnValue = ATxt;
              }
              else
              {
                  // We have to cut the text
                  float WidthDotDotDot = GetWidthString("...", AFont);
                  float WidthForText = AWidth - WidthDotDotDot;
  
                  if (WidthForText <= 0.0)
                  {
                      // only space for ...
                      ReturnValue = "...";
                  }
                  else
                  {
                      ReturnValue = CutTextToLength(ATxt, AFont, WidthForText) + "...";
                  }
              }
  
              return ReturnValue;
          }
  
          /// <summary>
          /// Cuts a given text so it will not extend the given width.
          /// </summary>
          /// <param name="ATxt">The text to cut</param>
          /// <param name="AFont">The font used</param>
          /// <param name="AWidth">The available length for the text in cm.</param>
          /// <returns>The maximum part of the text that will not extend the width.</returns>
          protected String CutTextToLength(String ATxt, eFont AFont, float AWidth)
          {
              String ReturnValue = "";
  
              for (int Counter = 1; Counter <= ATxt.Length; ++Counter)
              {
                  float length = GetWidthString(ATxt.Substring(0, Counter), AFont);
  
                  if (length <= AWidth)
                  {
                      ReturnValue = ATxt.Substring(0, Counter);
                  }
                  else
                  {
                      break;
                  }
              }
  
              return ReturnValue;
          }
  
          /// <summary>
          /// word wrap text, return the number of characters that fit the line width;
          /// if the first word does not fit the space available, wrap the word in itself
          /// </summary>
          /// <param name="ATxt"></param>
          /// <param name="AFont"></param>
          /// <param name="AWidth"></param>
          /// <returns>returns the length of the first word; this is needed if even the first word does not fit</returns>
          protected Int32 GetTextLengthThatWillFit(String ATxt, eFont AFont, float AWidth)
          {
              // see also http://www.codeguru.com/vb/gen/vb_misc/printing/article.php/c11233
              string buffer = ATxt;
              string fittingText = "";
  
              char[] whitespace = new char[] {
                  ' ', '\t', '\r', '\n'
              };
  
              if (FPrinterBehaviour == ePrinterBehaviour.eReport)
              {
                  // whitespace setting for ePrinterBehaviour.eReport
                  whitespace = new char[] {
                      ' ', '\t', '\r', '\n', '-'
                  };
              }
  
              string previousWhitespaces = "";
              string nextWhitespaces = "";
              Int32 result = 0;
  
              while (GetWidthString(fittingText, AFont) < AWidth)
              {
                  result = fittingText.Length;
  
                  if (buffer.Length == 0)
                  {
                      return ATxt.Length;
                  }
  
                  Int32 indexWhitespace = buffer.IndexOfAny(whitespace);
  
                  if (indexWhitespace > -1)
                  {
                      string nextWord = buffer.Substring(0, indexWhitespace);
                      fittingText += previousWhitespaces + nextWhitespaces + nextWord;
  
                      nextWhitespaces = "";
  
                      // sometimes there are forced whitespaces (eg &nbsp;, already replaced by spaces etc)
                      // consider them as a word
                      previousWhitespaces = buffer[indexWhitespace].ToString();
  
                      while (indexWhitespace < buffer.Length - 1 && buffer.Substring(indexWhitespace + 1).IndexOfAny(whitespace) == 0)
                      {
                          indexWhitespace++;
                          nextWhitespaces += buffer[indexWhitespace];
                      }
  
                      buffer = buffer.Substring(indexWhitespace + 1);
                  }
                  else // no whitespace left
                  {
                      // store length of result without the rest of the text, because it might not fit;
                      // but include whitespaces already, avoid printing them at start of new line
                      result = (fittingText + previousWhitespaces).Length;
                      fittingText += previousWhitespaces + buffer;
                      previousWhitespaces = "";
                      buffer = "";
                  }
              }
  
              if (result > 0)
              {
                  result += previousWhitespaces.Length;
              }
  
              if (result == 0)
              {
                  // the first word is already too long for the assigned space
                  // see how many characters would fit
                  buffer = fittingText;
                  fittingText = "";
  
                  while (GetWidthString(fittingText, AFont) < AWidth && buffer.Length > 0)
                  {
                      fittingText += buffer[0];
                      buffer = buffer.Substring(1);
                  }
  
                  return fittingText.Length - 1;
              }
  
              return result;
          }
  
          /// <summary>
          /// prints into the current line, absolute x position with width and alignment
          /// this method uses FCurrentXPos and FCurrentYPos to be able to continue a paragraph
          /// uses FCurrentXPos and FCurrentYPos to know where to start to print, and also sets
          /// valid values in those member variables
          /// </summary>
          /// <returns>true if any text was printed</returns>
          public override bool PrintStringWrap(String ATxt, eFont AFont, float AXPos, float AWidth, eAlignment AAlign)
          {
              int PreviousLength = -1;
  
              while (ATxt.Length > 0)
              {
                  Int32 length = -1;
  
                  PreviousLength = ATxt.Length;
  
                  if (FPrinterBehaviour == ePrinterBehaviour.eFormLetter)
                  {
                      length = GetTextLengthThatWillFit(ATxt, AFont, AXPos + AWidth - CurrentXPos);
                  }
                  else if (FPrinterBehaviour == ePrinterBehaviour.eReport)
                  {
                      length = GetTextLengthThatWillFit(ATxt, AFont, AWidth);
                  }
  
                  if (FCurrentState.FNoWrap)
                  {
                      length = ATxt.Length;
                  }
  
                  if (length > 0)
                  {
                      string toPrint = ATxt.Substring(0, length);
                      ATxt = ATxt.Substring(length);
  
                      UpdateBiggestLastUsedFont(AFont);
  
                      if (FPrinterBehaviour == ePrinterBehaviour.eFormLetter)
                      {
                          PrintString(toPrint, AFont, CurrentXPos, AWidth, AAlign);
                      }
                      else if (FPrinterBehaviour == ePrinterBehaviour.eReport)
                      {
                          PrintString(toPrint, AFont, AXPos, AWidth, AAlign);
                      }
  
                      CurrentXPos += GetWidthString(toPrint, AFont);
  
                      if (ATxt.Length > 0)
                      {
                          // there is still more to come, we need a new line
                          CurrentXPos = AXPos;
                          LineFeed(); // will use the biggest used font, and reset it
                      }
                  }
                  else if ((ATxt.Length > 0) && (CurrentXPos != AXPos))
                  {
                      // the first word did not fit the space; needs a new line
                      CurrentXPos = AXPos;
                      LineFeed();
                  }
  
                  if (ATxt.Length == PreviousLength)
                  {
                      TLogging.Log("No space for " + ATxt);
                      return false;
                  }
              }
  
              if (FPrinterBehaviour == ePrinterBehaviour.eReport)
              {
                  CurrentXPos = 0;
              }
  
              return true;
          }
  
          /// <summary>
          /// This function uses the normal DrawString function to print into a given space.
          /// It will return whether the text did fit that space or not.
          ///
          /// </summary>
          /// <param name="ATxt"></param>
          /// <param name="AFont"></param>
          /// <param name="AXPos"></param>
          /// <param name="AWidth"></param>
          /// <param name="AAlign"></param>
          /// <returns></returns>
          public override Boolean PrintStringAndFits(String ATxt, eFont AFont, float AXPos, float AWidth, eAlignment AAlign)
          {
              PrintString(ATxt, AFont, AXPos, AWidth, AAlign);
              return GetWidthString(ATxt, AFont) <= AWidth;
          }
  
          /// <summary>
          /// prints into the current line, into the given column
          /// </summary>
          /// <param name="ATxt"></param>
          /// <param name="AFont"></param>
          /// <returns>Return the width of the string, if it was printed in one line, using the given Font</returns>
          public override float GetWidthString(String ATxt, eFont AFont)
          {
              return FEv.Graphics.MeasureString(ATxt, GetFont(AFont)).Width;
          }
  
          /// <summary>
          /// Draws a line, either above or below the current text line
          /// the font is required to get the height of the row
          /// </summary>
          /// <param name="AXPos1"></param>
          /// <param name="AXPos2"></param>
          /// <param name="ALinePosition"></param>
          /// <param name="AFont"></param>
          /// <returns></returns>
          public override Boolean DrawLine(float AXPos1, float AXPos2, eLinePosition ALinePosition, eFont AFont)
          {
              float YPos;
  
              YPos = CurrentYPos;
  
              if (ALinePosition == eLinePosition.eBelow)
              {
                  YPos = CurrentYPos + GetFont(AFont).GetHeight(FEv.Graphics);
              }
              else if (ALinePosition == eLinePosition.eAbove)
              {
                  YPos = CurrentYPos;
              }
  
              if (AXPos1 != LeftMargin)
              {
                  // lines above/below columns should not touch
  
gt:                 AXPos1 = AXPos1 + Cm(0.3f);