Simple Text Printing

Simple Text Printing

An example that shows the process of printing a plain text file with word wrap and page numbering.

Printing in VB .Net is often seem as a complex procedure.  Some options exist to simplify it, such as the print option available for a rich text box.  But for many pruposes it is still a difficult procedure that requires a number of steps and some very careful processing.

This example demonstrates one way of ‘wrapping’ the print process in a routine that provides some basic functionality to enable plain text files to be printed with word wrap and page numbering.  No other formatting is provided, but additional features (such as a page header, different formatting for the footer) could easily be added.  The ‘wrapper’ class can then be incorporated into any program that needs basic print functionality, and implemented with just a few lines of code – create the class instance, set the text, set the font and print.

No design-time controls are required for this example.  Five buttons are created in code – Select text, Select font, Print preview, Page setup, and Print.  When the class is used in an application, Print preview, Page setup and Print would probably be menu options, while the text would be provided in code, and Font select could be a user option or simply hard coded.

Create a new windows form project and paste the code listed below.

Imports System.Text
Imports System.Collections
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Printing

Public Class Form1

Dim PrintDocument As MyPrintDocument = New MyPrintDocument()

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Dim B0 As Button = New Button
  Me.Controls.Add(B0)
  B0.Text = "Get Text File"
  B0.Left = 20
  B0.Top = 20
  AddHandler B0.Click, AddressOf OnGetTextFile
  Dim B1 As Button = New Button
  Me.Controls.Add(B1)
  B1.Text = "Select Font"
  B1.Left = 120
  B1.Top = 20
  AddHandler B1.Click, AddressOf OnSelectFont
  Dim B2 As Button = New Button
  Me.Controls.Add(B2)
  B2.Text = "Preview"
  B2.Left = 220
  B2.Top = 20
  AddHandler B2.Click, AddressOf OnFilePrintPreview
  Dim B3 As Button = New Button
  Me.Controls.Add(B3)
  B3.Text = "Page Setup"
  B3.Left = 320
  B3.Top = 20
  AddHandler B3.Click, AddressOf OnFilePageSetup
  Dim B4 As Button = New Button
  Me.Controls.Add(B4)
  B4.Text = "Print"
  B4.Left = 420
  B4.Top = 20
  AddHandler B4.Click, AddressOf OnFilePrint
End Sub

Private Sub OnGetTextFile(ByVal sender As [Object], ByVal e As EventArgs)
  Dim TD As OpenFileDialog = New OpenFileDialog
  TD.Filter = "Text Files|*.txt|All Files|*.*"
  If TD.ShowDialog <> Windows.Forms.DialogResult.Cancel Then
    PrintDocument.Text = System.IO.File.ReadAllText(TD.FileName)
  End If
End Sub

Private Sub OnSelectFont(ByVal sender As [Object], ByVal e As EventArgs)
  Dim FD As New FontDialog()
  FD.Font = Me.Font
  If FD.ShowDialog() <> Windows.Forms.DialogResult.Cancel Then
    PrintDocument.Font = FD.Font
  End If
End Sub

Private Sub OnFilePrint(ByVal sender As [Object], ByVal e As EventArgs)
  Dim PD As New PrintDialog()
  PD.Document = PrintDocument
  PD.ShowDialog()
End Sub

Private Sub OnFilePrintPreview(ByVal sender As [Object], ByVal e As EventArgs)
  Dim PPD As New PrintPreviewDialog()
  PPD.Document = PrintDocument
  PPD.ShowDialog()
End Sub

Private Sub OnFilePageSetup(ByVal sender As [Object], ByVal e As EventArgs)
  Dim PSD As New PageSetupDialog()
  PSD.Document = PrintDocument
  PSD.ShowDialog()
End Sub

End Class

Class MyPrintDocument
Inherits PrintDocument

Const EndOfText As Integer = -1
Const NewLine As Integer = -2

Private myText As String = "" 'Local copy of the text
Private myFont As Font = Nothing 'Local copy of the font
Private myOffset As Integer = 0 'Pointer to current text position 
Private myPageNo As Integer = 0 'Current page number

Public Sub New()
End Sub

Public Property Text() As [String]
  Get
    Return myText
  End Get
  Set(ByVal value As [String])
    myText = value
  End Set
End Property

Public Property Font() As Font
  Get
    Return myFont
  End Get
  Set(ByVal value As Font)
    myFont = value
  End Set
End Property

Protected Overrides Sub OnBeginPrint(ByVal e As PrintEventArgs)
  myOffset = 0 'Start at beginning of text
  myPageNo = 1 'Start at page 1 (todo: initialise from page setup)
  MyBase.OnBeginPrint(e)
End Sub

