25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music

C++ text file format and print program for GNU g++

cprint is an application for the formatted printing of source code or other text files. It has numerous options for setting lines-per-inch, lines-per-page, margin settings, tab expansion, and optional line numbering.

It is intended for a "text-only" type printer for which print modes are changed by embedding escape codes in the character stream. It has escape codes for my Toshiba P351 printer defined in it, but they should be fairly easy to customize for other text printers.

cprint.cpp

/*	cprint project, main.cc			1-25-2010	   GNU GCC g++
	Copyright (C)1996-2000, 2002, 2007, 2010 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License (GPL)
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

For printing C programs or other files to Toshiba P351 printer in various
formats. Creates a formatted .L text file, optionally with embedded escape
codes to manage print modes, which you can copy to printer.

Should be easily modified for other text only printers.
Its printer-specific code is the escape codes to activate different modes. 

I originally needed this program because CLIST.EXE can't omit line numbers, MSWord paginates
incorrectly, and DOSPrint only allows page widths of 80 or 132.
However, there are numerous existing Linux prettyprint programs.

------
TO DO:

--DoubleStrike option added 10/2002, untested.
--Allow pausing DURING printing, just after a FF, to allow realigning paper at page top.
  (To do it, you must READ the .L file, and print 1 line at a time, not copy to PRN.)
  (Keep note, but this isn't necessary if creepadjust feature works.)

------
NOTES:

If I change it to read the file input from stdin (to be usually redirected with <),
the user input can't be interactive, and must be handled with invocation
options. GNU module for handling and interpreting those is getopt.h (in C includes)
http://www.gnu.org/software/libc/manual/html_node/Getopt.html

But if the program is used as a filter, it can only process 1 file at a time, anyway, and FileArray is no use.
So how would you invoke for multiple files?
First, normally you wouldn't. Or (something like this):
find -iregex '.*\.[c]' -exec cprint < {} > {}.L\;
But that doesn't allow interactively setting options for the individual files, which is important.
You must be able to redo a file if the chosen options don't work well.
 
IF DESIRED, ADD ABILITY TO USE STDIN/OUT IF NO FILE WAS GIVEN.
THIS ISN'T REALLY WORTH CONVERTING EXCEPT TO GET FAMILIAR WITH GCC.
 
--Toshiba P351 printer page creep is about 1/64" per page average, not consistent on
  each page, and not always the same at the top and bottom of a page.  Ideas:
  1. Something has worn out, is not its original size, so it measures page length wrong.
  2. Logic is confused by line spacing that isn't 6lpi (No)
  3. I have a dip switch set wrong (probably not)
  4. Paper holes are off (probably not, but would explain why it happened a long
	 time ago (I think), then stopped, then reappeared).
  5. Paper isn't 11" long (No) (creep occurs on various paper types)
  6. Logic translates FF into a # of lines instead of a distance # of inches
  7. Temperature affects belt tightness or something else.
  8. Stepper motor is worn

*/
#include "../include/mylib.cpp"
#include "../include/filearay.cpp"

#define ESC	(27)

using namespace std;

