[comp.text.tex] Multi.ps - 2 up printing

rossc@extro.ucc.su.oz.au (Ross Cartlidge) (02/24/90)

To everyone who asked for this:-

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	multi.ps
#	eg.sh
#	README.multi
# This archive created: Wed Feb 21 20:47:57 1990
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'multi.ps'
then
	echo shar: "will not over-write existing file 'multi.ps'"
else
cat << \SHAR_EOF > 'multi.ps'
%!PS-Adobe-1.0
%%Creator: Ross Cartlidge <rossc@extro.ucc.su.oz>
%%Title: Multiple pages on one page
%%CreationDate: Tuesday July 25 18:00:00 1989
%%Pages: 0
%%DocumentFonts:
%%BoundingBox: 0 0 0 0
%%EndComments
%
% Uncomment the next line if you wish to load multi into the "exitserver"
% state of the PostScript device
% serverdict begin 0 exitserver
%
%
%	make each operator to overlay a procedure so a bind in 
%	a prolog will not stop the overlaying by "multi"
%

[
	/gsave
	/grestore
	/grestoreall
	/initgraphics
	/initmatrix
	/currentmatrix
	/setmatrix
	% Path construction operators
	/initclip
	% Virtual memory operators
	/save
	% ones which needed special overloading
	/showpage
	/erasepage
	/copypage
	/restore
	% ignore these
	/letter
	/legal
	/a4
	/b5
	/lettersmall
	/note
]
{
%	if exists check if operator else define {}
	dup where
	{
		pop
% 		If operator then make into procedure
		dup load type /operatortype eq
		{
			1 array cvx dup
			0
			3 index cvx		% /n -> n
			put			% {}[0] -> n
			bind
			def
		}
		{
			pop
		}
		ifelse
	}
	{
		{} def
	}
	ifelse
}
forall

%
%	Initialise endmulti to execute an error
%
/endmulti
{
	count array astore /ostack exch def
	250 array execstack /estack exch def
	20 array dictstack /dstack exch def
	$error /newerror true put
	$error /errorname (No matching multi) cvn put
	$error /command (endmulti) put
	$error /ostack ostack put
	$error /estack estack put
	$error /dstack dstack put
	stop
}
bind
def

