// t2psncol.C -- Ascii text to n-column Postscript.
// MR Jul 2001.
//
// Output a PostScript script/program that displays the input ascii text
// in n columns per page, in MONOSPACE (Courier) font.
//
// The only control characters are '\t' and '\n'; no backspace character.


#include <stdio.h>
#include <assert.h>
#include <stdlib.h>


//---------------------------------------------------------------------------
// Paper size stuff
//---------------------------------------------------------------------------

// Throughout we use the standard Postscript coordinates, which are in 
// "points" and have origin (0,0) in the bottom left corner of the paper.

// All distances are in ''POINTS'': 72 points = 1 inch = 2.54 cm.


class PrintPositionCoordinator
	{
	//
	// Settings passed to constructor
	//
	double xPaperwidth;	// Everything in points
	double yPaperheight;
	double xLRMar; // Left & right horizontal margins
	double xMMar;  // Middle horizontal margin between text-columns
	double yTBMar; // Top & bottom vertical margins
	int nchar_line; // # of characters in a line within a text-column
	int nCols; // # of text-columns in a page
	double fLineH_CharW;  // { line height / char width } factor
	double fFontSz_CharW; // { font size / char width } factor


	//
	// Calculated settings
	//
	double xCharwid;  // Width of character including inter-char spacing
	double yLinehigh; // Height of line including inter-line spacing
	double fontsize;

	double xColoffset; // Width of text-column plus inter-column margin
	double yLine0;     // y print position of top line

	int nLines; // # of lines that fits in height of paper


	//
	// Variables
	//
	// NB: iline, icol: all 0-based.
	int iline; // Current line number within text column
	int icol;  // Current text-column number within page
	

public:
	PrintPositionCoordinator(
		double paperwidth0,	// Everything in points
		double paperheight0,
		double lrMar0,
		double mMar0,
		double tbMar0,
		int nchar_line0,  // # of chars on line
		int ncol0,
		double fLineH_CharW,  // { line height / char width } factor
		double fFontSz_CharW ); // { font size / char width } factor

	double getfontsize( void ) { return fontsize; }
	int getnchar_line( void ) { return nchar_line; }
	
	double getXprintpos( void ); //Coords of start of current line
	double getYprintpos( void );

	bool nextline( void );
	   // Update print position.
	   // Return true if moved to new page, false if not.
	};


PrintPositionCoordinator::PrintPositionCoordinator(
	double paperwidth0,	// Everything in points
	double paperheight0,
	double lrMar0,
	double mMar0,
	double tbMar0,
	int nchar_line0,
	int ncol0,
	double fLineH_CharW0,   // { line height / char width } factor
	double fFontSz_CharW0 ) // { font size / char width } factor
	{
	assert ( ncol0 >= 1 );

	//
	// Store constructor arguments
	//
	xPaperwidth = paperwidth0;
	yPaperheight = paperheight0;
	xLRMar = lrMar0;
	xMMar = mMar0;
	yTBMar = tbMar0;
	nchar_line = nchar_line0;
	nCols = ncol0;
	fLineH_CharW = fLineH_CharW0;
	fFontSz_CharW = fFontSz_CharW0;

	//
	// Calculate settings dependent on constructor arguments
	//
	double xColwid = 
	   ( xPaperwidth - 2.0*xLRMar - (nCols-1)*xMMar ) / nCols;
	if ( xColwid <= 0.0 )
		{
		fprintf( stderr, 
		   "t2psncol ERROR: width of text columns <= 0\n" );
		exit( -1 );
		}

	xCharwid = xColwid / nchar_line;

	yLinehigh = fLineH_CharW * xCharwid;
	fontsize = fFontSz_CharW * xCharwid;

	xColoffset = xColwid + xMMar;
	yLine0 = yPaperheight - yTBMar;

	double yTexthigh = yPaperheight - 2.0*yTBMar;
	nLines = (int)( yTexthigh / yLinehigh ); // round down

	
	//
	// Initialize variables
	//
	iline = 0;
	icol = 0;
	}


double PrintPositionCoordinator::getXprintpos( void )
	// Return X-coord (printing position) of start of current line
	{
	return ( xLRMar + icol*xColoffset );
	}
double PrintPositionCoordinator::getYprintpos( void )
	// Return Y-coord (printing position) of start of current line
	{
	return ( yLine0 - iline*yLinehigh );
	}

bool PrintPositionCoordinator::nextline( void )
	// Update print position.
	// Return true if moved to new page, false if not.
	{
	iline++;
	if ( iline >= nLines )
		{
		iline = 0;
		icol++;
		if ( icol >= nCols )
			{
			// To new page
			icol = 0;
			return true;
			}
		}
	return false;
	}




