Insight Statistical Consulting Ltd
John Kirkpatrick MSc BSc (Hons) CStat CSci

Pagination and the ODS

Pagination is a difficult problem in the ODS, just as it is with any other method of output. In some ways pagination is even more of a problem in the ODS than it used to be in conventional ASCII listing files. Why? Because the ODS can create files which use proportional fonts. The space required to print different characters in a proportional font can be different. Here’s an example:

In an ASCII text file, it doesn’t matter whether text is written in upper or lowercase. It will always occupy the same space on the line:

THE QUICK BROWN FOX
The quick brown fox

With a proportional font, this is no longer true:

THE QUICK BROWN FOX
The quick brown fox

The length of the text will even depend on the font (all these examples use 10pt fonts)

THE QUICK BROWN FOX
The quick brown fox
The quick brown fox

and the point size - and maybe even on the order of the characters in the text. This means that determining where a long text will wrap within a narrow column is potentially difficult.

SAS avoids all these problems by passing responsibility for controlling pagination over to the word processor: this is why, by default, titles and footnotes appear in the header and footer sections of the word processor. This has a number of problems for us:

One potential solution to these problems, in the RTF destination at least, is to use the BODYTITLE option as we did when producing the graphs in Chapter 4. Unfortunately, this isn’t a generic solution. It will fail when a table is too long to fit on a single page. The titles appear on the first page, the footnotes on the last page. In between, the headers and footers are empty.

Actually, that’s not quite true. In SAS Version 8, if you use the BODYTITLE option with explicit pagination, TITLEs do appear on every page, but FOOTNOTEs do not. According to SAS, the presence of the TITLEs is the bug, not the absence of the FOOTNOTEs (SAS defect number S0184681).

You might think you could pre-process the data set to define a flag variable that indicated when a new page was needed, say by counting the number of observations since the last page break. This simple solution will indeed work when you can guarantee that each observation will fit on a single line (or any known constant number of lines). However, because SAS can’t determine in advance when a long variable text will wrap within a column, we can’t work out how many lines a single observation will take to print in a table row, and so we can’t determine in advance how many observations will fit on a page.

That’s not quite true. It could be done within SAS using only DATA step functions… but you’d need some sort of a lookup table that, for a given font, point size and character, returned the length required to render the character. I, for one, wouldn’t like to implement an accurate solution to the problem.

Fortunately, other applications are more graphically aware than BASE SAS. For example, both the Microsoft Windows API and Java (JDK1.3) have functions that return the required information in a single function call.

If we can get the information from one of these applications into SAS, the problem will be made a lot simpler. This could be done in a number of ways. The simplest is to create an ASCII file containing the table text and then pass it to the helper application using an X or SYSTASK command and then read an ASCII file created by the helper application back into the SAS job. Alternatively, the Java code could be wrapped in an SCL class and called directly. Finally, a more sophisticated, and therefore more controllable way, would be to use SAS/TOOLKIT to import the function directly into SAS.

If this all sounds a little esoteric, don’t worry: the SRSPaginator is a simple and reliable implementation.

In SAS 9, the DATA step JAVAOBJ statement will allow the Java code to be integrated directly into a DATA step, without the need for SAS/TOOLKIT or other more complicated solutions. This is now my method of choice. But because the JAVAOBJ statement is experimental in SAS 9.1, I still use a command line interpreter for production jobs.