Skip to content

Hackers Corner: Proportional Tables in PostScript

Vector 8:1, page 92

programming techniques graphics

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 92

My immediate problem was to output a decent looking table of figures from an APL application to a PostScript printer. Rather than chickening out and just using Courier (and hence falling into the category of someone who thinks WordPerfect is a wordprocessor), I thought it would be more fun to get APL to write a little PostScript program to do the hard work.

Here is the kind of output I was after:

Hello from TEST.TXT … the following lines are tabbed

Name Age Notes
Adrian 38 Well known troublemaker
Gill 38 Wife of well-known troublemaker
Richard 7 Generally a good child
Tim 5 Generally a bad child

The end.

The two problems to be solved were the simple one of lining up the columns in a proportional font, and the secondary issue of getting right justification on the numeric data.

The APL code to do this is mostly just a few globals to set up font name, size and pitch, as well as the page layout and tab positions. All the serious processing is done with a few PostScript routines, which go as a prologue on the front of the file:

%!PS-Adobe-2.0
%% Prologue for PostScript print
/inch {72 mul} def
/LM 1.00 inch def /BM 1.00 inch def /TM 1.00 inch def
/PW 11.25 inch def /PH 8.6 inch def

The ‘%’ is simply a comment, and the first line is a useful convention … several printers such as the Qume PS range will switch automatically between Epson, LaserJet and PostScript emulation … they recognise a PostScript file by the ‘%!’ at the beginning. The subsequent lines define a trivial function to convert from inches to points, and a bunch of globals to define the page size and margins.

Next we set up some tabs, measured in inches from the left margin. My simple convention is to make right tabs negative … if you want centred or decimal tabs you will have to devise something better!

/TABS [0 .5 -3 3.2] def /TB 0 def
/PITCH 20 def

Define Top Of Form as the Page Height less the Top Margin, and a null function to Orient the page (not required if we are in portrait mode, but essential for landscape … see below). Then define FormFeed as a function to print the current page, orient it and go to the top …

/TOF    { /VPOS PH TM sub def } def
/ORIENT {} def
/FF     { showpage ORIENT TOF } def

… and NewLine to set the typeball to the next tab, output any pending text, reset the tab bar to the first tab, and drop down Pitch positions. It checks if we have overstepped the bottom margin … if so we need a FormFeed:

/NL     { TAB /VPOS VPOS PITCH sub def  /TB 0 def
          VPOS BM le {FF} if } def

Now for the hard bit! The PostScript ‘get’ primitive is essentially APL’s array indexing, so read the first line of TAB as tb←TABS[TB] (zero origin, of course). The next line reads moveto (LM+inch |tb),VPOS (remember that negative tabs are right aligned, so the sign must be ignored at this stage); then we check that we have not fallen off the end of the tab rack, and if there is another tab to go, we increment the tab counter TB. (length is the PostScript equivalent of ⍴, but because we are in ⎕IO←0, we need to subtract 1.) Finally, we check the sign of the tab and either display the preceeding text directly (using show) or right align it (using our own rshow function)

/TAB    { TABS TB get /tb exch def
          tb abs inch LM add VPOS moveto
          TABS length 1 sub TB gt { /TB TB 1 add def } if
          tb 0 lt {rshow} {show} ifelse } def
/rshow  { dup stringwidth pop neg 0 rmoveto show } def

If the user has requested landscape printing, we can throw in a little function to rotate the paper and shift origin appropriately:

% Landscape mode requested
/ORIENT { PH 0 translate 90 rotate } def
ORIENT TOF

And finally, we select the requested font and display the data. Note that APL needs to make no allowance for pagination … all the hard work is done by the PostScript NL function which will allow for the specified margins and throw pages accordingly.

/Helvetica findfont 18 scalefont setfont
(Hello from TEST.TXT ... the following lines are tabbed) NL
() TAB (Name) TAB (Age) TAB (Notes) NL
() TAB (Adrian) TAB (38) TAB (Well known troublemaker) NL
() TAB (Gill) TAB (38) TAB (Wife of well-known troublemaker) NL
() TAB (Richard) TAB (7) TAB (Generally a good child) NL
() TAB (Tim) TAB (5) TAB (Generally a bad child) NL
() NL
(The end.) NL
FF

Note the final FormFeed to throw out the last page!

Simple lines are just wrapped up in an opening parenthesis and a ‘) NL’; anything between parentheses is treated as a text string, and hence is the argument to the NL function (remember that NL calls TAB to do the output!). Where we would logically insert tab characters in the text, we instead insert the string ‘) TAB (’. This conveniently takes account of the first tab at position zero (i.e. the left margin) by inserting a null string at the first tab … subsequent tabbed sections are output by the TAB function at the appropriate position on the page.

I’m afraid I wrote all this in a boring old first generation APL, and simply hard-coded the PostScript into my reporting function. The correct solution is obviously to write a general-purpose formatter that will take a nested argument (in the example a 3-element array consisting of two text vectors separated by a numeric vector) and the required tab positions, and will automatically generate the PostScript file ready to send to the printer.

It could even use a table of character widths to work out the optimum tab positions for itself (automatically left and right aligning text and numbers); this one is definitely left as an exercise for the reader … I will stick to the trial-and-error approach until I have a real need for something better.