int main(int argc, char** argv)
{
//----------------------------------------------------------------------------
// USER INSTRUCTIONS
// cerr << endl;
cerr << "Usage: CPRINT infile..." << endl;
cerr << "Output goes to infile.L (a text file)" << endl;
cerr << "Print the .L file with system commands: lp or lpr" << endl;
cerr << endl;
// Quit if no command line arguments. 
if(argc < 2)
{
	cerr << "No files to process.";
	return(0);
}	
//----------------------------------------------------------------------------
// GET THE FILES TO PROCESS, AND DISPLAY THE LIST.
FileArray FileList;
for(uint i = 1 ; i < argc ; i++)
{
	string s(argv[i]);
	FileList.AddFile(s);
}
cerr << "Files to be printed:" << endl << FileList << endl;
//presskey();
//----------------------------------------------------------------------------
// VARIABLES
// printer initialization strings for Toshiba P351.  
// Also see my.h, and think about how to handle Epson,
// too, although Epson resident fonts are so poor that the best method is to use cprint
// for formatting and tab expansion, but print the .L file from MSWord in a good font.
enum { ELITE12, COURIER10, CONDENSED };
string fontnames[3] = { "ELITE12", "COURIER10", "CONDENSED" };
char elite12[]   = { ESC,'*','1',ESC,'E','1','0',0 };
char courier10[] = { ESC,'*','2',ESC,'E','1','2',0 };
char condensed[] = { ESC,'*','0',ESC,'[',0 }; 

char DoubleStrikeOn[] = { ESC,'K','2',0 };		// for use when ribbon is weak
char DoubleStrikeOff[] = { ESC,'M',0 };

char printerreset[] = { ESC,26,'I',0 };
char lpi4[] = { ESC,'L','1','2',0 };
char lpi6[] = { ESC,'L','0','8',0 };
char lpi7[] = { ESC,'L','0','7',0 };			// actually 2/15", 7.5lpi
char lpi8[] = { ESC,'L','0','6',0 };
char backup[] = { ESC,'V','@','@','C',0 };		// reverse carriage 3/48"
char settopofform[] = { ESC, 'F','6','6',0 };   // also sets length to 11"

								// USER-SETTABLE PARAMETERS
bool pageheaders = false;		// THESE STARTING DEFAULT VALUES ARE FOR A TEXT FILE
bool linenumbers = false;
bool pagecreepadjust = false; 	// whether to adjust for Toshiba printer page creep
bool sendprintcodes = true;    	// whether to embed printer escape codes 
bool doublestrike = false;		// whether to print every line in bold (overstrike)
int font = COURIER10;			// default Toshiba P351 font
uint tabexpand = 5;				 
int leftmargin = 0;
uint rightmargin = 80;
uint linesperpage = 60;			// printable lines, not total form length
int pageno = 1;					// starting # for first page, then counts from there
int logline = 1;				// starting # for first line, then counts from there
int linesperinch = 6;
						// VARIABLES USED INTERNALLY
string lastfileext;		// extension of the previous file processed
//----------------------------------------------------------------------------
// PROCESS EACH FILE
while(FileList.GetItemsInContainer())
{
	//----------------------------------------------------------------------------
	// SET UP AND VERIFY FILE NAMES
	string infilename = FileList.GetNext();
	string headerfilename = infilename;
	ifstream infile(infilename.c_str());
	if(!infile)
	{
		cerr << "\aInput file " << infilename << " not found." << endl;
		presskey();
		continue;									// try next file in list
	}
	string infileext;                       		// file name extension used later
	if(stringcontains(infilename,"."))
	{
		string t = infilename.substr(infilename.rfind("."));
		infileext = toupper(t);
	}
	if(infileext == ".L")
	{
		cerr << infilename << ":" << endl;
		cerr << "\aFile with extension .L cannot be the input file." << endl;
		presskey();
		continue;
	}
	string outfilename = infilename;  					// output file same name
	if(stringcontains(outfilename,"."))					// strip extension
		outfilename.erase(outfilename.rfind("."));
	outfilename += ".L";								// add .L

	ifstream testfile(outfilename.c_str());				// .L file already exists?
	if(testfile)
		if(!yesno("Output file " + outfilename + " already exists.  Overwrite"))
		{
			cerr << endl << "\aWill not create listing for " << infilename << "." << endl;
			presskey();
			continue;
		}
	testfile.close();
	ofstream outfile(outfilename.c_str());
	
	//----------------------------------------------------------------------------
	// PRE-READ THE FILE SO WE CAN REPORT # OF LINES
	uint linesinfile = 0;   			// counts lines from the file, used later
	string s;
	while(getline(infile,s,'\n'))   	// the '\n' is required.
		linesinfile++;					// does this underreport by 1 line?
	infile.close();
	infile.clear();						// MUST clear flags before reusing infile.
	infile.open(infilename.c_str());  	// reopen to start fresh
	//----------------------------------------------------------------------------
	// SET DEFAULT PRINT FORMAT, SOME DEPENDING ON WHAT TYPE OF FILE IS BEING PRINTED
	// if the current fileext is same as the previous, carry those settings over.
	pageno = 1; logline = 1;					// these should always be reset
	if(infileext != lastfileext)
	{
		if((infileext == ".C") || (infileext == ".CPP") || (infileext == ".CC") || (infileext == ".H") ||
			(infileext == ".RH") || (infileext == ".RC") || (infileext == ".BAS"))
		{
			pageheaders = true; linenumbers = false; pagecreepadjust = false;
			sendprintcodes = true; font = ELITE12; tabexpand = 4; leftmargin = 0;
			rightmargin = 98; linesperpage = 71; linesperinch = 7;
		}
		else
			if((infileext == ".ASM") || (infileext == ".A"))
			{
				pageheaders = true; linenumbers = false; pagecreepadjust = false;
				sendprintcodes = true; font = ELITE12; tabexpand = 9; leftmargin = 0;
				rightmargin = 98; linesperpage = 71; linesperinch = 7;
			}
			else						// text .txt file or other
			{
				pageheaders = false; linenumbers = false; pagecreepadjust = false;
				sendprintcodes = true; font = COURIER10; tabexpand = 5; leftmargin = 0;
				rightmargin = 80; linesperpage = 60; linesperinch = 6;
			}
	}
	lastfileext = infileext;	// we're through with it.  current becomes previous.
	//--------------------------------------------------------------------------
	// DISPLAY AND ALLOW USER TO CHANGE FORMATTING SETTINGS
	while(1)
	{
		char choice = '0';
		
		cerr << "-----------------------------------------------------------" << endl;
		cerr << "Input file   : " << infilename << endl;
		cerr << "Output file  : " << outfilename << endl;
		cerr << "Lines in file: " << linesinfile << endl;
		cerr << endl;
		cerr << "0  " << "Done." << endl;
		cerr << "1  " << "Header each page :  " << (pageheaders ? "Yes" : "No") << endl;
		cerr << "2  " << "Line numbers     :  " << (linenumbers ? "Yes" : "No") << endl;
		cerr << "3  " << "Tab expansion    :  " << tabexpand << endl;
		cerr << "4  " << "Left margin      :  " << leftmargin << endl;
		cerr << "5  " << "Right margin     :  " << rightmargin << endl;
		cerr << "6  " << "Lines per page   :  " << linesperpage << endl;
		cerr << "7  " << "Font             :  " << fontnames[font] << endl;
		cerr << "8  " << "Page #s start at :  " << pageno << endl;
		cerr << "9  " << "Line #s start at :  " << logline << endl;
		cerr << "A  " << "Lines per INCH   :  " << linesperinch << endl;
		cerr << "B  " << "Page creep adjust:  " << (pagecreepadjust ? "Yes" : "No") << endl;
		cerr << "C  " << "Use printer codes:  " << (sendprintcodes ? "Yes" : "No") << endl;
		cerr << "D  " << "Doublestrike Bold:  " << (doublestrike ? "Yes" : "No") << endl;
		cerr << endl;
		cerr << "Enter choice: ";
		cin >> choice;
		choice = tolower(choice);
		string u;
		getline(cin,u);	// must discard anything extra up to and including the \n or it causes next getline() to fail
		cerr << endl; 
		
		if(choice == '0')
			break;				// Done.  break from while(1)
		switch(choice)
		{
			case '1': pageheaders = !pageheaders; break;
			case '2': linenumbers = !linenumbers; break;
			case '3': userinput("Tabs expand to how many spaces?",tabexpand); break;
			case '4': userinput("Enter left margin",leftmargin); break;
			case '5': userinput("Enter right margin",rightmargin); break;
			case '6': userinput("Enter printable lines per page",linesperpage); break;
			case '7':
				do 
				{ 
					userinput("Enter font, 0=ELITE12 1=COURIER10 2=CONDENSED",font); 
				}
				while((font < 0) || (font > 2));
				break;
			case '8':
				pageheaders = true;				// assume we want headers
				userinput("Enter page number for the first page",pageno);
				break;
			case '9':
				linenumbers = true;				// assume we want line numbering
				userinput("Enter line number for the first line in the file",logline);
				break;
			case 'a':
				do
				{
					userinput("LPI: 4=GraphPaper, 6=Normal, 7(7.5)=Readable, 8=Barely",linesperinch);
				}
				while((linesperinch < 4) || (linesperinch > 8) || (linesperinch == 5));
				break;
			case 'b': pagecreepadjust = !pagecreepadjust; break;
			case 'c': sendprintcodes = !sendprintcodes; break;
			case 'd': doublestrike = !doublestrike; break;
			default:
				cerr << "Invalid entry: " << choice << endl;
				break;
		}
	}				// while(1)
	//----------------------------------------------------------------------------
	// USE FILENAME ONLY (NO PATH) ON PAGE HEADERS?
	if(pageheaders)
	{
		cerr << "In page headers, file will show as: " << infilename << endl;
		if(yesno("Do you want to use a different name"))
		{
			// for some reason, userinput() wouldn't work for this string input in g++
			cerr << "File name to use <CR for " << headerfilename << ">: ";
			cin >> headerfilename;
		}
	}
	//----------------------------------------------------------------------------
	// CALCULATE MARGINS AND PAGE HEADINGS
	// column where file text begins.
	// when adding line numbers, filename aligns with body text, not the line numbers.
	// I allow ####__ for line numbers
	int lefttextmargin = leftmargin + (linenumbers ? 6 : 0);

	// build page title string
	time_t now;
	time(&now);
	string timenow(ctime(&now));			// time and date (we need its length)
	timenow.erase(timenow.rfind("\n"));		// get rid of auto-added /n
											// the 10 below allows for "  Page ###"
	int padding = rightmargin - lefttextmargin - headerfilename.length() - timenow.length() - 10;
	padding = max(padding,0);				// negative padding not allowed
	string title = string(lefttextmargin,' ') + headerfilename + string(padding,' ') + timenow + "  Page ";
	//----------------------------------------------------------------------------
	// SENDING DATA TO OUTPUT FILE STARTS HERE: PRINTER INITIALIZATION
	if(sendprintcodes)
	{
		switch(font)    			// font and size info
		{
			case ELITE12: 	outfile << elite12; break;
			case COURIER10: outfile << courier10; break;
			case CONDENSED:	outfile << condensed; break;
		}
		switch(linesperinch) 		// lines per inch
		{
			case 4: outfile << lpi4; break;
			case 6: outfile << lpi6; break;
			case 7: outfile << lpi7; break;
			case 8: outfile << lpi8; break;
		}
		outfile << (doublestrike ? DoubleStrikeOn : DoubleStrikeOff);
	}
	//----------------------------------------------------------------------------
	// READ AND PROCESS EACH LINE FROM THE FILE
	int pagecount = 0;						// # of actual physical pages in the printout
	uint physline = 1;  					// line count on the physical page
	string fileline;     					// a line read from the input file
	while(getline(infile,fileline,'\n'))
	{
		//----------------------------------------------------------------------------
		// EXPAND TABS BEFORE ANYTHING ELSE SO TAB STOPS RELATE TO RAW FILELINE.LENGTH()
		if(stringcontains(fileline,'\t'))
		{
			string temp;
			int count = fileline.length();
			for(uint i = 0 ; i < count ; i++)
				if(fileline[i] == '\t')
				{
					// if we're already on a tab stop, start towards the next one
					if((temp.length() % tabexpand) == 0)
						temp += ' ';
					while((temp.length() % tabexpand) != 0)
						temp += ' ';
				}
				else						// if it wasn't tab, just append the char
					temp += fileline[i];
			fileline = temp;				// we built it in temp; copy back to fileline.
		}
		//----------------------------------------------------------------------------
		// ADD LINE NUMBER, IF NEEDED
		if(linenumbers)
		{
			ostringstream os;
			os << setw(4) << logline << "  ";			// format the line number
			fileline.insert(0,os.str());				// and insert it at beginning
			logline++;                          		// increment LOGICAL line count
		}
		fileline.insert(0,string(leftmargin,' '));		// additional margin, if any
		//----------------------------------------------------------------------------
		// WRITE THE LINE TO THE OUTPUT FILE, WRAPPING AS OFTEN AS NEEDED
		// wrapped lines have no line numbers, and they align with the rest of the text
		do
		{
			if(physline > linesperpage)			// if at end of this page,
			{
				outfile << "\f";     			// form feed to next page
				// after every 4 pages, back up 3/48" and reset top of form to new pos
				if(pagecreepadjust && sendprintcodes)
					if((pagecount % 4) == 0)
						outfile << backup << settopofform;
				physline = 1;
				pageno++;
			}
			if(physline == 1)
			{
				pagecount++;
				if(pageheaders) 				// print page title header
				{
					outfile << title << setw(3) << pageno << endl << endl;
					physline = 3;
				}
			}
			string nextline;                  		// any text that overran page width
			if(fileline.length() > rightmargin)
			{
				nextline = fileline.substr(rightmargin); 		// save overrun portion,
				nextline.insert(0,string(lefttextmargin,' '));	// align with text
				fileline.erase(rightmargin);					// truncate current line,
			}
			outfile << fileline << endl;						// and print it
			physline++;
			fileline = nextline;                				// loop to process overrun.
		}
		while(fileline.length());
	}											// while(getline)
	outfile << "\f";                			// form-feed eject
	infile.close();
	outfile.close();
	//----------------------------------------------------------------------------
	// END. OPTIONALLY PRINT, AND OPTIONALLY DELETE THE .L FILE
	// Setting top-of-form manually makes it possible to print on single sheets.
	// If you align at the physical top of page and count lines from there,
	// you can't print on single sheets because the Toshiba pulls single sheets
	// through until there is a 1" top margin.
	cerr << "--------------------------------------------------------" << endl;
	cerr << "Align printhead on the line where printing should start." << endl;
	cerr << "That will be the top-of-form position." << endl;
	cerr << endl;
	cerr << "Output file " << outfilename << " has " << pagecount << " pages.  ";
	cerr << endl;
	cerr << "If printing is interrupted, edit the .L file to eliminate" << endl;
	cerr << "what's already been printed, then print the rest." << endl;
	cerr << "Don't use the an auto-tabbing editor," << endl;
	cerr << "and don't type tabs yourself. Use only spaces." << endl;
	cerr << endl;
	cerr << "You can use lpr to print the file." << endl;
	cerr << endl;
}						// while(FileList.GetItemsInContainer())
return 0;
}

Sample Output

These are a few lines of sample output with line numbering enabled. Wrapped lines do not have their own line numbers:

      test.cpp                                                  Mon Jan 25 19:32:05 2010  Page   2

  47  find -iregex '.*\.[c]' -exec cprint < {} > {}.L\;
  48  But that doesn't allow interactively setting options for the individual files, which is impo
      rtant.
  49  You must be able to redo a file if the chosen options don't work well.
  50   
  51  IF DESIRED, ADD ABILITY TO USE STDIN/OUT IF NO FILE WAS GIVEN.
  52  THIS ISN'T REALLY WORTH CONVERTING EXCEPT TO GET FAMILIAR WITH GCC.

 

Valid HTML 4.01 Transitional Valid CSS
Yahoo! Search
Search the web Search this site
View content labeling at ICRA.