%
%	Put multiple logical pages on one physical page
%	until "endmulti" called
%	
%	landscape nrows ncols dividers multi -
%
%	landscape	boolean, if true divide page in landscape orientation
%	nrows		integer, number of logical pages down physical page
%	ncols		integer, number of logical pages across physical page
%	dividers	boolean, if true divide logical pages by lines
%
/multi
{
	currentdict
	64 dict begin
	/initdict exch def	% store initial dict for backward reference
	/dividers exch def
	/cols exch def
	/rows exch def

%
%	get size of current page
%
	initgraphics clippath pathbbox
	/Y exch def	% Max Y
	/X exch def	% Max X
	/y exch def	% Min Y
	/x exch def	% Min X
	/W X x add def	% Width of Page
	/H Y y add def	% Height of page

%	if landscape
	{
%
%		Note: x and y are reversed
%
		/w Y y sub def	% Width of imageable region
		/h X x sub def	% Height of imageable region
		/L		% Map to landscape
			-90 matrix rotate
			0 H matrix translate
			matrix concatmatrix
		def
		/O y x matrix translate def	% Move to origin
	}
	{
		/w X x sub def
		/h Y y sub def
		/L matrix def
		/O x y matrix translate def
	}
	ifelse

%
%	CTM (multi) = C x T x M x L x I
%	CTM (normal) = C x I
%	CTM (normal) = CTM (multi) x (T x M x L x I)-1 x I
%	M = (Scale rows/cols) x (Scale logical to physical) x
%		(Translate to physical clip origin
%	T = (Convert logical page to spot and physical)
%	L = (Convert to landscape)
%	I = Initial Physical CTM
%	C = Random transform on logical page
	/I
		matrix currentmatrix
	def
	/I_inv
		I matrix invertmatrix
	def

	/M
			w W div cols div
			h H div rows div
		matrix scale			%TMP
		O
		matrix concatmatrix
	def

%	matrix T <current T>
	/T
	{
		page# cols mod W mul
		rows page# cols idiv sub 1 sub H mul
		3 -1 roll translate
	}
	def

%
%	Utility functions
%	NB: *_t1 are temporary variables
%

%	matrix fromcanon <I-1 x T x M x L x I>
	/From_t1 matrix def
	/From_t2 matrix def
	/From_t3 matrix def
	/From_t4 matrix def
	/fromcanon
	{
		I_inv
		From_t1 T
		M
		L
		I
		From_t2 concatmatrix	
		From_t3 concatmatrix
		From_t4 concatmatrix
		3 -1 roll concatmatrix
	}
	def

%	/n {} mkmulti -
%	makes a new function called "n" in previous dict with:-
%		{}[0] = /n
%		{}[1] = currentdict
%		currentdict.n = prevdict.n
%
	/mkmulti
	{
		1 index dup load def	%define old val in current dict
		5 array cvx
		dup 3 4 -1 roll put	% A[3] = {}
		dup 0 3 index put	% A[0] = /n
		dup 1 currentdict put	% A[1] = currentdict
		dup 2 /begin cvx put	% A[2] = begin
		dup 4 /exec cvx put	% A[4] = exec
		initdict 3 1 roll
		put			% define initdict.n to multi function
	}
	def

%
%	path_to_proc {}
%		make proc represenation of current path
%
	/path_to_proc
	{
		{
			[
				/newpath cvx
				{ /moveto cvx}
				{ /lineto cvx}
				{ /curveto  cvx}
				{ /closepath cvx }
				pathforall
			]
			cvx
			exch pop
		}
		stopped
		{
			$error /errorname get /invalidaccess eq
			{
				cleartomark
				$error /newerror false put
				(%%Warning%% charpath in path - path nulled) =
				cvx exec
			}
			{
				stop
			}
			ifelse
		}
		if
	}
	def
	/path_def
	{
		{ currentpoint } stopped
		{
			$error /newerror false put
			{ newpath }
		}
		{
			/newpath cvx 3 1 roll /moveto cvx 4 array astore cvx
		}
		ifelse
	}
	cvlit def

%
%	Draw lines round logical pages
%
	/draw_dividers
	{
		initgraphics
		L concat
		M concat
		1 1 cols 1 sub
		{
			W mul
			dup
			0 moveto
			rows H mul lineto
		}
		for
		1 1 rows 1 sub
		{
			H mul
			dup
			0 exch moveto
			cols W mul exch lineto
		}
		for
		stroke
	}
	def

%
%	for each graphics operator which affects absolute state
%
	/M1 matrix def
	/M3 matrix def
	/M2 matrix def
	[
		/gsave
		/grestore
		/grestoreall
		/initgraphics
		/initmatrix
		/currentmatrix
		/setmatrix
		% Path construction operators
		/initclip
		% Virtual memory operators
		/save
	]
	{
		{
%			Save paths
			path_def path_to_proc
			clippath  { {} } path_to_proc

%
%			CTM <- CTM x Tocano (canon mode)
%
			M1 currentmatrix
			Tocanon
			M2
			concatmatrix
			setmatrix

%			Restore paths
			initclip exec clip
			exec

			load exec

%			Save paths
			path_def path_to_proc
			clippath  { {} } path_to_proc

%
%			CTM <- CTM x Fromcanon (Non canon mode)
%
			M1 currentmatrix
			Fromcanon
			M2
			concatmatrix
			setmatrix

%			Restore paths
			initclip exec clip
			exec
			end
		}
		mkmulti
	}
	forall

%
%	Define the operators which can't use the standard template
%
	/showpage
	{
		/page# page# 1 add def

%		Update the transform matrices
		page# npages eq
		{
			dividers
			{
				draw_dividers
			}
			if
			load exec	% the previous showpage
			/page# 0 def
		}
		{
			pop
		}
		ifelse
		/Fromcanon Fromcanon fromcanon def
		/Tocanon Fromcanon Tocanon invertmatrix def
		end
		initgraphics	% the new initgraphics
	}
	mkmulti

	/copypage
	{
		pop
		end
		gsave
		showpage
		grestore
	}
	mkmulti

	/erasepage
	{
		pop
		end
		gsave
		initclip
		clippath
		1 setgray fill
		grestore
	}
	mkmulti
	[
		/letter
		/legal
		/a4
		/b5
		/lettersmall
		/note
	]
	{
		{
			pop end
			(%%Warning%% Device change ignored) =
		}
		mkmulti
	}
	forall

%
%	Define restore separately as it affects the value of page#, etc
%
	/restore
	{
		pop
%		Push the values to restore after restore
		mark exch 	% put mark under -save-
		page#
		Fromcanon aload pop
		Tocanon aload pop

		counttomark -1 roll	% get -save- to the top
		restore

%		Restore popped values
		Tocanon astore pop
		Fromcanon astore pop
		/page# exch def
		pop	% mark

%		Save paths
		path_def path_to_proc
		clippath  { { } } path_to_proc

%
%		CTM <- CTM x Fromcanon (Non canon mode)
%
		M1 currentmatrix
		Fromcanon
		M2
		concatmatrix
		setmatrix

%		Restore paths
		initclip exec clip
		exec
		end
	}
	mkmulti
%
%	procedure to undo the effect of multi
%
	/endmulti
	{
		pop	% don't need /endmulti
		[
			/gsave
			/grestore
			/grestoreall
			/initgraphics
			/initmatrix
			/currentmatrix
			/setmatrix
			% Path construction operators
			/initclip
			% Virtual memory operators
			/save
			% ones which needed special overloading
			/showpage
			/erasepage
			/copypage
			/restore
			% ignore these
			/letter
			/legal
			/a4
			/b5
			/lettersmall
			/note
			%
			/endmulti
		]
		{
			initdict exch
			dup load 		% get old value
			put			% restore old value
		}
		forall
		page# 0 ne	% if not at new page show uncomplete page
		{
			dividers
			{
				draw_dividers
			}
			if
			showpage
		}
		if
		end
	}
	mkmulti

%
%	Set up in multi(non canon) mode
%
	/page# 0 def
	/npages rows cols mul def
	/Fromcanon matrix fromcanon def
	/Tocanon Fromcanon matrix invertmatrix def
	end
	initgraphics
}
bind
def
SHAR_EOF
fi
if test -f 'eg.sh'
then
	echo shar: "will not over-write existing file 'eg.sh'"
else
cat << \SHAR_EOF > 'eg.sh'
#!/bin/sh
cat multi.ps - <<-'!'
	% landscape mode, 1 row, 2 cols, no dividers
	true 1 2 true multi
	%
	/Helvetica findfont 12 scalefont setfont
	100 100 moveto
	save
	(This is Page One) show
	showpage
	restore
	save
	(This is Page Two) show
	showpage
	restore
	%
	endmulti
!
SHAR_EOF
chmod +x 'eg.sh'
fi
if test -f 'README.multi'
then
	echo shar: "will not over-write existing file 'README.multi'"
else
cat << \SHAR_EOF > 'README.multi'
This package is a PostScript prolog which when used with almost
any valid PostScript will output it with multiple logical pages
on each physical page. Each logical page will be identical (but smaller)
to the original result of each showpage.

Written By:

	Ross Cartlidge
	University Computing Service
	Building H08, Sydney University
	NSW, 2006, Australia

	rossc@extro.ucc.su.oz		Email
	+61 2 692 3495			Phone
	+61 2 660 6557			Fax

It works by overlaying each PostScript Operator which is affected by
or affects the absolute graphics state with a procedure which first goes back
to normal full page graphics state, does the operator, and
then restores the multiple page graphics state. Thus when "initgraphics", eg,
is executed its transformation of CTM, path and clippath is mapped
onto the multiple page representation. Also when a "save" is done the
full page version is saved, thus when "restore" is done - possibly many
logical pages into the future - the graphics state saved is transformed
into the new multi page.

All its local variables are stored in private (unnamed) directories
and the only "pollution" of the current directory is with the entry
and exit points "multi" and "endmulti".

It is written to be able to be included in the "exitserver" state
of the the server, thus it can be loaded before any other permanent
prologs which may use absolute graphics commands (like "showpage").

It works recursively so a "multi" can be called inside another "multi"
to any level (VM permitting).

It tries to use as little VM as possible but some is consumed in
restoring paths between multi and single page graphics states
so large documents may cause VMerror earlier than in normal representation
unless "save-restore" is used appropriately. 

USAGE:
	"multi.ps" defines 2 procedures:-
	
    (i)
	landscape nrows ncols dividers multi -

	Go into multi page representation

	landscape	boolean, if true divide page in landscape orientation
	nrows		integer, number of logical pages down physical page
	ncols		integer, number of logical pages across physical page
	dividers	boolean, if true divide logical pages by lines

   (ii)
	endmulti -

	End multi page representation


EXAMPLES:
	Display pages in "side by side" form

	% landscape mode, 1 row, 2 cols, dividers
	true 1 2 true multi
	%
	/Helvetica findfont 12 scalefont setfont
	100 100 moveto
	save
	(This is Page One) show
	showpage
	restore
	save
	(This is Page Two) show
	showpage
	restore
	%
	endmulti

To run this example type:-
	sh eg.sh | <your PostScript print command>

It's not a bad idea to load multi.ps in to the
"exitserver" state of your LaserWriter as then
it will work with any prologs you may be using.
Make sure you load it first! To load it into
the "exitserver" state see the comments at the start of multi.ps.
You then only need the :-
	true 1 2 false multi
line in code sent to the LaserWriter.


CAVEATS:
	If you have a proprietary font in you path
	or your clippath then the path will be lost
	when one of the overlayed operators is used.
	If you need the font in the path then either
	stop the overloading of that operator and
	assume its effect won't be used across logical
	page boundaries or change the postscript you are "multi-ing"
	to refer to the systemdict version of the operators
	where the effect is not used across page boundaries.
	
	EG: CHANGE
		(FRED true charpath gsave fill grestore stroke
	TO
		(FRED) true charpath systemdict begin gsave end
		fill systemdict begin grestore end stroke

	OR
		remove the overloading of gsave/grestore from multi.ps

	More memory is consumed than normal output

	Direct references to operators through "systemdict" will
	circumvent overlays

	Programs which rely on the overlayed named being operators
	and  not procedures will fail. For example:-
		/showpage { showpage } bind def
	will create an infinite loop when showpage is called.
	Also procedures directly on the stack do not execute
	unlike operators so the program:-
		/s [ /showpage load ] cvx def s
	will not work ( an "exec" is required after the "load")

	Device changing operators such as "letter", etc
	are mapped to null procedures
SHAR_EOF
fi
exit 0
#	End of shell archive
--
________________________________________________________________________
Ross Rodney Cartlidge			    |   rossc@extro.ucc.su.oz.au
University Computing Service, H08	    |   Phone:     +61 2 6923497
University of Sydney, NSW 2006, Australia   |   FAX:       +61 2 6606557

murthy@algron.cs.cornell.edu (Chet Murthy) (02/25/90)

WOW!

I LIKE it.
Better than anything I've tried yet.
--chet--
	--chet--
	murthy@cs.cornell.edu