Private Function NextCharIsNewLine() As [Boolean]
  Dim NL As Integer = Environment.NewLine.Length
  Dim TL As Integer = myText.Length - myOffset

  If TL < NL Then
    'Not enough characters left for a newline
    Return False
  End If

  Dim NewLine As String = Environment.NewLine
  For I As Integer = 0 To NL - 1
    If myText(myOffset + I) <> NewLine(I) Then
      Return False
    End If
  Next
  Return True
End Function

Private Function NextChar() As Integer
  If myOffset >= myText.Length Then
    Return EndOfText
  End If

  If NextCharIsNewLine() Then
    myOffset += Environment.NewLine.Length
    Return NewLine
  End If

  myOffset += 1
  Return AscW(myText(myOffset - 1))
End Function

Protected Overrides Sub OnPrintPage(ByVal e As PrintPageEventArgs)
  MyBase.OnPrintPage(e)

  Dim PageWidth As Single = e.MarginBounds.Width * 3.0F
  Dim PageHeight As Single = e.MarginBounds.Height * 3.0F
  Dim TextWidth As Single = 0.0F
  Dim TextHeight As Single = 0.0F
  Dim OffsetX As Single = e.MarginBounds.Left * 3.0F
  Dim OffsetY As Single = e.MarginBounds.Top * 3.0F
  Dim X As Single = OffsetX 'Horizontal print position
  Dim Y As Single = OffsetY 'Vetcial print position
  Dim Line As New StringBuilder(256)
  Dim SF As StringFormat = StringFormat.GenericTypographic
  SF.FormatFlags = StringFormatFlags.DisplayFormatControl
  SF.SetTabStops(0.0F, New Single() {300.0F})
  Dim R As RectangleF
  Dim G As Graphics = e.Graphics
  G.PageUnit = GraphicsUnit.Document
  Dim mySize As SizeF = G.MeasureString("X", myFont, 1, SF)
  Dim LineHeight As Single = mySize.Height

  ' Check that we can print at least 1 line and a footer
  If lineheight + (lineheight * 3) > pageheight Then
    G.Dispose()
    e.HasMorePages = False
    Return
  End If

  ' Get page height without footer
  PageHeight -= lineheight * 3
  ' Initialise last white space position to <none>
  Dim LastWS As Integer = -1
  ' Initialise next character to <none> 
  Dim C As Integer = EndOfText

  While True
    ' Get next character
    C = NextChar()
    ' Append the character to line if not NewLine or EoT
    If (c <> NewLine) AndAlso (c <> EndOfText) Then
      Dim ch As [Char] = Convert.ToChar(c)
      Line.Append(ch)
      ' if ch is whitespace, remember pos and continue
      If ch = " "c OrElse ch = ControlChars.Tab Then
        LastWS = Line.Length - 1
        Continue While
      End If
    End If
    ' Calculate length of line to print
    If line.Length > 0 Then
      mySize = G.MeasureString(Line.ToString, myFont, Integer.MaxValue, StringFormat.GenericTypographic)
      textwidth = mySize.Width
    End If
    ' Draw line if line is full, if end of line or if end of text
    If TextWidth > PageWidth OrElse C = NewLine OrElse C = EndOfText Then
      'Check if wrapping at last white space is needed
      If TextWidth > PageWidth Then
        If LastWS <> -1 Then
          myOffset -= Line.Length - LastWS - 1
          Line.Length = LastWS + 1
        Else
          Line.Length -= 1
          myOffset -= 1
        End If
      End If
      ' There is text to be printed
      If Line.Length > 0 Then
        R = New RectangleF(X, Y, PageWidth, LineHeight)
        SF.Alignment = StringAlignment.Near
        G.DrawString(Line.ToString(), myFont, Brushes.Black, R, SF)
      End If
      ' Increase vertical position
      Y += LineHeight
      TextHeight += LineHeight
      ' empty line buffer and reset white space position
      Line.Length = 0
      TextWidth = 0.0F
      LastWS = -1
    End If
    ' If next line doesn't fit on page anymore, exit loop
    If TextHeight > (PageHeight - LineHeight) Then
      Exit While
    End If
    If C = EndOfText Then
      Exit While
    End If
  End While
  ' Print footer 
  X = OffsetX
  Y = OffsetY + PageHeight + (LineHeight * 2)
  R = New RectangleF(X, Y, PageWidth, LineHeight)
  SF.Alignment = StringAlignment.Center
  G.DrawString(myPageNo.ToString(), myFont, Brushes.Black, R, SF)
 
  G.Dispose()
  myPageNo += 1
  e.HasMorePages = (C <> EndOfText)
End Sub

Protected Overrides Sub OnEndPrint(ByVal e As PrintEventArgs)
  MyBase.OnEndPrint(e)
End Sub

End Class

 

Advertisements
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s