//---------------------------------------------------------------------------
// PS code output 
//---------------------------------------------------------------------------


void printBeginDocument( FILE * fp,
	double fontpointsize )
	{
	fprintf( fp, "%%!PS-Adobe-3.0\n" );
	fprintf( fp, "%%%%DocumentNeededResources: font Courier\n" );
	fprintf( fp, "%%%%Pages: (atend)\n" );
	fprintf( fp, "%%%%EndComments\n" );

	//fprintf( fp, "%%%%BeginProlog\n" );
	//fprintf( fp, "%%%%EndProlog\n" );

	fprintf( fp, "%%%%BeginSetup\n" );
	fprintf( fp, "%%%%IncludeResource: font Courier\n" );
	fprintf( fp, "/Courier findfont\n" );
	fprintf( fp, "%g scalefont setfont\n", fontpointsize );
	fprintf( fp, "%%%%EndSetup\n" );
	}


int GLOB_pagenr = 0;

void printBeginPage( FILE * fp )
	{
	GLOB_pagenr++;
	fprintf( fp, "%%%%Page: %d %d\n", GLOB_pagenr, GLOB_pagenr );
	}
void printEndPage( FILE * fp )
	{
	fprintf( fp, "showpage\n" );
	}

void printEndDocument( FILE * fp )
	{
	fprintf( fp, "%%%%Trailer\n" );
	fprintf( fp, "%%%%Pages: %d\n", GLOB_pagenr );
	fprintf( fp, "%%%%EOF\n" );
	}



void printLine( FILE * fp, 
	double x0, double y0, 
	const char * buf, int n )
	{
	int i;

	fprintf( fp, "%g %g moveto (", x0, y0 );

	for ( i = 0; i < n; i++ )
		{
		switch ( buf[i] )
			{
		case '(': fprintf( fp, "\\050" ); break;
		case ')': fprintf( fp, "\\051" ); break;
		case '\\': fprintf( fp, "\\134" ); break;
		default: fprintf( fp, "%c", buf[i] ); break;
			}//switch
		}

	fprintf( fp, ") show\n" );
	}



//---------------------------------------------------------------------------
// LineBuffer
//---------------------------------------------------------------------------

//
// LineBuffer: Character buffer that automatically writes
//             its contents to output when buffer is full.
//
// Uses class PrintPositionCoordinator, and writes output
// to file 'fp' as PostScript, using the Postscript output funcs
// defined above.

class LineBuffer
	{
	char * pbuf;
	int ibuf; // # of chars in pbuf[]
	int linesize; // Length of array pbuf[], and max. # of chars
	              //  on a line.  Equal to return value of
	              //  'pppc->getnchar_line()'.

	FILE * fp;
	PrintPositionCoordinator * pppc;

public:
	LineBuffer( FILE * fp0, PrintPositionCoordinator * pppc0 );
	~LineBuffer( void );
	
	int getlinesize( void ) { return linesize; }

	int getnchar( void ) { return ibuf; }
	   // Return current # of chars in buffer from start of 
	   // line.  The buffer always contains the chars on the
	   // current line that still have to be written, te
	   // buffer contains all the chars from the start of the line.
	
	void flush( void );
	  // Write out the contents of the line buffer and set
	  // the buffer to empty.
	  // Also always advance to the print position for the
	   // next line (which may be on the next column, page).

	void outchar( char c );
	  // Append to buffer, flush when necessary.
	};


LineBuffer::LineBuffer( FILE * fp0, PrintPositionCoordinator * pppc0 )
	{
	fp = fp0;
	pppc = pppc0;

	// Init buffer
	linesize = pppc->getnchar_line();
	pbuf = new char[linesize];
	ibuf = 0;

	// Initialize the output document
	printBeginDocument( fp, pppc->getfontsize() );
	printBeginPage( fp );
	}

LineBuffer::~LineBuffer( void )
	{
	// Print the rest of the buffer
	if ( ibuf > 0 ) { flush(); }

	// Finish the output document
	printEndPage( fp );
	printEndDocument( fp );

	// Deallocate buffer
	delete[] pbuf;
	}


void LineBuffer::flush( void )
	{
	printLine( fp,
	   pppc->getXprintpos(),
	   pppc->getYprintpos(),
	   pbuf, ibuf );
	ibuf = 0;

	bool newpage = pppc->nextline();
	if ( newpage )
		{
		printEndPage( fp );
		printBeginPage( fp );
		}
	}
	
