views:

184

answers:

2

I'm using Graphics.ScaleTransform to stretch lines of text so they fit the width of the page, and then printing that page. However, this converts the print job to a bitmap - for a print with many pages this causes the size of the print job to rise to obscene proportions, and slows down printing immensely.

If I don't scale like this, the print job remains very small as it is just sending text print commands to the printer.

My question is, is there any way other than using Graphics.ScaleTransform to stretch the width of the text?

Sample code to demonstrate this is below (would be called with Print.Test(True) and Print.Test(False) to show the effects of scaling on print job):

Imports System.Drawing
Imports System.Drawing.Printing
Imports System.Drawing.Imaging

Public Class Print

    Dim FixedFont As Font
    Dim Area As RectangleF
    Dim CharHeight As Double
    Dim CharWidth As Double
    Dim Scale As Boolean

    Const CharsAcross = 80
    Const CharsDown = 66
    Const TestString = "!""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

    Private Sub PagePrinter(ByVal sender As Object, ByVal e As PrintPageEventArgs)

        Dim G As Graphics = e.Graphics
        If Scale Then
            Dim ws = Area.Width / G.MeasureString(Space(CharsAcross).Replace(" ", "X"), FixedFont).Width
            G.ScaleTransform(ws, 1)
        End If

        For CurrentLine = 1 To CharsDown
            G.DrawString(Mid(TestString & TestString & TestString, CurrentLine, CharsAcross), FixedFont, Brushes.Black, 0, Convert.ToSingle(CharHeight * (CurrentLine - 1)))
        Next

        e.HasMorePages = False

    End Sub

    Public Shared Sub Test(ByVal Scale As Boolean)

        Dim OutputDocument As New PrintDocument
        With OutputDocument
            Dim DP As New Print
            .PrintController = New StandardPrintController
            .DefaultPageSettings.Landscape = False
            DP.Area = .DefaultPageSettings.PrintableArea
            DP.CharHeight = DP.Area.Height / CharsDown
            DP.CharWidth = DP.Area.Width / CharsAcross
            DP.Scale = Scale
            DP.FixedFont = New Font("Courier New", DP.CharHeight / 100, FontStyle.Regular, GraphicsUnit.Inch)
            .DocumentName = "Test print (with" & IIf(Scale, "", "out") & " scaling)"
            AddHandler .PrintPage, AddressOf DP.PagePrinter
            .Print()
        End With
    End Sub
End Class

UPDATE: I used interop with GDI calls instead. Here's the relevant code; the GDI class is just full of definitions I copied from the wiki at http://pinvoke.net/ for the relevant functions and constants.

    ' convert from Graphics units (100 dpi) to device units
    Dim GDIMappedCharHeight As Double = CharHeight * G.DpiY / 100
    Dim GDIMappedCharWidth As Double = CharWidth * G.DpiX / 100

    Dim FixedFontGDI As IntPtr = GDI.CreateFont(GDIMappedCharHeight, GDIMappedCharWidth, 0, 0, 0, 0, 0, 0, GDI.DEFAULT_CHARSET, GDI.OUT_DEFAULT_PRECIS, GDI.CLIP_DEFAULT_PRECIS, GDI.DEFAULT_QUALITY, GDI.FIXED_PITCH, "Courier New")
    Dim CharRect As New GDI.STRUCT_RECT

    Dim hdc As IntPtr = G.GetHdc()
    GDI.SelectObject(hdc, FixedFontGDI)

    ' I used SetBkMode transparent as my text needed to overlay a background
    GDI.SetBkMode(hdc, GDI.TRANSPARENT)

    ' draw it character by character to get precise grid
    For CurrentLine = 1 To CharsDown
        For CurrentColumn = 1 To CharsAcross
            With CharRect
                .left = GDIMappedCharWidth * (CurrentColumn - 1)
                .right = GDIMappedCharWidth * CurrentColumn
                .top = GDIMappedCharHeight * (CurrentLine - 1)
                .bottom = GDIMappedCharHeight * CurrentLine
            End With
            ' 2341 == DT_NOPREFIX|DT_NOCLIP|DT_VCENTER|DT_CENTER|DT_SINGLELINE
            GDI.DrawText(hdc, Mid(TestString & TestString & TestString, CurrentLine+CurrentColumn, 1), 1, CharRect, 2341)
        Next
    Next

    GDI.DeleteObject(FixedFontGDI)

    G.ReleaseHdc(hdc)
A: 

Im guessing here but you should increase the size of the Font by a percentage of the proportion you want to scale by.

James Westgate
I do set the height of the font [shown in the code New Font("Courier New", DP.CharHeight / 100, FontStyle.Regular, GraphicsUnit.Inch)] relative to the height of the line, but need to set width independently of that. Unfortunately, scaling just size stretches both width and height in the same proportion.
Philip Dunaway
+1  A: 

Yes, the Graphics class supports scaling text. But it needs to do so by rendering the text to a bitmap first, rescale the bitmap and pass that resized bitmap to the printer driver. All those bitmaps make for a large spooler file.

You will need to justify the text yourself. There's no support for this in the framework. One way to do it is to hijack a rich edit control and let it take care of the justification and printing. Version 5, msftedit.dll, supports full justification. The best way to find the code you need is to find one of the many projects that implement a text editor with RTB, similar to Wordpad on Windows.

Hans Passant
Thanks for your advice! I took a look at using rich edit controls but it seemed to overwhelming, so I ended up using interop with the old GDI calls straight to the Graphics hDC - fortunately the CreateFont for that supports setting width as well as height, and passes it on to the printer without bitmap rendering.
Philip Dunaway