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   Ads   Donate   Humor

Text file format and print program for Visual C++ .NET (CLR console)

Microsoft Visual C++ CLR console application for printing program 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, and has escape codes for my Toshiba P351 printer defined in it. These should be fairly easy to customize for other text printers.

There is no zip file. Copy the text and paste it into files of the names shown.

It makes reference to an optional textual "help" file in which you can keep instructions to yourself about which settings to use with which printers or paper sizes, etc. Sample text for that file (CPrint.txt) is on the page for the earlier Borland C++ 4.0 version from which this was converted.

cprint.cpp

/*	cprint.cpp			6-3-07
	MS Visual C++ CLR console application version
	Copyright (C)1996-2000, 2002, 2007 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. Should be easily modified for other text only printers. 

Needed because CLIST.EXE can't omit line numbers, MSWord paginates
incorrectly, and DOSPrint only allows page widths of 80 or 132.
Creates a formatted .L file, which you can COPY to printer,
or optionally prints it immediately, and optionally deletes it.

------
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:

--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 "stdafx.h"
#include "..\..\mylib\mylib.cpp"
#include "..\..\mylib\filearay.cpp"

#define ESC	(27)

using namespace System;
using namespace std;

int main(array<System::String ^> ^args)
{
//----------------------------------------------------------------------------
// USER INSTRUCTIONS
// cout << endl;
cout << "Usage: CPRINT  file1.ext  file2.ext  ...   [wildcards ok]" << endl;
cout << "Output goes to file1.L    file2.L    ..." << endl;
cout << "Print the .L file with: COPY /B filename PRN" << endl;
cout << endl;
// Quit if no command line arguments. 
// Apparently, the program's exe is no longer passed as argv[0] (args[0]).
// The first array element actually is the first command line argument.
if(args->Length < 1)
{
	Console::Write("No files to process. Press <Enter> to close the window...");
	Console::ReadLine();
	return(0);
}	
//----------------------------------------------------------------------------
// GET THE FILES TO PROCESS, AND DISPLAY THE LIST.
// (During testing in the VCPP IDE, Go to Project > Properties, 
// and add a file name (e.g. TEST.CPP) as the Command Arguments.)
FileArray FileList;
for(int i = 0 ; i < args->Length ; i++)
{
	string s;
	SystemStringToBasicString(args[i],s);
	FileList.AddFile(toupper(s));
}
cout << "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();
	toupper(infilename);					// string search fns used below are now case sensitive
	ifstream infile(infilename.c_str());
	if(!infile)
	{
		cout << "\aInput file " << infilename << " not found." << endl;
		presskey();
		continue;									// try next file in list
	}
	string infileext;                       		// file name extension used later
	if(stringcontains(infilename,"."))
		infileext = infilename.substr(infilename.rfind("."));
	if(infileext == ".L")
	{
		cout << infilename << ":" << endl;
		cout << "\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"))
		{
			cout << 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 == ".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;
		do
		{
		cout << "-----------------------------------------------------------" << endl;
		cout << "Input file   : " << infilename << endl;
		cout << "Output file  : " << outfilename << endl;
		cout << "Lines in file: " << linesinfile << endl;
		cout << endl;
		cout << "0  " << "Done." << endl;
		cout << "1  " << "Header each page :  " << (pageheaders ? "Yes" : "No") << endl;
		cout << "2  " << "Line numbers     :  " << (linenumbers ? "Yes" : "No") << endl;
		cout << "3  " << "Tab expansion    :  " << tabexpand << endl;
		cout << "4  " << "Left margin      :  " << leftmargin << endl;
		cout << "5  " << "Right margin     :  " << rightmargin << endl;
		cout << "6  " << "Lines per page   :  " << linesperpage << endl;
		cout << "7  " << "Font             :  " << fontnames[font] << endl;
		cout << "8  " << "Page #s start at :  " << pageno << endl;
		cout << "9  " << "Line #s start at :  " << logline << endl;
		cout << "A  " << "Lines per INCH   :  " << linesperinch << endl;
		cout << "B  " << "Page creep adjust:  " << (pagecreepadjust ? "Yes" : "No") << endl;
		cout << "C  " << "Use printer codes:  " << (sendprintcodes ? "Yes" : "No") << endl;
		cout << "D  " << "Doublestrike Bold:  " << (doublestrike ? "Yes" : "No") << endl;
		cout << "H  " << "View (or edit) HELP file" << endl;
		cout << endl << "Enter choice: ";
		choice = tolower(getche());
		cout << endl << endl;
		}
		while(!stringcontains(string("0123456789abcdh"),choice));
		if(choice == '0')
			break;				// Done.  break from while(1)
		switch(choice)
		{
			// case '0': return;	// left over from when this was a separate fn
			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;
			case 'h': 
				// This opens a text file in which you can keep any reference notes
				// you want concerning your various printers, their settings, etc. 
				_flushall();	//Help says: "You must _flushall() before calling system()"
				system("notepad.exe cprint.txt"); 
				break;
		}
	}				// while(1)
	//----------------------------------------------------------------------------
	// USE FILENAME ONLY (NO PATH) ON PAGE HEADERS?
	if(pageheaders)
	{
		cout << "In page headers, file will show as: " << infilename << endl;
		if(yesno("Strip the path and use the FILENAME ONLY"))
			infilename = FilePathParser(infilename).FileExt();
	}
	//----------------------------------------------------------------------------
	// 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
	string timenow;							// time and date (we need its length)
	SystemStringToBasicString(DateTime::Now.ToString(),timenow);	
											// the 10 below allows for "  Page ###"
	int padding = rightmargin - lefttextmargin - infilename.length() - timenow.length() - 10;
	padding = max(padding,0);				// negative padding not allowed
	string title = string(lefttextmargin,' ') + infilename + 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(int 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)
		{
			char buf[10]; 								// format the line number
			ostrstream(buf,sizeof(buf)) << setw(4) << logline << "  " << ends;
			fileline.insert(0,buf);						// 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.
	cout << "--------------------------------------------------------" << endl;
	cout << "Align printhead on the line where printing should start." << endl;
	cout << "That will be the top-of-form position." << endl;
	cout << endl;
	cout << "Output file " << outfilename << " has " << pagecount << " pages.  ";
	if(yesno("Print it now"))
	{
		string cmd = "copy /b " + outfilename + " prn";
		system(cmd.c_str());
		if(yesno("Delete " + outfilename + " now"))
			_unlink(outfilename.c_str());
		cout << endl;
	}
	else
	{
		cout << endl;
		cout << "If printing is interrupted, edit the .L file to eliminate" << endl;
		cout << "what's already been printed, then print the rest." << endl;
		cout << "Don't use the IDE (or any auto-tabbing) editor," << endl;
		cout << "and don't type tabs yourself.  Use spaces." << endl;
		cout << endl;
		cout << "Copy " << outfilename << " to the printer as a binary file:" << endl;
		cout << "    COPY /B " << outfilename << " PRN" << endl;
		cout << endl;
	}
}						// while(FileList.GetItemsInContainer())

Console::Write("Done. Press <Enter> to close the window...");
Console::ReadLine();
return 0;
}

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 07/26/2008.