Published by
Apr 18, 2010 (last update: Apr 18, 2010)

Printer Stream

Score: 3.7/5 (53 votes)
*****
A little while ago, someone made a post like this
1
2
ostream printer;
printer << "Some text" << endl;

and wondered why it didn't print.

So I thought I'd write a printer stream (and get to learn about doing stream manipulators at the same time.)

Here is the 'proof of concept' code.
Most of the comments have been stripped to fit the pstream.cpp file in one post.
It is for Windows only.
It is a working version - but obviously not finished.

It is for printing text (and text files) - so don't bother trying to print binary files like word files or pdf files. :-)


Enjoy.
The pstream header file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#ifndef P_STREAM_H
#define P_STREAM_H

#include <string>
#include <iostream>
#include <vector>
#include <fstream>



namespace pst
{
   
    //stream status
    const int  GOOD = 0x0000;
    const int  FAIL = 0x0001;

    //margins ninimum values
    const int MARGINMIN = 3; //millimetres



    template <typename T> class pstreamManip;

    /*!
    \brief class pstream 
    */
    class pstream
    {

    public:
        pstream();
        pstream(std::string whichPrinter);
        ~pstream();

        operator bool()
        {
            return !streamStatus;
        }

        
        void setTitle(std::string newTitle) { title = newTitle; }
        void setPageNumbers(bool bVal)      { pageNumbers = bVal;}
        void setTabSize(int newTabSize)     { tabSize = newTabSize;}
        
        /*!
        The following are member functions NOT manipulators
        */
        void setLeftMargin(int newMargin)   {leftMargin = newMargin <= MARGINMIN? MARGINMIN:newMargin; }
        void setTopMargin(int newMargin)    {topMargin = newMargin <= MARGINMIN? MARGINMIN:newMargin; };
        void setRightMargin(int newMargin)   {rightMargin = newMargin <= MARGINMIN? MARGINMIN:newMargin; };
        void setBottomMargin(int newMargin)   {bottomMargin = newMargin <= MARGINMIN? MARGINMIN:newMargin; };

        
        
        //Insertion Overloads
        //Add a string
        pstream& operator << (std::string name);
        //Add a char buffer
        pstream& operator << (char* name);
        //Add a file
        pstream& operator << (std::ifstream & inFile);

        //other insertion overlaods
        pstream& operator << (pstream & (*ptr)(pstream&)); //manipulator with no parameters
        /*!
        \brief Insertion overload for with manipulators with one parameter
        Note this is a template function so we put the definition here - much easier
        */
        template <typename T>
        pstream& operator << (pstreamManip<T> manip)
        {
            return manip(*this);
        }

        /*!
        \brief Useful static functions
        */
        static std::vector<std::string> getPrinterNames();
        

    private:
        std::string defaultPrinter;
        int streamStatus;
        std::vector<std::string> lines;
        int leftMargin;
        int rightMargin;
        int topMargin;
        int bottomMargin;
        std::string title;
        bool pageNumbers;
        int tabSize;

        
        //Copy Constructor - private and no body
        pstream(const pstream& other);

        
        /*!
        \brief manipulators
        These are mostly friends of pstream.
        They are in the pst namespace
        */
        
        /*!
        \brief Manipulators taking no arguments
        */
        friend pstream& flushp (pstream& p);
        /*!
        \brief manipulators taking one or more arguments
        These are structure based
        */
        
        template <typename T>
        friend pstreamManip<T> setLeftMargin (T margin);

        template <typename T>
        friend pstreamManip<T> setRightMargin (T margin);

        template <typename T>
        friend pstreamManip<T> setTopMargin (T margin);

        template <typename T>
        friend pstreamManip<T> setBottomMargin (T margin);

        /*!
        \brief private  functions for the manipulator(s) with arguments.
        \note These are static.
        */
        static pstream& _setMarginL(pstream & p, int n);
        static pstream& _setMarginR(pstream & p, int n);
        static pstream& _setMarginT(pstream & p, int n);
        static pstream& _setMarginB(pstream & p, int n);

    };//class pstream


    /*!
    \brief The manipulator class for manipulator with one parameter
    */
    template <typename T>
    class pstreamManip 
    {
    public:
        pstreamManip(pstream& (*fp)(pstream&, T val), T arg) : pf(fp), argValue(arg){};
        pstream& operator() (pstream & ps)
        { 
            return (*pf)(ps, argValue);
        }

    private:
        pstream& (*pf)(pstream&, T);
        T argValue;

    }; //class pstream

    /*!
    \brief All manipulators are in the pst namespace
    */
    pstream& flushp (pstream& p);

    
    template <typename T> 
    pstreamManip<T> setLeftMargin (T margin)
    {
        return pstreamManip<T>(pstream::_setMarginL, margin);
    }

    template <typename T> 
    pstreamManip<T> setRightMargin (T margin)
    {
        return pstreamManip<T>(pstream::_setMarginR, margin);
    }

    template <typename T> 
    pstreamManip<T> setTopMargin (T margin)
    {
        return pstreamManip<T>(pstream::_setMarginT, margin);
    }