void LineBuffer::outchar( char c )
	{
	if ( ibuf >= linesize )
		{
		// Print buffered characters first
		flush();
		}

	// Now append new character to buffer
	pbuf[ibuf++] = c; 
	}




//---------------------------------------------------------------------------
// main()
//---------------------------------------------------------------------------

//
// Cmd line arg (option) values:
//
// (All sizes are in "points")

#define A4WIDE_PT 595.3
#define A4HIGH_PT 841.9

static double G_paperwidth = A4WIDE_PT;
static double G_paperheight = A4HIGH_PT;

static double G_lrMar_pt = 56.7; // Left & right margin = 2.0 cm
static double G_mMar_pt = 42.5; // Left & right margin = 1.5 cm
static double G_tbMar_pt = 56.7; // Top & bottom margin = 2.0 cm

static int G_nchar_line = 80; // # of chars on one line
static int G_ncol = 2; // # of text columns on page

static double G_fLineH_CharW = 2.0; 
static double G_fFontSz_CharW = 1.8;

static int G_ntab = 8;  // Tab width in # of chars

static bool G_v = false; // Verbose messages on stderr


void usage( void )
	{
	fprintf( stderr, 
	   "Usage: t2psncol [options] < txtfile > psfile\n"
	   "Options are: (xx = real value, nn = int value)\n"
	   "   Lxx    Set factor lineheight/charwidth (default %g)\n"
	   "   Fxx    Set factor fontsize/charwidth (default %g)\n"
	   "   Mxx    Use left & right margin of xx points (default %g)\n"
	   "          (NB: 72 points = 1 inch = 2.54 cm)\n"
	   "   wnn    nn characters per line (default %d)\n"
	   "   cnn    Print in nn columns (default %d)\n"
	   "   Tnn    Use tab with of nn characters (default %d)\n"
	   "   v      Verbose\n"
	   "Note: No '-' before option letter, and no space between\n"
	   "option letter and value.\n",
	   G_fLineH_CharW,
	   G_fFontSz_CharW,
	   G_lrMar_pt,
	   G_nchar_line,
	   G_ncol,
	   G_ntab );
	}

bool doArgs( int argc, char ** argv )
	{
	int i; 
	double a;
	int n;
	for ( i = 1; i < argc; i++ )
		{
		switch ( argv[i][0] ) 
			{
		case 'L':
		case 'F':
		case 'M':
			if ( sscanf( argv[i] + 1, "%le", &a ) != 1 ||
			   ! ( a > 0 ) )
				{ return 0; }

			switch ( argv[i][0] )
				{
			case 'L': G_fLineH_CharW = a; break;
			case 'F': G_fFontSz_CharW = a; break;
			case 'M': G_lrMar_pt = a; break;
				}
		break;

		case 'w':
		case 'c':
		case 'T':
			if ( sscanf( argv[i] + 1, "%d", &n ) != 1 ||
			   ! ( n > 0 ) )
				{ return 0; }

			switch ( argv[i][0] )
				{
			case 'w': G_nchar_line = n; break;
			case 'c': G_ncol = n; break;
			case 'T': G_ntab = n; break;
				}
		break;

		case 'v':
			G_v = true;
		break;

		default: 
			return false;
		break;
			}//switch
		}

	return true;
	}



int main( int argc, char ** argv )
	{
	int ic;


	//
	// Cmd line args 
	//
	if ( ! doArgs( argc, argv ) ) { usage(); return -1; }


	//
	// Initialize
	//
	PrintPositionCoordinator ppc( 
	   G_paperwidth, 
	   G_paperheight,
	   G_lrMar_pt,    //double lrMar0
	   G_mMar_pt,     //double mMar0
	   G_tbMar_pt,    //double tbMar0
	   G_nchar_line,  //int nchar_line0
	   G_ncol,        //int ncol0
	   G_fLineH_CharW,
	   G_fFontSz_CharW );
	
	if( G_v )
		{
		fprintf( stderr, "fontsize = %g\n", ppc.getfontsize() );
		}

	LineBuffer lbuf( stdout, &ppc );



	//
	// Filter the input
	//

	while ( ic = fgetc(stdin), 
	   ic != EOF )
		{
		switch( ic )
			{
		case '\t': // Tab
			do
				{
				if ( lbuf.getnchar() >= lbuf.getlinesize() )
					{ break; }
	
				lbuf.outchar( ' ' );
				}
			while ( lbuf.getnchar() % G_ntab != 0 );
		break;

		case '\n': // Newline
			lbuf.flush();
		break;

		default: // Normal character
			lbuf.outchar( ic );
		break;
			}//switch


		}//while



	return 0;
	}


