A streambuf Derivation Example

The following example modifies the cout object to print in two-column landscape (horizontal) mode on a printer that uses the PCL control language (for example, Hewlett-Packard LaserJet printer). As the test driver program shows, all member functions and manipulators that work with the original cout object work with the special version. The application programming interface is the same.

The example is divided into three source files:

  • HSTREAM.H — the LaserJet class declaration that must be included in the implementation file and application file

  • HSTREAM.CPP — the LaserJet class implementation that must be linked with the application

  • EXIOS204.CPP — the test driver program that sends output to a LaserJet printer

HSTREAM.H contains only the class declaration for hstreambuf, which is derived from the filebuf class and overrides the appropriate filebuf virtual functions.

// hstream.h - HP LaserJet output stream header
#include <fstream.h> // Accesses filebuf class
#include <string.h>
#include <stdio.h> // for sprintf

class hstreambuf : public filebuf
{
public:
    hstreambuf( int filed );
    virtual int sync();
    virtual int overflow( int ch );
    ~hstreambuf();
private:
    int column, line, page;
    char* buffer;
    void convert( long cnt );
    void newline( char*& pd, int& jj );
    void heading( char*& pd, int& jj );
    void pstring( char* ph, char*& pd, int& jj );
};
ostream& und( ostream& os );
ostream& reg( ostream& os );

HSTREAM.CPP contains the hstreambuf class implementation.

// hstream.cpp  - HP LaserJet output stream
#include "hstream.h"

const int REG  = 0x01;   // Regular font code
const int UND  = 0x02;   // Underline font code
const int CR   = 0x0d;   // Carriage return character
const int NL   = 0x0a;   // Newline character
const int FF   = 0x0c;   // Formfeed character
const int TAB  = 0x09;   // Tab character
const int LPP  = 57;     // Lines per page
const int TABW = 5;      // Tab width

// Prolog defines printer initialization (font, orientation, etc.
char prolog[] =
{ 0x1B, 0x45,                         // Reset printer
  0x1B, 0x28, 0x31, 0x30, 0x55,         // IBM PC char set
  0x1B, 0x26, 0x6C, 0x31, 0x4F,         // Landscape
  0x1B, 0x26, 0x6C, 0x38, 0x44,       // 8 lines per inch
  0x1B, 0x26, 0x6B, 0x32, 0x53};        // Lineprinter font

// Epilog prints the final page and terminates the output
char epilog[] = { 0x0C, 0x1B, 0x45 };   // Formfeed, reset

char uon[] = { 0x1B, 0x26, 0x64, 0x44, 0 }; // Underline on
char uoff[] = { 0x1B, 0x26, 0x64, 0x40, 0 };// Underline off

hstreambuf::hstreambuf( int filed ) : filebuf( filed )
{
    column = line = page = 0;
    int size = sizeof( prolog );
    setp( prolog, prolog + size );
    pbump( size );   // Puts the prolog in the put area
    filebuf::sync(); // Sends the prolog to the output file
    buffer = new char[1024]; // Allocates destination buffer
}

hstreambuf::~hstreambuf()
{
    sync(); // Makes sure the current buffer is empty
    delete buffer; // Frees the memory
    int size = sizeof( epilog );
    setp( epilog, epilog + size );
    pbump( size );   // Puts the epilog in the put area
    filebuf::sync(); // Sends the epilog to the output file
}
int hstreambuf::sync()
{
    long count = out_waiting();
   if ( count ) {
      convert( count );
   }
   return filebuf::sync();
}

int hstreambuf::overflow( int ch )
{
    long count = out_waiting();
   if ( count ) {
      convert( count );
   }
   return filebuf::overflow( ch );
}
// The following code is specific to the HP LaserJet printer

// Converts a buffer to HP, then writes it
void hstreambuf::convert( long cnt )
{
    char *bufs, *bufd; // Source, destination pointers
    int j = 0;

    bufs = pbase();
    bufd = buffer;
    if( page == 0 ) {
        newline( bufd, j );
    }
    for( int i = 0; i < cnt; i++ ) {
        char c = *( bufs++ );  // Gets character from source buffer
        if( c >= ' ' ) {       // Character is printable
            * ( bufd++ ) = c;
            j++;
            column++;
        }

else if( c == NL ) {            // Moves down one line
            *( bufd++ ) = c;    // Passes character through
            j++;
            line++;
            newline( bufd, j ); // Checks for page break, etc.
        }
        else if( c == FF ) {    // Ejects paper on formfeed
            line = line - line % LPP + LPP;
            newline( bufd, j ); // Checks for page break, etc.
        }
        else if( c == TAB ) {   // Expands tabs
            do {
                *( bufd++ ) = ' ';
                j++;
                column++;
            } while ( column % TABW );
        }
        else if( c == UND ) { // Responds to und manipulator
            pstring( uon, bufd, j );
        }
        else if( c == REG ) { // Responds to reg manipulator
            pstring( uoff, bufd, j );
        }
    }
    setp( buffer, buffer + 1024 ); // Sets new put area
    pbump( j ); // Tells number of characters in the dest buffer
}