    template <typename T> 
    pstreamManip<T> setBottomMargin (T margin)
    {
        return pstreamManip<T>(pstream::_setMarginB, margin);
    }


} //namespace pst




#endif 

The pstream implementation file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#undef  UNICODE
#undef _UNICODE

#include "pstream.h"

#include <windows.h>
#include <tchar.h>
#include <iostream>

namespace pst
{

    pstream::pstream() :leftMargin(),rightMargin(),topMargin(),bottomMargin(),tabSize(4),pageNumbers(false)
    {
        DWORD buffSize;
        _TCHAR* pPrinterName;
        GetDefaultPrinter(0, &buffSize);
        pPrinterName = new _TCHAR[buffSize]();
        GetDefaultPrinter (pPrinterName,&buffSize);
        defaultPrinter =  pPrinterName;
        //Set the stream status
        streamStatus = (defaultPrinter == "")? FAIL : GOOD;
    }

    
    pstream::pstream (std::string whichPrinter) :leftMargin(), rightMargin(), topMargin (), bottomMargin(), tabSize(4), pageNumbers(false)
   {
       defaultPrinter = whichPrinter;
       streamStatus = (defaultPrinter == "")? FAIL : GOOD;     
   }

    
    
    pstream::~pstream()
    {

    }


    pstream& pstream::operator <<(std::string name)
    {
        if (! streamStatus)
        {
            //replace all tabs by the required number of spaces
            size_t position =0;

            while (position != std::string::npos)
            {
                
                if (   (position = name.find('\t', position)     ) != std::string::npos    )
                {
                    name.replace(position,1,std::string(tabSize,' ') );
                }

            }

            //We will split the string up at the new line char

            position = 0;
            size_t foundPos =0;

            do
            {
                foundPos = name.find('\n', position);
                std::string str = name.substr(position,foundPos-position);
                lines.push_back(str);
                position = foundPos+1;
            }while (position != 0);
        }
        
        return *this; 
    }   
    
    pstream& pstream::operator <<(char * name)
    {
        if (! streamStatus)
            operator<< (std::string(name));

        return *this;
    }


    pstream& pstream::operator << (std::ifstream & inFile)
    {
        if (!streamStatus)
        {
            if (inFile)
            {
                while (inFile)
                {
                    std::string str;
                    std::getline(inFile,str);

                    *this  << str ;
                }
            }
        }
        return *this;
    }



    pstream& pstream::operator <<(pstream &(*ptr)(pstream &))
    {

        ptr(*this);

        return *this;

    }


    pstream& flushp (pstream& p)
    {
        
        if (!p.lines.size() ||  p.streamStatus )
        {
            return p;
        }
        HDC pdc;

        pdc = CreateDC("winspool",p.defaultPrinter.c_str(),NULL, NULL);

        int logPixelsX, logPixelsY, vRes, hRes, scaleX;

        logPixelsX = GetDeviceCaps(pdc,LOGPIXELSX);
        logPixelsY = GetDeviceCaps(pdc, LOGPIXELSY);
        vRes = GetDeviceCaps(pdc, VERTRES);
        hRes = GetDeviceCaps(pdc, HORZRES);
        scaleX = GetDeviceCaps(pdc,SCALINGFACTORX);

        TEXTMETRIC tm;
        GetTextMetrics(pdc, &tm);
        long aveCharWidth=tm.tmAveCharWidth;
        long maxCharWidth=tm.tmMaxCharWidth;
        long charHeight =tm.tmHeight;
        long lineHeight = charHeight;// + tm.tmExternalLeading;

        //we need to adjust for the margins (change mm to pixels)
        int hPixelsMM = logPixelsX/25;
        int vPixelsMM = logPixelsY/25;
        int leftStart = ( (p.leftMargin*hPixelsMM) > hRes/4)? hRes/4: p.leftMargin*hPixelsMM;
        int rightEnd =  ( (p.rightMargin*hPixelsMM) > hRes/4)? hRes - hRes/4: hRes - p.rightMargin*hPixelsMM;
        int topStart = ( (p.topMargin*vPixelsMM) > vRes/4)? vRes/4: p.topMargin*vPixelsMM;
        int bottomEnd = ( (p.bottomMargin *vPixelsMM) > vRes/4)? vRes - vRes/4: vRes - p.bottomMargin*vPixelsMM;

        //Use 10 point courier font.
        HFONT hFont = CreateFont(-(logPixelsY/72)*10,0,0,0,0,0,0,0,0,0,0,0,FIXED_PITCH,"courier");
        HFONT hOldFont =(HFONT)SelectObject(pdc,hFont);
        

        //start document
        DOCINFO docInfo = {0};
        docInfo.cbSize = sizeof(docInfo);

        StartDoc(pdc,&docInfo);

        //start the pages
        int curPosY = topStart+lineHeight; 
        int curPosX= leftStart;
        int lastSpacePos; //used for word breaking checks

        std::vector<std::string>::iterator iter = p.lines.begin(), iterEnd = p.lines.end();

        StartPage(pdc); //Start the first page
        
        for (; iter != iterEnd ; ++iter )
        {
            std::string curStr = *iter;
            std::string tempStr;
            
            //So for empty lines we will  make the line one space long.
            if(curStr.length() ==0)
                curStr = " ";
            lastSpacePos = 0;
            for (int startIndex = 0,count =0; startIndex < curStr.length() ; count ++)
            {

                int charWidth;
                char c = curStr[count];
                GetCharWidth32(pdc, curStr[count],curStr[count],&charWidth);

                if (count < curStr.length())
                {
                    if(c ==' ')
                        lastSpacePos = count;

                    //check if the character will take us past the right hannd side margin
                    if( (curPosX += charWidth) > rightEnd)
                    {
                        if(lastSpacePos ==startIndex)
                        {
                            lastSpacePos = count-2; //fake a break
                        }
                        tempStr = curStr.substr(startIndex, lastSpacePos-startIndex);
                        count = lastSpacePos;//move the char indexer back to the last space position
                        startIndex = lastSpacePos;
                        //print the line 

                        TextOut(pdc,leftStart,curPosY,tempStr.c_str(), tempStr.length() );

                    }
                    else //No we won't go past RHS margin
                    {
                        continue;              
                    }

                }
                else
                {

                    tempStr = curStr.substr(startIndex, (count)-startIndex);
                    //print
                    TextOut(pdc,leftStart,curPosY,tempStr.c_str(), tempStr.length() );

                    startIndex = count;
                }

                //calculate the next print position - start new page if req'd
                curPosX = leftStart;
                curPosY += lineHeight;
                if (curPosY > bottomEnd)
                {
                    EndPage(pdc);
                    curPosY = topStart+lineHeight;
                    StartPage(pdc);
                }

            }  

        }
    
        EndDoc(pdc);
        p.lines.clear();
        //cleanup
        SelectObject(pdc, hOldFont);
        DeleteObject(hFont);
        DeleteDC(pdc);
        return p;
    }
   
