Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C++
Article

A line printer class

Rate me:
Please Sign up or sign in to vote.
4.33/5 (6 votes)
22 Nov 19994 min read 146K   2.3K   30   14
A handy way to write to a line printer

Introduction

Windows is really fun, mostly. But one of the things I miss about UNIX (or the IBM 1401) is the handy way it writes to a line printer. No sissy graphics, no obsequious "Page n of m" ( unless you want to write a lot of extra code), just thousands of sturdy fixed width lines of ASCII characters. Gayle Manufacturing Company (The GM in all the names and labels below) is the steel fabricating company I work for. Most of the routine reports we generate are in line printer format, nothing fancy - just the numbers. Did you ever wonder why there isn't some easy way to simply iterate through your data and print a line at a time (nostalgic sigh)?? Maybe it's just been too obvious and that's why I missed it.

I'm immersed in a project that is a redesign of a system that presently runs on UNIX. Most of the reports are fine the way they are so I want a simple way to re-create them from Windows apps. I need a line printer!! I want printf() to go to 'stdout' so I can pipe it to 'lp'. Is that so wrong? The idea is to loop throught the data in a query and print a line or two for each row of data with the odd total here and there.

Ah, well. Things have changed and there isn't a line printer so I guess I had to make my own. I designed a COM class that acts sort of like a line printer. It's written in VC++ 5 with MFC4.2, ATL and STL. Since it is a dual inteface COM component, it may be used from C++ or VBA. It makes it trivially easy to create line printer style reports and it runs under MTS. It is another example of a way to print without using a CView class. It compiles under UNICODE. The error checking is rather minimal and some of the code is probably BFI (Brute Force and Ignorance, but if I'm ignorant how can I tell?).

The COM Interface has the following methods

write(LINE_TYPE, BSTR text)  // to send lines to the print arrays
reset(RESET_TYPE)            // to clear the print arrays
print(PRINT_TYPE)            // to format the print arrays onto the default printer
These properties
font_size ( GMP_FONT_CPI)       //to set the fixed width font size(10, 12, 15 cpi)
orientation ( GMP_ORIENTATION)  //portrait or landscape
title(BSTR)                     //prints on the lower right corner of each page
print_heading(bool)             // set to false for really plain printing
page_breaks(bool)               //if false don't allow forced page breaks
punch_margin(double )           // Sets the width of the margin for 3 hole punching
and these enumerations
[helpstring("enum line types")]
typedef enum
{
        GMP_LT_DEFAULT,
        GMP_LT_HEAD,
        GMP_LT_BODY,
        GMP_LT_NEWPAGE
} LINE_TYPE;

[helpstring("enum reset types")]
typedef enum
{
        GMP_RS_NONE,
        GMP_RS_HEAD,
        GMP_RS_BODY,
        GMP_RS_ALL
} RESET_TYPE;

[helpstring("enum print types")]
typedef enum
{
        GMP_DEFAULT,
        GMP_PAGE_RANGE,
        GMP_NO_PAGE_BREAK,
        GMP_NO_HEADING
} PRINT_TYPE;

[helpstring("enum orientation")]
typedef enum
{
        GMP_PORTRAIT,
        GMP_LANDSCAPE
} GMP_ORIENTATION;

[helpstring("enum font cpi selection")]
typedef enum
{
        GMP_FONT_10,
        GMP_FONT_15,
        GMP_FONT_12
} GMP_FONT_CPI;

It works like this Use the usual means of getting a pointer to an IGMPrintEZ instance. I like the #import method so

IGMPrintEZPtr p_prt;
HRESULT hr = p_prt.CreateInstance(__uuidof(GMPrintEZ));

The printer object starts out empty but just to be sure I can reset it

p_prt->reset(GMP_RS_ALL);    // clears all the data from the arrays

Write the heading lines to the heading line table

// the text is a BSTR so use any means of providing one
BSTR text = SysAllocString(L"HeadingLine 1");
CString str_blankline = "";
p_prt->write(GMP_LT_HEAD, text);
// a blank line
p_prt->write(GMP_LT_HEAD, str_blankline.AllocSysString());
p_prt->write(GMP_LT_HEAD,
        SysAllocString(L"Column 1        Column 2  Column3"));

You can write heading lines any time during the process. There is a separate array for the heading and the body lines. The driving program could write the heading lines AFTER processing all the body lines so that totals appear on each page heading. This opens a number of possibilities. In fact the title, orientation, and font can all be determined after the body lines have been written to the body line array, and set just prior to actually printing.

The other operation is to write the body lines

LINE_TYPE     lt = GMP_LT_BODY;
while(! data.IsEOF())
{
  CString data;
  data.Format("%25s %10d", data.item, data.quantity);
  p_prt->write(lt, data.AllocSysString());
  data.MoveNext();
}

Before we print we set the title, the format to landscape and the font to 12 characters per inch

p_prt->title = _T("Test Job");
 p_prt->orientation = GMP_LANDSCAPE;
 p_prt->font_size = GMP_FONT_12;

And then we print

p_prt->print(GMP_DEFAULT);

The print object iterates through the BODY line array and prints a line for each entry in the array. The heading and footer are separated from the body by horizontal lines. The date and time print in the lower left, the page number in the center and the title in the lower right.

The main elements of the program are a pair of vectors ( vector), one for the heading lines and one for the body lines, and a print loop. Here's what the print loop code looks like

bool GMLinePrintpage_loop()
{
// loop thru the body lines and print to the page
        try
        {
        CPrintDialog pd(FALSE);
        DOCINFO di;     // must have DOCINFO for CDCStartDoc(DOCINFO)
        m_line = 0;
        m_max_lines = 20;
        m_last_body_line = m_max_lines;
        m_line_height = LINEHEIGHT_10;

                // if the body lines don't contain anything just return now
                if(0 == body_lines->size())
                return true;

        memset(di, 0, sizeof(DOCINFO)); // make a clean start
        di.cbSize = sizeof(DOCINFO);
        di.lpszDocName = m_title;


        // lazy way of getting the default printer
        // just get all the printer defaults - no  display
        // so this COM object can run from MTS

        pd.GetDefaults();

        DEVMODE *dm = pd.GetDevMode();

        // set orientation
        // print landscape or portrait?
        dm->dmOrientation = m_orientation + 1;
        // signify te presence of orientation data
        dm->dmFields |= DM_ORIENTATION;

        // set punch margin
        switch(m_orientation)
        {
        case GMP_PORTRAIT
                m_top_offset = 0;
                m_left_offset = INCH * m_punch_margin;
                break;
        case GMP_LANDSCAPE
                m_top_offset = INCH * m_punch_margin;
                m_left_offset = 0;
                break;
        }

        // create the printer device context by getting values from the
        // printdialog and the dm structure
        CDC dc;
        if(! dc.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
                pd.GetPortName(), dm))
        {
                AfxMessageBox(_T("Can't create DC in print_loop"));
                return false;
        }


        dc.StartDoc();

        // obtain the page dimensions from the Device Context
        m_page_height = dc.GetDeviceCaps(VERTSIZE) * MM_TO_INCH;
        m_page_width = dc.GetDeviceCaps(HORZSIZE) * MM_TO_INCH;

        CFont *oldfont;

        // select font and set line height
        switch(m_font)
        {
        case GMP_FONT_12
                oldfont = dc.SelectObject(C12);
                m_line_height = LINEHEIGHT_12;
                m_cpi = 12;
                break;
        case GMP_FONT_15
                oldfont = dc.SelectObject(C15);
                m_line_height = LINEHEIGHT_15;
                m_cpi = 15;
                break;
        case GMP_FONT_10
        default
                oldfont = dc.SelectObject(C10);
                m_line_height = LINEHEIGHT_10;
                m_cpi = 10;
                break;
        }


        // compute the lines per page
        m_max_lines = -(( m_page_height - m_top_offset) /
                                m_line_height);

        // compute the last body line
        if(m_b_print_head)
        {
                m_last_body_line = m_max_lines - FOOTER_LINES -
                head_lines->size() - HEADER_SEPARATOR;
        }
        else
        {
                m_last_body_line = m_max_lines;
        }

        // the print loop
        // I like to use the Standard Library collections when I can.
        vectoriterator itext;
        m_page = 0;
        for(itext = body_lines->begin();
                itext < body_lines->end();
                ++itext)
        {
                // look for a pagebreak
                if("@@FF" == itext->Left(4))
                {
                        if(m_b_use_page_breaks)
                        {
                        m_line = 0;
                        dc.EndPage();
                        new_page(&dc);
                        }
                        // otherwise just ignore the page_break token
                }
                else
                {
                        if (m_line >= m_last_body_line)
                        {
                        m_line = 0;
                        dc.EndPage();
                        }
                        if (m_line == 0)
                        new_page(&dc);

                dc.TextOut(
                                m_left_offset,
                                (m_line++ * m_line_height) - m_top_offset,
                                *itext
                                );
                }

        }

        dc.EndPage();
        dc.EndDoc();
        dc.DeleteDC();
        return true;
        }
        catch(...)
        {
                return false;
        }
}