// simple manipulators - apply to all ostream classes
ostream& und( ostream& os ) // Turns on underscore mode
{
    os << (char) UND; return os;
}

ostream& reg( ostream& os ) // Turns off underscore mode
{
    os << (char) REG; return os;
}

void hstreambuf::newline( char*& pd, int& jj ) {
// Called for each newline character
   column = 0;
   if ( ( line % ( LPP*2 ) ) == 0 ) { // Even page
      page++;
      pstring( "\033&a+0L", pd, jj );  // Set left margin to zero
      heading( pd, jj );                // Print heading
      pstring( "\033*p0x77Y", pd, jj );// Cursor to (0,77) dots
   }
   if ( ( ( line % LPP ) == 0 ) && ( line % ( LPP*2 ) ) != 0 ) {
   //  Odd page; prepare to move to right column
      page++;
      pstring( "\033*p0x77Y", pd, jj ); // Cursor to (0,77) dots
      pstring( "\033&a+88L", pd, jj );  // Left margin to col 88
   }
}

void hstreambuf::heading( char*& pd, int& jj ) // Prints heading
{
   char hdg[20];
   int i;

   if( page > 1 ) {
      *( pd++ ) = FF;
      jj++;
   }
   pstring( "\033*p0x0Y", pd, jj ); // Top of page
   pstring( uon, pd, jj );          // Underline on
   sprintf( hdg, "Page %-3d", page );
   pstring( hdg, pd, jj );
   for( i=0; i < 80; i++ ) {        // Pads with blanks
      *( pd++ ) = ' ';
      jj++;
   }
   sprintf( hdg, "Page %-3d", page+1 ) ;
   pstring( hdg, pd, jj );
   for( i=0; i < 80; i++ ) {        // Pads with blanks
      *( pd++ ) = ' ';
      jj++;
   }
   pstring( uoff, pd, jj ); // Underline off
}
// Outputs a string to the buffer
void hstreambuf::pstring( char* ph, char*& pd, int& jj )
{
    int len = strlen( ph );
    strncpy( pd, ph, len );
    pd += len;
    jj += len;
}

EXIOS204.CPP reads text lines from the cin object and writes them to the modified cout object.

// exios204.cpp
// hstream Driver program copies cin to cout until end of file
#include "hstream.h"

hstreambuf hsb( 4 ); // 4=stdprn

void main()
{
    char line[200];
    cout = &hsb; // Associates the HP LaserJet streambuf to cout
    while( 1 ) {
       cin.getline( line, 200 );
       if( !cin.good() ) break;
       cout << line << endl;
    }
}

Here are the main points in the preceding code:

  • The new class hstreambuf is derived from filebuf, which is the buffer class for disk file I/O. The filebuf class writes to disk in response to commands from its associated ostream class. The hstreambuf constructor takes an argument that corresponds to the operating system file number, in this case 1, for stdout. This constructor is invoked by this line:

    hstreambuf hsb( 1 );
    
  • The ostream_withassign assignment operator associates the hstreambuf object with the cout object:

    ostream& operator =( streambuf* sbp );
    

    This statement in EXIOS204.CPP executes the assignment:

    cout = &hsb;
    
  • The hstreambuf constructor prints the prolog that sets up the laser printer, then allocates a temporary print buffer.

  • The destructor outputs the epilog text and frees the print buffer when the object goes out of scope, which happens after the exit from main.

  • The streambuf virtual overflow and sync functions do the low-level output. The hstreambuf class overrides these functions to gain control of the byte stream. The functions call the private convert member function.

  • The convert function processes the characters in the hstreambuf buffer and stores them in the object’s temporary buffer. The filebuf functions process the temporary buffer.

  • The details of convert relate more to the PCL language than to the iostream library. Private data members keep track of column, line, and page numbers.

  • The und and reg manipulators control the underscore print attribute by inserting codes 0x02 and 0x03 into the stream. The convert function later translates these codes into printer-specific sequences.

  • The program can be extended easily to embellish the heading, add more formatting features, and so forth.

  • In a more general program, the hstreambuf class could be derived from the streambuf class rather than the filebuf class. The filebuf derivation shown gets the most leverage from existing iostream library code, but it makes assumptions about the implementation of filebuf, particularly the overflow and sync functions. Thus you cannot necessarily expect this example to work with other derived streambuf classes or with filebuf classes provided by other software publishers.