    pstream& pstream:: _setMarginL(pstream & p, int margin)
    {
        p.setLeftMargin(margin);
        return p;
    }  
     
     pstream& pstream:: _setMarginR(pstream & p, int margin)
    {
        p.setRightMargin(margin);
        return p;
    }

    pstream& pstream:: _setMarginT(pstream & p, int margin)
    {
        p.setTopMargin(margin);
        return p;
    }

    pstream& pstream:: _setMarginB(pstream & p, int margin)
    {
        p.setBottomMargin(margin);
        return p;
    }  
        
    std::vector< std::string>  pstream::getPrinterNames()
    {

        LPBYTE buff = 0;
        DWORD buffSize =0;
        DWORD bytesNeeded;
        DWORD numPrinters=0;
        PRINTER_INFO_4 *pInfo;
        std::vector<std::string> names;

        if ( EnumPrinters(PRINTER_ENUM_LOCAL,NULL,4,
                            buff,0,&bytesNeeded,
                            &numPrinters) == FALSE  && bytesNeeded > 0)
        {
            buff = new byte[bytesNeeded];
            EnumPrinters(PRINTER_ENUM_LOCAL,NULL,4,buff,bytesNeeded,&bytesNeeded,&numPrinters);

        }

        pInfo = (PRINTER_INFO_4*)buff;
        for (int count =0; count < numPrinters; count ++)
        {
            names.push_back( std::string (pInfo->pPrinterName));
            ++pInfo;          
        }
   
        delete [] buff;

        return names;

    }

}//namespace pst 

A main program file for testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// printer_stream.cpp : Defines the entry point for the console application.


#include "pstream.h"
#include <iostream>
#include <fstream>
#include <windows.h>
#include <string>
#include <iomanip>
using namespace std;




int main(int argc, char* argv[])
{
     
    //The stream has a static function that will  give  the names of the local printers attached to the system;
    vector<string> printerNames = pst::pstream::getPrinterNames();
    vector<string>::iterator itr;
    for (itr = printerNames.begin(); itr != printerNames.end(); ++ itr)
    {
        cout << *itr << '\n';
    }

    
    //Create a stream - default constructor will use the default ptinter
    pst::pstream p;

    //Set the page margins using the printer stream manipulators
    p << pst::setLeftMargin(10);
    p << pst::setTopMargin(20);
    p << pst::setRightMargin(30);
    p << pst::setBottomMargin(-50); //A zero or negative value will clamp to 0;

    //The margins can also be set using the printer stream member functions
    p.setLeftMargin(25);

    
    //We can put strings into the stream
    p << string("This is a very long string with lots of text  way past the eend of"
                "the riiiight margin to test the word break function.");
    p << string("This second string should start on a new line??");

    p << string("This short line\n has embeded newline char(s)");

    p<< string("This line contains\t embedded \t\t tab(s)");

    p << "Alonglinewithnospacestoseewhatwillhappenattheendofthelineabcdefghijklmnopqrstuvwxyz";

    //printing will start when we flush
    p << pst::flushp;

   
    //We can insert files into the stream
    ifstream f("testingx.cpp"); //use some available  text file
    p << f;   
    p << pst::flushp;

    return 0;
}