There is no user interface to this object (that's a feature!!) so there is no print preview or printer dialog. This makes it it possible to install this COM class on MTS and run the printer from a process on a remote system. This article was contributed by Richard Warg .

There are many applications where I don't really want to use the usual windows page print. Instead I want output to go directly to the printer using standard print i/o. It's actually a topic that is hard to find in any of the books on Windows, at least I've never found anything on it. But to my surprise I recently learned that standard (DOS/UNIX) printing is alive and well underneath windows. All we need to do is open a printer port and print to it.

If the printer is directly attached to the computer it's trivial. The method for obtaining a printer port when the printer is on the network isn't hard either.

The example below shows how I use the Windows NET USE command to re-direct LPT1 to a shared printer on an NT Server. The same technique applies for Novell networks with a slightly different syntax.

Try this out by creating a new MFC Form-based project. Put a button on the form and attach this code to it. You can actually print with only 3 lines of code:

FILE *fp = fopen("LPT1", "w");
fprintf(fp,"What's up, Doc?\n");
fclose(fp);

Instant print gratification!!

While the program is open it hogs the printer port. In my shop that isn't a problem but be aware of the effect on your windows spooled output.

        *********************************************************
                        THE CODE
        *********************************************************

// the headers for the conventional i/o routines
#include
#include
#include

using namespace std;      // makes string and ofstream
                        // work without std:: qualifier

void CLineprtView::OnButton1()
{
        // I could have used a CString instead of the buff[]
        // but I wanted to show how this is used with lightweight
        // ATL code and STD library

        char buff[MAX_BUFF_SIZE];

        // My printer is located on another server so I must re-direct the
        // printer port.  If the printer is directly attached this extra step
        // is not needed.
        // on my network the printer is published as \\GREEN\hp5annex
        // All those back-slashes escape the backslash in the path name

        if (PRINTER_IS_REMOTE)
        {
                system("NET USE LPT1 /d");  // free up the port
                system("net use lpt1 \\\\green\\hp5annex");
        }

        // old fashioned file handle with
        // old fashioned open of the printer port
        FILE *ptr = fopen("LPT1","w");

        // laser printer setup string
        sprintf(buff,"\033E\033(s0p4102t1b16.66H\033&l1O");
        fprintf(ptr,buff);

        // old fashioned print
        fprintf(ptr,"Who of late doth make a thimble.\n");
        fprintf(ptr,"Is a lower bunk a status symbol??\n");

        // old fashioned close
        fclose(ptr);

        // now the same thing with stream io
        ofstream optr("LPT1", ios::out);

        string str_text = "Hey Doc, Ain't this a print test from windows\n";
        str_text += "with more lines to follow?\n";

        optr << str_text << endl;
        optr << "Quiet, wabbit. I'm conversing with my muse!!\n";

        optr << "That's all folks." << "\f" << flush;     // add a formfeed

        // the printer connection is still open so close it
        optr.close();

        // drop the network link
        if (PRINTER_IS_REMOTE)
        {
                system("net use lpt1 /d");
        }
}

In practice I get printer path information from the registry on each machine, so the real live code is a little busier than this example, but not much.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
Dick Warg is very old, 60 in fact.
Dick thinks FORTH is still cool but he mostly writes dreadful C++ which ends up looking a lot like FORTH anyway when he gets done with it. He hopes to finish his current project real soon, or at least before his Social Security kicks in.

Favorite saying: "Who wrote this garbage? Oh, I did."

Comments and Discussions

 
GeneralHere's to you, old man!! Pin
WREY28-Oct-03 10:46
WREY28-Oct-03 10:46 
GeneralRe: Here's to you, old man!! Pin
Fastfootskater11-Jul-05 20:07
Fastfootskater11-Jul-05 20:07 
GeneralRe: Here's to you, old man!! Pin
Rajeev Verma4-Dec-07 20:36
Rajeev Verma4-Dec-07 20:36 
GeneralRe: Here's to you, old man!! Pin
Le@rner2-Jul-08 2:53
Le@rner2-Jul-08 2:53 
Generaldoes it work for windows CE Pin
keen7-May-03 23:26
keen7-May-03 23:26 
Generala lost art Pin
Marc Clifton25-Feb-03 0:49
mvaMarc Clifton25-Feb-03 0:49 
Generalquestion about CPrintDialog and DEVMODE Structure Pin
lavocat2-Dec-02 0:26
lavocat2-Dec-02 0:26 
GeneralWin95 not working with sizes Pin
Antonis24-Jul-02 19:43
Antonis24-Jul-02 19:43 
GeneralNot so good... Pin
8-Mar-02 11:14
suss8-Mar-02 11:14 
GeneralWell, line printing is for geeks :-) Pin
Anonymous7-Sep-02 15:51
Anonymous7-Sep-02 15:51 
GeneralWell, well Pin
23-Aug-01 10:38
suss23-Aug-01 10:38 
GeneralRe: Well, well Pin
Blake Miller7-Jun-04 5:27
Blake Miller7-Jun-04 5:27 
Generalline printer Pin
24-Jul-01 4:22
suss24-Jul-01 4:22 
GeneralRe: line printer Pin
14-Mar-02 12:45
suss14-Mar-02 12:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.