images

An Extended ms Macro Package

In the previous chapter, we’ve looked at some of the essential elements of a macro package-the innards that make it tick. However, few people will write a macro package just because they think they can do a better job at the basics than ms or mm. More often, users who need specific formatting effects will build a macro set to achieve those effects.

The macros used to produce this book are a good example of a custom macro package. They were developed to create a distinctive and consistent style for a series of books on UNIX by different authors. Although this macro package must of course do all of the basics we’ve talked about, many of its macros provide solutions to more specific problems. For example, there are macros for showing side-by-side before and after screens for vi and macros for inserting italicized commentary in examples.

To illustrate more concretely the issues that force you to create or significantly extend a macro package, this chapter will look at a set of extended ms macros for typesetting technical manuals. Extensions built into this package fall into two major categories:

images   Extensions that make it easier to control the appearance of a document, particularly the page size (described in the last chapter) and the style of section headings, tables, and figures.

images   Extensions that address needs of books, manuals, and other documents larger than the technical papers that ms and mm were originally designed for. These extensions include improved methods for handling tables of contents and indexes.

One of the chief weaknesses of the ms and mm packages is that they were designed for smaller documents. For example, ms does not provide table of contents generation, and the approach used by mm is suitable only for short documents. Neither package supports automatic index generation. In this chapter and the next, we will also look at ways to redress these problems.

▪   Creating a Custom Macro Package   ▪

In this chapter, we will present an extended macro package designed for technical documentation. Based on the ms macro package, these extensions were originally developed by Steve Talbott of Masscomp; they have been extended and altered during several years of use in our technical writing and consulting business. Because we needed to produce technical manuals for a number of different clients, we needed a macro package that allowed us the flexibility to achieve a variety of document formats.

An important step in implementing this package was to establish the relation of new and redefined macros to the original ms package. We wanted to read in the standard tmac.s package, and then simply overwrite or remove unwanted macros. Then we organized our extensions into three groups: redefinitions of standard ms macros, common macros we added to provide specific features or capabilities for all documents, and format macros that were most often used to control the appearance or structure of a document.

The format macros can be modified for the specifications of a unique document format. Each format design has its own file, and the user only needs to specify which of these formats are to be read in during the formatting run.

Following is a summary of the steps we followed to implement our mS macro package. While describing this implementation, we don’t pretend that it is unique or right for all uses; we do hope that it suggests ways to set up your own custom package.

1.   Create a new directory to store the macro files.

2.   Make a working copy of tmac.s and any subordinate files it reads in, moving them to a new directory.

3.   Create the tmac.Sredefs file to contain definitions of standard ms macros that we’ve redefined, such as .IZ.

4.   Create the tmac. Scommon file to contain utility and feature macros available in all formats. The list macros described in this chapter are kept here.

5.   Create separate files containing definitions for unique document formats.

6.   Set up tmac.S to control which files are read in and to handle certain parameters that might be set from the command line.

7.   Put tmac.S in /usr/lib/tmac, either by placing the file in that directory or by creating a tmac.S file that sources the tmac.S file in the macro directory.

The master file of this package is tmac.S, although it does not contain any macro definitions. It allows users to set some parameters from the command line, and then it reads in the standard ms macro package and the two files that contain redefinitions and common macros. Last, it checks the value of a number register (v) to determine which group of format macros are to be read in.

Here’s what our tmac.S file looks like:

. \"    tmac.S - the main format macro package
.                                             
.so  /work/macros/tmac.s           \" Read in standard ms
.so  /work/macros/tmac.Sredefs     \" Redefinitions of macros
.so  /work/macros/tmac.Scommon     \" Common utility macros
.                    \" Check register v for version
.                    \"   and read in special format macros
.ie  \nv \{\
.if  \nv=9  .so /work/macros/tmac.Stest
.if  \nv=8  .so /work/macros/tmac.Squickref
.if  \nv=7  .so /work/macros/tmac.Slarge
.if  \nv=6  .so /work/macros/overheads
.if  \nv=5  .so /work/macros/tmac.Straining
.if  \nv=4  .so /work/macros/tmac.Sprime
.if  \nv=3  .so /work/macros/tmac.Scogx
.if  \nv=2  .so /work/macros/tmac.Smanuals
.if  \nv=l  .so /work/macros/tmac.Snutshell\}
.el  .so /work/macros/tmac.Sstandard

The -r option to nroff and troff is used to select a particular version of the format macros. For instance, the first set of format macros is designed for producing our Nutshell Handbooks. To format a document using the macros defined in tmac.Snutshell, a user would enter:

$ ditroff -Tps -mS -rvl ch01 | devps | lp

One of the files, tmac.Stest, is available for use during the development and testing of new versions of the macros. We’ll look at some of the different formats later in this chapter.

A few other details about this implementation may help you customize a package. Both ms and mm include a number of Bell-specific macros that are not very useful for users outside of AT&T. For example, it is unlikely that you will require the various styles of technical memoranda used internally at Bell Labs. Unused macro definitions need not get in your way, but they do use up possible names and number registers that may conflict with what you want to do. The .rn macro allows you to rename a macro; .rm will remove the definition of a macro.

You may want to remove selected macros. For example, you might want to start the modifications to a macro package built on ms with the following request:

.rm TM IM MF MR EG OK RP TR S2 S3 SG IE [] ][ [. .] [o  \
      [c [5 [4 [3 [2 [1 [0 [< ]< [> ]> [- ]-

(Note the use of the backslash to make this apparent two-line request into a single long line.)

There is a slight performance loss in reading in a large macro package, and then removing a number of the macros. For efficiency, you’d be better off removing the undesirable macros from your copy of the ms source file.

Reading in tmac.Sredefs after tmac.s overwrites some of the standard ms macros with our own definitions. The standard versions are thus not available. If you want to retain a standard macro definition, you can make it available under a different name. Use the .rn request to rename the standard macro before overwriting its definition.

As discussed in the previous chapter, we redefined the .IZ macro to allow the setting of various page sizes. Because the standard .IZ is invoked from tmac.s at the start of the formatting run, we can’t simply overwrite its definition. We must either delete the standard .IZ macro definition or comment out its invocation. Then the new.IZ in tmac.Sredefs will be executed.

As you develop your own set of extensions, you will undoubtedly consider additional modifications. Appendix F lists the set of extended macros that we use. You may not need many of the specialized macros provided in this package. But it will show you how to build on an existing package and how easy it is to modify the appearance of a document.

▪   Structured Technical Documents   ▪

The ms and mm packages provide a number of macros to produce title pages, abstracts, and so on for technical memoranda. Subsections can be numbered or unnumbered.

Anyone who has used the UNIX Programmers’ Manual is familiar with the output of these packages. The technical papers collected in that volume bear superficial resemblance to the chapters of a book. However, they lack continuity-section, figure, and table numbers, where present, are relative only to the current section, not to the entire volume.

A macro package designed for producing technical books or manuals may need at least some modification to produce section headings. Chapter and section headings should make the structure of a document visible. In a nontechnical book, chapters are often the only major structural element. They divide the book into major topics, and give readers stopping points to digest what they have read.

Chapters are usually distinguished from a formatting point of view by a page break and some kind of nonstandard typesetting. For example, a chapter number and title may be set in large type, and the text may begin lower on the page.

In technical books and manuals, which are often not read straight through as much as they are used for reference, frequent section headings within a chapter give the reader guideposts. There are often several levels of heading—more or less depending on whether the book is intended primarily for reading or for reference. This book uses three levels of headings within a chapter, one for major changes in topic, the others for less significant changes.

Section headings can be distinguished merely by type font and size changes, as in this book, or by section numbering as well. Properly used, section numbers can be very helpful in a technical manual. They allow detailed cross references to different parts of the book without using page numbers. Referencing by page numbers can result in errors because page numbers are not fixed until the book is done.

Detailed breakdown of a chapter into subsections can also help the writer of a technical manual. Because a manual (unlike an essay or other free-form work of non-fiction) has definite material that must be covered, it can be written successfully from an outline. It is often possible to write technical material by entering the outline in the form of section and subsection headings and then filling in the details.

In this approach, numbered sections also have a place because they make the outline structure of the document more visible. In reviewing technical manuals, we can often identify many weaknesses simply by looking at the table of contents. Sections in a technical manual should be hierarchical, and the table of contents should look effective as an outline. For example, a chapter in our hypothetical Alcuin User’s Guide might look like this:

Chapter Two: Getting Started with Alcuin

2.1    Objectives o f this Session

2.2    Starting Up the System
2.2.1    Power-up Procedure
2.2.2    Software Initialization

2.3    Creating Simple Glyphs
2.3.1    Opening Font Files
2.3.2    Using the Bit Pad
2.3.2.1    The Cell Coordinate System
2.3.2.2    Pointing and Clicking
               .
               .
               .

How much easier it is to see the structure than in a case where the proper hierarchical arrangement of topics has not been observed. How often have you seen a “flat” table of contents like this:

Chapter Two: Using Alcuin


2.0    Starting Up the System
2.1    Power-up Procedure
2.2    Software Initialization
2.3    Creating Simple Glyphs
2.4    Opening Font Files
2.5    Using the Bit Pad
2.6    The Cell Coordinate System
2.7    Pointing and Clicking
               .
               .
               .

Even when numbered section headings are not appropriate, they can be a useful tool for a writer during the draft stage, because they indicate where the organization has not been properly thought through. For example, we often see manuals that start with a general topic and then describe details, without a transitional overview.

A macro package should allow the writer to switch between numbered and unnumbered headings easily. Both mm and ms do provide this capability, and we want to include it in our macros. However, we also want to include more flexibility than either of these packages to define the format of headings.

Because headings are the signposts to the book’s structure, changing their appearance can make a big difference in how the book is read. Different levels of headings need to stand out from the text to a greater or lesser degree, so that readers can easily scan the text and find the topic that they want.

The mechanisms for emphasis (in troff) are font and size changes, and the amount of space before and after a heading. Underlining and capitalization can also be used (especially in nroff but also in troff) for alternate or additional emphasis.

In our package, we include five levels of heading: a chapter-level heading and four levels of numbered or unnumbered subsection headings.

As described in the previous section, our custom macro package incorporates several different versions of the basic macros required to produce technical documents. In each version, the name of the heading macro is the same, but its definition is modified slightly to produce a different appearance. These different versions help us conform to the document styles used by our clients. Whenever we have a client who needs a new format, we customize the macro definitions, rather than add new macros.

The beauty of this approach is that the input macros the user needs to enter in a document are identical, or nearly so. Thus, we don’t increase the number of new macros that our users must learn, and it eliminates the recoding of existing documents to achieve a new format.

This approach is also useful when you support different types of output devices. Originally, our designs were developed for the HP LaserJet printer, which supports a limited set of fonts and sizes. When we purchased an Apple Laserwriter and Linotronic L100 typesetter, our formatting options increased, making available multiple fonts and variable point sizes. In an environment supporting multiple types of printers, you might want to adapt formats for specific printers.

The Chapter Heading

The chapter heading is in a class by itself, because it requires more emphasis than subsection headings, and because the macro that produces it may need to initialize or reset certain registers used within the chapter (such as section, figure, or table numbers).

In an arbitrary reversal of terminology, we call our chapter macro .Se (section). It could just as well be called .CH for chapter, but we use .Ch for a subsection heading (as we’ll see in a moment) and want to avoid confusion. In addition, this macro can be used for appendices as well as chapters, so the more general name seems appropriate.

The chapter heading has three major parts:

images   chapter-specific register initialization, including registers for section numbering, table and figure numbering, and page numbering

images   appearance of the actual chapter break

images   table of contents processing

Because this is a long macro definition, let’s look at it in sections.

.de Se             \" section; $1 = number; $2 = name;
.                  \" $3 = type (Chapter, Appendix, etc)
.                  \"
.                  \" 1. Number Register Initialization
.                  \"
.ie !"\\$1"" \ {.             \" Test for sect number
.     nr sE  \\$1             \" Assign to register SE
.     if !\\n(sE \{.          \" Test if not a numeric
.            .af sE A         \"  Handle appendices
.            if "\\$1"A"  .nr sE 1
.            if "\\$1"B"  .nr sE 2
.            if "\\$1"C"  .nr sE 3
.            if "\\$1"D"  .nr sE 4
.            if "\\$1"E"  .nr sE 5
.            if "\\$1"F"  .nr sE 6
.            if "\\$1"G"  .nr sE 7
.            if "\\$1"H"  .nr sE 8
.            if "\\$1"I"  .nr sE 9
.            if "\\$1"J"  .nr sE 10\}\}
.                              \" Only go as far as J
.el \{\
.     nr sE 0
.     tm Preface or if Appendix past letter J:
.     tm     Set number register s Eto position
.     tm     of that letter in the alphabet
.     tm     and alter register format:
.     tm      For Appendix K, enter:
.     tm       .Se K "Title"
.     tm       .nr sE 11
.     tm       .af sE A
.\}
.if \\n%>l .bp         \" Check if consecutive sections
.                      \" in same file and break page
.nr % 1                \" Nowreset page number
.nr PN 1
.af PN 1
.ie !"\\$1"" \{.       \" Test for sect number
.                       \" to set page number type
.     ds NN \\\\n(sE-\\\\n(PN
.     ds H1 \\n(sE        \" Set for subsection numbering
.     \}
.el \{
.     ds NN \\\\n(PN
.     nr sE O\}
.ds RF \\\\*(NN          \" Assign page number t o footer
.nr fG 0                 \" Initialize figure counter
.nr tB 0                 \" Initialize table counter

The macro first initializes a number of registers. Chapters are usually numbered on the first page, along with the title. If subsections are to be numbered, the chapter number is the root number for all headings. We need to take this number as an argument, and store it into a register for later use.

Because appendices are usually lettered rather than numbered, we also need to consider the special case of appendices. (This could be done with a separate macro; however, this package uses a single multipurpose macro.) The code for this is quite cumbersome, but works nonetheless: if the first argument to the macro is non-numeric, it is tested to see if it is one of the first ten letters in the alphabet. If so, a number is stored into the register, but the output format is changed to alphabetic.

If the argument is not a letter between A and J, a message is printed. This message is more verbose than you would generally want to use, but it is included to make the point that you can include detailed messages.

The macro next sets up the special page numbering scheme used in many computer manuals–the chapter number is followed by a hyphen and the page number (e.g., 1-1). This numbering scheme makes it easier to make last minute changes without renumbering and reprinting the entire book.

Finally, the macro initializes counters for automatically numbering figures and tables. We’ll see how these are used in a few pages.

The next portion of the macro is the part that is most variable—it controls the actual appearance of the chapter heading. This is the part of the macro that has led us to develop several different versions.

In designing chapter headings, let your imagination be your guide. Look at books whose design you like, and work from there. Three different designs we used on the HP LaserJet are shown in Figure 17-1. (These designs are a compromise between aesthetics and the capabilities of the output device.) This book is another model.

The macro for the first heading in Figure 17-1 is used as follows:

.Se 2 "Getting Started with Alcuin"

or:

.Se A "Summary of Alcuin Drawing Primitives" "Appendix"

The heading starts on a new page. If a third argument is not present, it is assumed that the section type is Chapter, and the section is labeled accordingly. An alternate section type can be specified in the optional third argument. This argument is usually Appendix but can be any string the user wants printed before the section number.

images

Fig. 17-1. Some Different Styles of Chapter Heading

The portion of the macro definition that creates the first heading in Figure 17-1

.\" Part 2 of Se Macro: Output chapter heading
.RT
.in 0
.lg 0                   \" Disable ligature before .tr
.                       \" Translate title to uppercase
.tr aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ
.sp
.na
.                       \" Test for section type argument
.ie !"\\$3"" .ds cH \\$3
.el .ds cH Chapter      \" Default is chapter
.                       \" If section number supplied
.                       \" output section number and type
.                       \" in 14 pt. bold.
.if !"\\$1"" \{\
\s14\f3\\*(cH \\$1\f1\s0
\}
.                       \" If no section number but
.                       \" there is a type (i.e., Preface)
.                       \" then  output section type
.if "\\$1"" .if !"\\$3""  \{\
\s14\f3\\*(cH\f1\s0
\}
.sp 5P
.                       \" Test for section title
.                       \" Print it in 14 pt. bold
.if !"\\$2"" \{\
\s14\f3\\$2\f1\s0
\}
.sp 6p
.ad b
.H1                     \" Draw line
.tr aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz
.sp 3
.ns                     \" Enable no-space mode

There are a couple of points you may want to note about this code:

images   The actual section title, as specified in the second argument, is forced to all uppercase using the .tr request.

images   The horizontal line under the title is drawn using a utility macro called .H1 (horizontal line), which simply draws a line the width of the page, less any indent that is in effect:

.de H1  \" Horizontal line. $1 = underline char
.br
\1'\\n(.lu-\\n(.iu\&\\$1'
.br
..

images   No-space mode is turned on at the end of the macro, to inhibit inconsistent spacing caused by users placing spacing requests or paragraph macros after the .Se macro. All of the heading macros use this technique because inconsistent spacing around headings will give the page an uneven look.

An alternate definition for this section of the macro follows. This code produces the second heading shown in Figure 17-1.

.\" Part 2 of Se Macro (Alternate):
.ad r            \" Right justified
.f1
.rs
.sp .75i         \" Move down from top
.                \" Section number in 24 pt. bold
.if !"\\$1"" \{\
\s24\f3\\$1\f1\s0\}
.sp 12p
.                \" Section title in 20 pt. bold
.if !"\\$2"" \s20\f3\\$2\fP\s10
.sp 12p
.                \" Optional 2nd line of title
.if !"\\$3"" \s20\f3\\$3\fp\s10
.sp 3
.ad b
.ns

This version is much simpler; it doesn’t print the section type at all, just the number or letter. However, because it prints a right-justified title, we have given the user the option of splitting a long title into two parts.

The final part of the macro (in either version) adds the section title to the table of contents. As was the case with .H1, this is done by an internal utility routine that is defined elsewhere. We’ll discuss how this works later.

.                    \" Last Part of Se Macro
.                    \" Now do toc
.tC \\$1 \\$2 \\$3
..

A Mechanism for Numbered Headings

Before we describe the lower-level headings used within a chapter, we need to explore how to generate automatically numbered sections. We have defined a version of the ms .NH macro that is called internally by our own heading macros. It has the same name and uses the same internal registers as the ms macro, but the font and spacing requests specified in the ms .NH macro are removed. All that this macro now does is generate the section number string.

.de NH                 \" redefine from -MS
.nr NS \\$1            \" Set NS to arg 1
.if !\\n(.$ .nr NS 1   \" Set NS to 1 if no arg
.if !\\n(NS .nr NS 1   \"  or NS is null or negative
.nr H\\n(NS +1         \" Increment Heading level register
.                      \" Test which level is in effect
.if !\\n(NS-4 .nr H5 0    \" then reset lower levels to 0
.if !\\n(NS-3 .nr H4 0
.if !\\n(NS-2. nr H3 0
.if !\\n(NS-1 .nr H2 0
.                         \" Put together section number
.if !\\$1 .if \\n(.$ .nr H1 1    \" Set first level
.ds SN \\n(H1                    \" Begin building SN
.ie \\n(NS-1 .as SN  .\\n(H2     \" == 1.1 2nd level
.el .as SN .                     \"   or == 1.
.if \\n(NS-2 .as SN  .\\n(H3     \" == 1.1.1     3rd
.if \\n(NS-3 .as SN  .\\n(H4     \" == 1.1.1.1   4th
.if \\n(NS-4 .as SN  .\\n(H5     \" == 1.1.1.1.1 5th
'ti \\n(.iu
\\*(SN                           \" Output SN string
..

This macro repays study, because it shows several clever ways to use number registers. First, the argument to the macro is placed into a number register. This register is then used to select which of a series of further registers will be incremented:

.nr NS \\$1
        .
        .
        .
.nr H\\n(WS +1

If the macro is called as .NH 1, register H1 will be incremented; if the call is .NH2, register H2 will be incremented, and so on. Then, depending on the value of that same NS register, the appropriate register value will be appended to the section number string SN.

Subsection Headings

In our package, we allow four levels of subsection headings, created by macros called .Ah (A head) through .Dh (D head). The macros for all four levels have the same essential structure; they differ only in the appearance of the printed text. Again, we have different styles for different clients.

The distinction between levels of headings in one of those styles is as follows:

images   The A head prints the heading in 14-point bold type, all uppercase, with 26 points of space above the heading and 18 points below.

images   The B head prints the heading in 14-point bold type, mixed case, with 23 points of space above the heading and 15.5 points below.

images   The C head prints the heading in 12-point bold type, mixed case, with 18 points of space above the heading and 12 points below.

images   The D head prints the heading in 10-point bold type, mixed case, with 18 points of space above the heading and none below. The heading actually runs into the text and is separated from it only by a period.

All levels of headings can be either numbered or unnumbered, depending on the state of a number register called nH. If nH is 0, headings are unnumbered; if it is 1, they are numbered.

Here is one version of the .Ah macro. From this example, you should be able to build the lower-level headings as well.

.de Ah            \" A-heading ; $1 = title
.sp 26p
.RT
.ne 8             \" Need room on page
.ps 14            \" 14 pt. on 16 pt, heading
.vs 16
.lg 0
.tr aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ
.bd I 4           \" Embolden italic font (optional)
\f3\c             \" Bold font; concatenate next input
.if \\n(nH \{.    \" if producing numbered heads
        ie \\n(sE .NH 2    \" If chapter (Se macro) is
.                          \" numbered, then 2nd level
.       el .NH 1\}         \" If not, 1st level head
\&\\$1\f1         \" Output title
.LP 0             \" Paragraph reset; (0 = no space)
.                 \"   RT resets default point size
.bd I             \" Turn off emboldening
.tr aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz
.lg
.sp 18p
.ns
.tC \\*(SN \\$1 Ah         \" Output TOC info
..

Some pointers: First, whenever you force capitalization with .tr, be sure to turn off ligatures, because they do not capitalize. Second, when you boldface a user-supplied string, it is wise to artificially embolden italics as well, in case the user embeds an italic font switch in the heading. Third, don’t forget to enter no-space mode to ensure consistent spacing following the heading.

As you can see, the .NH macro is called to generate a section heading only if the nH register has been set. In addition, the macro checks to make sure that a major section number has been specified by the .Se macro. As you may recall, .Se sets the first number in the numbered heading string (Hl). If .Se has been called, the subsection headings start at level 2, otherwise they start from the top.

To make it very easy for even novice users to specify whether they want numbered or unnumbered headings, the package includes a macro called .Nh (numbered headings) that turns numbering on or off:

.de Nh   \" Numbered headings; $1 = turn on (1) or off (0)
.        \" $1 = 2 will cause only A heads to be numbered
.nr nH \\$1
..

This is a matter of macro package style, as mentioned earlier. Steve Talbott’s style, when he initially developed this package, was to code everything as macros, even where the macro simply sets a number register or defines a string. This makes the package very easy to learn, because you can give a new user a concise, unambiguous list of macros to enter into a file.

Other examples of this style include the .Ti and .St (title and subtitle) macros, described in Appendix F, which simply define the ms RF and LF strings for running footers. Because of the mnemonically named macros, new users don’t have to remember whether the title goes in the right footer or the left, and so on. They simply enter the title of the book and chapter as arguments to the respective macros. The disadvantage is that users are insulated from an understanding of what is really going on, which may be an obstacle to learning more advanced skills.

An Alternate Definition

To give you an idea of how easy it is to change the look of a document by redefining a few macros, let’s look at how we could redefine the heading for this section. One popular layout style in technical manuals uses a very wide left margin in which only the headings are printed, as follows.

An Alternate Definition

To give you an idea of how easy it is to change the look of a document ...

Here’s the modified macro to produce this heading:

.de Ah                 \" A-heading; alternate version
.                      \" Requires resetting default page
.                      \" (PO) to allow for extra offset.
.                      \" .nr PO 2.5i for 1.5 extra offset
.nr Po 1.5i            \" Set amount of extra offset
.nr Gw .2i             \" Set width of gutter           
.mk                    \" Mark vertical position
.po −1.5i              \" Set new page offset
.ll \\n (Pou-\\nGwu
.ps 12                 \" Set 12 pt. on 14 pt.
.vs 14
\&\f3\\$1\f1           \" Output header in bold
.rt                    \" Return to vertical position
.po \\n(POu            \" Reset default page offset
.LP 0                  \" Reset point size and line length
.ns
.tC \\*(SN \\$1 Ah     \" Output TOC info
..

▪   Figure and Table Headings   ▪

In technical manuals, it is common to number and title all figures and tables, both for easy reference from within the text, and for collection into lists of figures and tables that will appear in the table of contents.

These macros are easy to construct and, apart from whatever appearance you decide to give them, nearly identical in content. There is a "start" macro and an "end" macro:

.de Fs              \" Start figure; $1= reserved space;
.                   \"               $2= F, floating figure
.RT
.if "\\$2"F" \{.    \" Figure can float
.     nr kF  1
.     KF\}
.if \\$1 \{.        \" Specify amount of space
.     ne \\$1       \"  required for paste-up
.     f1
.     rs
.     sp \\$1\}
..
.de Fe               \" Figure end; $1 = title
.sp
.bd I 3
.nr fG +1            \" Increment Figure counter
.                  \" then determine format
.ie \\n(Se .ds fG \\*(H1-\\n(fG
.el .ds fG \\n(fG
.ce                \" Output centered figure
\f3Figure \\*(fG.  \\$1\f1
.tC "\\*(fG" "\\$1" "Figure"
.bd I
.sp
.if \\n(kF=1 .KE    \" End keep if in effect
.tC "\\*(fG" "\\$1"  "Figure"  \" Output TOC info
..

As you can see, the .Fs (figure start) macro allows the user to reserve space for a figure to be pasted in, and for it to float to a new page, using the ms "floating keep" mechanism.

Neither of these options are necessary. The macro can simply bracket a figure created with pic, for example, in which case all that the macro provides is a consistent amount of space before the figure starts.

The .Fe (figure end) macro does most of the work. If a keep is in effect, .Fe terminates it. In addition, it prints the figure caption below the figure and adds a consistent amount of space below the caption. The figure is automatically numbered with the section number, and a figure number that is incremented each time the macro is called. As you may remember, this figure number register, fG, was initialized to 0 in .Se.

To give the user some options with figure numbering, a second argument allows the user to turn it off entirely. In addition, if the section is unnumbered, the section number and hyphen will be omitted. To accomplish this involves a little juggling of strings and number registers (which is something you should plan to get used to when you write macros). Notice that we use the string H1 for the section number rather than the section number register itself (sE), because we went to some trouble in the .Se macro to handle lettered appendices as well as numbered chapters.

You could easily add optional appearance features to this macro. For example, in one implementation, we draw a horizontal line above and below the figure, and print the caption left justified and in italics below the bottom line.

The figure end macro also calls the table of contents macro, which will be described later.

The macros for labeling tables are very simple, because the standard .TS and .TE macros do everything necessary except providing consistent pre- and post-spacing and printing the caption. In this case, the caption is at the top:

.de Ts                          \" Table start; $1 = title
.nr tB +1                       \" Increment Table counter
.                               \"   Determine format
.ie \\n(Se .ds tB \\*(H1-\\n(tB \" Section Table
.el .ds tB \\n(tB
.sp
.ce 2                           \" Output label and
\f3Table \\*(tB.                \" title on 2 lines
\&\\$1\f1
.tC "\\*(tB" "\\$1" "Table"     \" Output TOC info
.bd I
.LP                             \" Paragraph reset
..
.de Te    \" Table end -- no arguments
.RT                             \" Reset
.sp
..

▪   Lists, Lists, and More Lists   ▪

One of the significant features lacking in the ms macros is the ability to generate automatically numbered or lettered lists. You can use the .IP macro and number or letter a list yourself—but what good is a computer if it can’t handle a task like this?

One of the nicest features of Steve Talbott’s extended ms package is its set of comprehensive, general-purpose list generation macros. There are three macros: .Ls (list start), .Li (list item), and .Le (list end). Unlike mm, in which different types of lists must be specified using different macros, here you request a different type of list by giving an argument to the .Ls macro. You can request any of the types of lists in Table 17-1.

images

The bulleted list uses the bullet character (images) by default. However, as you will see, the macro allows you to specify an alternate bullet using an optional third argument. This "bullet" could be a dash, a box (\(sq)), a checkmark (\(sr),) or any other character.

Lists can be nested, and there is a default list type for each level of nesting, so the type argument does not really need to be specified.

Here’s the list start macro:

.nr 10 0 1      \" Initialize nested list level counter
.de Ls
.\" list start; $1 = A(LPHA), a(alpha), B(ullet), N(umeric),
.\"                  R(oman), r(oman);  $2 = indent
.\"                  $3 = alternate bullet character
.br
.if !"\\$1"A" .if !"\\$1"B" .if !"\\$1"N" .if !"\\$1"R" \
.       if !"\\$1"r" .if !"\\$1"a" .if !"\\$1"" \
.      tm Ls: Need A a B N R r or null as list type
.nr l\\n+(10 0 1
.ie "\\$1"" \{\                     \"Set defaults
.     if "\\n(10"1" .af l\\n(10 1   \"Numeric at 1st level
.     if "\\n(10"2" .af l\\n(10 a   \"1c alpha at 2nd level
.     if "\\n(10"3" .af l\\n(10 i   \"1c roman at 3rd level
.     if "\\n(10"4" .ds l\\n(10 \(bu\"Bullet at 4th level
.     if "\\n(10"5" .ds l\\n(10 \f3\-\f1\"Dash at 5th level
.     if \\n(10-5 .ds l\\n(10\(bu  \"Bullet above 5th level
.     if \\n(10-3 .nr l\\n(10 0-1 \}
.el \{\
.     if "\\$1"A" .af l\\n(10 A
.     if "\\$1"a" .af l\\n(10 a
.     if "\\$1"B"\{\
.          if "\\$3"" .ds l\\n(10 \(bu
.          if !"\\$3"" .ds l\\n(10 \\$3
.          nr l\\n(10 0-1\}
.     if "\\$1"R" .af l\\n(10 I
.     if "\\$1"r" .af l\\n(10 i \}
.ie ! "\\$2"" .nr i\\n(10 \\$2    \" List indent
.el .nr i\\n(10 5                 \" Default indent
.RS
..

When you first look at this macro, you may be a little overwhelmed by the complex number register names. In fact, there is not much to it.

One number register, 10, is used as a counter for nested lists. As you can see, this register is initialized to 0 outside of the list macro definition itself. Then, when the .Ls macro is called, this register is autoincremented at the same time as it is used to define the name of another number register:

.nr 1\\n+(10 0 1

It is this second number register interpolation—1\\n+(10—that is actually used to number the list. This is a technique we promised to show you back when we were first describing number registers. We create a series of related number register names by interpolating the value of another register as one character in the name.

Think this through for a moment. The first time  .Ls is called, the request:

.nr l\\n+(10 0 1

defines a number register that is actually called 11 (the letter l followed by the value of number register 10—which is 1). A second call to .Ls without closing the first list (which, as we shall see, bumps the counter back one) will define number register 12, and so on.

In a similar way, another series of number registers (i\\n(10) allows a different indent to be specified for each nested level, if the user so desires.

With the exception of the bulleted list, all of the different list types are numbered using the same number register (ln, where n is the nesting depth). The different types of lists are created simply by changing the output format of this register using the .af request.

Here’s the .Li macro:

.de Li  \" List item; $1 = 0 no blank line before item
.br
.if "\\$1"0" .ns
.ie "\\n (l\\n(10"-1". IP "\\*(l\\n(10" "\\n(i\\n(10"
.el \{\
.nr 1\\n(10 +1
.IP "\\n(l\\n(10." "\\n(i\\n(10" \}
..

The actual list counter itself (as opposed to the nesting counter) is incremented, and the appropriate value printed.

The number and the associated text is positioned with the standard ms .IP macro. If you don’t have access to the ms macros, you could simulate the action of the .IP macro as follows:

.de IP
.nr Ip 1
.sp \\n(PDu
.in \\$2u
.ti -\\$2u
.ta \\$2u
\\$1\t\c
..

However, there is one drawback to using an .IP-style macro as the basis of the list.

images   The .IP macro puts its argument at the left margin, as was done with this sentence.

images   Instead, we’d like something that puts the mark in the middle of the indent, as was done with this sentence.

Here’s the macro that produced the second example:

.de IP
.nr Ip 1
.sp \\n(PDu
.in \\$2u
.nr i1 \\$2/2u+\w'\\$1'   \" Amount to move left
.nr i2 \\$2-\w'\\$1'      \" Amount to move back
.ta \\n(i2u
.ti -\\n(ilu
\\$1\t\c
..

This version of the macro places the mark not just at a position half the depth of the indent, but exactly in the middle of the indent by adjusting the indent by the width of the mark argument. Number registers are used for clarity, to avoid nesting the various constructs too deeply.

(Note that this simplified .IP macro lacks some of the functionality of the ms .IP macro, which saves the current indent and therefore allows you to nest indents by using the .RS and .RE macros.)

If you are using ms, and you want to create a macro that puts the mark in the center of the indent, be sure to name this macro something other than .IP, so that you don’t conflict with the existing macro of that name.

Here’s the list end:

.de Le    \" List end; $1=0 no blank line following last item
.br
.rr 1\\n(10
.rr i\\n(10
.rm 1\\n(10
.nr 10 −1
.RE
.ie !\\n(10 \{\
.       ie "\\$1"0" .LP 0
.       el .LP\}
.el .if !"\\$1"0" .sp\\n(PDu
..

This macro removes the list numbering registers  and strings, decrements the nested list counter, and calls the ms .RE macro to "retreat" back to the left (if necessary because of a nested loop). Finally, it leaves a blank line following the end of the list. (As you might remember, PD is the ms register containing the paragraph distance—0.3v by default.)

▪   Source Code and Other Examples   ▪

In a technical manual, there are often further issues brought out by the need to show program source code or other material that loses essential formatting if it is set with proportional rather than monospaced type.

As previously discussed, the basic trick in ditroff is to use the Cw font. If you are using otroff, you will need to use the cw preprocessor (see your UNIX manual for details) or some other type of workaround. (When we were using otroff, our print driver allowed font substitutions based on size. We told the driver to use the printer’s constant-width font whenever troff used a point size of 11. Then, we wrote a macro that changed the point size to 11, but used .cs to set the character spacing to the actual size for the printer’s constant-width font. This was not a very elegant solution, but it worked—so if you are stuck with otroff, don’t despair. Put your ingenuity to work and you should come up with something.)

Besides the change to the CW font, though, there are several other things we’d like to see in a macro to handle printouts of examples. We’d like examples to be consistently indented, set off by a consistent amount of pre- and post-line spacing, and set in no-fill mode.

Here’s an example of a pair of macros to handle this situation:

.de Ps\" Printout start; $1 = indent (default is 5 spaces)
.br
.sp \\n(PDu
.ns
.nr pS \\n(.s \" Save current point size
.nr vS \\n(.v \" Save current vertical spacing
.nr pF \\n(.f \" Save current font
.nr PI \\n(.i \" Save current indent
.ps 8
.vs 10
.ft Cw
.ie !"\\$1"" .in +\\$1n
.el .in +5n
.nf
..
.de Pe \" Printout end; $1 non-null, no concluding
.br
.if "\\$1"" .sp \\n(PDu
.ps \\n(pSu
.vs \\n(vSu
.ft \\n(pF
.in \\n(pIu
.rr pS
.rr VS
.rr pF
.rr PI
.fi
..

The trick of saving the current environment in temporary registers is a useful one. The alternative is to use a separate environment for the printouts, but this assumes that the available environments are not already in use for some other purpose. You could also call a reset macro to restore the default state—but this may not actually be the state that was in effect at the time.

In addition, you shouldn’t rely on troff’s ability to return to the previous setting by making a request like .ll without any argument. If you do so, an error might result if the user has himself made an .ll request in the interim.

In short, you should either save registers or use a different environment whenever you change formatting parameters in the opening macro of a macro pair. Then restore them in the closing macro of the pair.

▪   Notes, Cautions, and Warnings   ▪

Another important macro for technical manuals is one that gives a consistent way of handling notes, cautions, and warnings. (Traditionally, a note gives users important information that they should not miss, but will not cause harm if they do. A caution is used for information that, if missed or disregarded, could lead to loss of data or damage to equipment. A warning is used for information that is critical to the user’s life or limb.)

Obviously, this is a simple macro—all that is required is some way of making the note, caution, or warning stand out from the body of the text. You could redefine the macro shown here in any number of ways depending on the style of your publications.

.de Ns \" note/caution/warning; $1 = type "N", "C", "W"
.sp 2
.ne 5
.ce
.if !"\\$1"N" .if !"\\$1"C" .if !"\\$1"W" \{\
. tm "Need N, C, or W as argument for Ns macro-using N"
\f3NOTE\f1\}
.if "\\$1"N" \f3NOTE\f1
.if "\\$1"C" \f3CAUTION\f1
.if "\\$1"W" \f3WARNING\f1
.sp
.ns
.nr nI \\n(.iu      \" Save current indent, if any
.nr nL \\n(.lu      \" Save current line length
.ie \\nS&0 .nr IN 5n\" Make indent less if in small format
.el .nr IN 10n      \" Larger indent for full-size page
.in +\\n(INu        \" Indent specified amount
.ll -\\n(INu        \" Decrement line length same amount
..
.de Ne \" "note end"; no args
.in \\n(nIu         \" Restore previous indent
.ll \\n(nLu         \" Restore previous line length
.rr nI              \" Remove temporary registers
.rr nL
.sp 2
..

A warning looks like this:

WARNING

You should be careful when reading books on troff, because they can be damaging to your health. Although escape sequences are allowed, they are not exactly high adventure.

A different version of a caution macro is shown below. It uses a graphic symbol to mark a caution statement.

CAUTION

images

One client had a convention of marking a caution statement with a large diamond in a square. These diamonds will appear in a second color in the printed book.

To produce the escape sequences to draw the symbol, we used pic, processing the description and capturing it in a file. Then we read it into our macro definition. (We could also have produced the escape sequences to draw the symbol without pic’s help; this would result in much more compact code.) The drawing of the symbol does take up most of the .Gc macro definition. Before we actually output the symbol, the current vertical position is marked. After it is output, we mark its bottom position. Then we return to the top before placing the warning label and processing the text. After the caution statement is output, the closing macro, .GE, checks the current vertical position against the bottom position of the symbol.

.de Gc \"Graphic Caution Macro
.ne 10
.mk a                    \" Mark current top position
.br                      \" pic output belongs here
\v'720u'\D'10u -720u'
.sp  -1
\D'1720u 0u'
.sp -1
\h'720u'\D'10u 720u'
.sp -1
\h'720u'\v'720u'\D'1-720u 0u'
.sp -1
\h'360u'\D'1360u 360u'
.sp -1
\h'720u'\v'360u'\D'1-360u 360u'
.sp -1
\h'360u'\v'720u'\D'l-360u -360u'
.sp -1
\v'360u'\D'1360u -360u'
.sp -1
.sp 1+720u                   \" End of pic output    
.sp
.mk q                        \" Mark bottom of symbol
.sp |\\nau                   \" Move back to top (.mk a)
.in +1.5i                    \" Indent to right of symbol
.ll -.5i                     \" Reduce line length
.sp .5v
.ce
\f3CAUTION\f1                \" Output Caution label
.sp .3v
.
.de GE                       \" Graphic Caution end
.br
.sp
.in                          \" Reset previous settings
.ll
.                            \" If bottom of symbol (.mk q )
.                            \" is below current vertical position
.                            \" then move to that position
.if \\nqu>\\n(nlu+\\n(.vu .sp |\\nqu
.sp .3v
..

▪   Table of Contents, Index, and Other End Lists   ▪

Here’s the part you’ve all been waiting for. One of the nicest things a formatter can do for a writer is automatically generate lists such as a table of contents and an index. These are very time consuming to produce manually, and subject to error. There are basically two ways to do the trick, and both apply to an index as well as a table of contents, endnotes, and other collected lists.

The technique used by mm, which generates an automatic table of contents at the end of each formatting run, is to collect headings into a diversion using the .da request. This diversion is then output from within a special macro called the "end macro," which we have yet to discuss.

The second technique is to use the .tm request to write the desired information to standard error output. Then that output is redirected to capture the messages in a file, where they can be edited manually or automatically processed by other programs.

The advantage of the first approach is that it is clean and simple, and entirely internal to the formatter. However, it is really suitable only for short documents. A long document such as a book is not normally formatted in a single pass, but chapter by chapter. It is not desirable to format it all at once just to get the table of contents at the end. In addition, a large document generally will end up creating a large diversion—often one that is too large for troff to handle.

The second approach, on the other hand, opens up all kinds of possibilities for integration with other tools in the UNIX environment. The output can be saved, edited, and processed in a variety of ways. As you can imagine from our philosophy of letting the computer do the dirty work, this is the approach we prefer.

However, there is still a place for diversions, so we'll take a close look at both approaches in the sections that follow.

Diverting to the End

Although we prefer to create our major end lists—the able of contents and index—by writing to stderr, we find it very useful to use diversions for another type of list.

We’ve added a couple of special macros that allow a writer to insert remarks intended specifically for the reviewers of a draft document or for personal use. Because technical reviewers frequently miss questions embedded in the text, we designed the .Rn macro to highlight notes. This macro makes these remarks stand out in the text and then collects them for output again at the end of the document.

.de Rn  \" Note to reviewers : $1 = Note
        \" Print note in text and at end
                        \" Output note first
.sp
\f3Note to reviewers:\fP \\$l
.sp
.ev 2
.da rN                  \" Then append into diversion
.sp 0.2v
.in 0
.ie "\\*(NN"" \(sq Page \\n(PN: \\$1
.el \(sq Page \\*(NN: \\$1
.br
.da
.nr RN 1                \" Flag it for EM
.ev
..

Another macro, .Pn, is used to collect a list of personal notes or reminders and output them on a page at the end. These notes do not appear in the body of the text.

.de Pn       \" Personal Note; $1= note
.            \" Note listed at end, but not in text
. ev2
.if \\n(pn&l .nr Pn 0 1     \" Set up autoincrement counter
.da pN
.br
.IP "\\n+(Pn." 5n
\\$1
.ie "\\*(NN"" (Page \\n(PN)
.el (Page \\*(NN)
.br
.da
.nr pN 1                    \" Flag it for EM
.ev
..

Only the .Rn macro produces output in the body of the document, but both macros append the notes into a diversion that we can process at the end of the document. The divert and append (.da) macro creates a list of notes that can be output by invoking the macro created by the diversion.

For each macro, we format the lists slightly differently. In the .Rn macro, we print a box character (images) (to give the feeling of a checklist), then the page number on which the review note occurred. This allows the reviewer or the writer to easily go back and find the note in context. In the .Pn macro, we use an autoincrementing counter to number personal notes; this number is output through .IP. It is followed by the note and the page reference in parentheses.

The formatting of text inside a diversion can be tricky. The text could be formatted twice: when it is read into the diversion, and when the diversion is output. The one thing to keep in mind is that you don’t want line filling to be in effect both times. If line filling is in effect when the text is read into the diversion, you should turn it off when the diversion is output. You can also use transparent output (\!) to hide macros or requests so that they will be executed only at the time the diversion is output. We have also taken the precaution of processing the diversion in a separate environment.

Now what about printing the list at the end? Well, as it turns out, nroff and troff include a special request called .em that allows you to supply the name of a macro that will be executed at the very end of the processing run, after everything else is finished.

The .em request allows you to define the name of a macro that will be executed when all other input has been processed. For example, the line:

.em EM

placed anywhere in a file or macro package, will request that the macro .EM be executed after everything else has been done. The definition of .EM is up to you.

The ms macros already have specified the name of this macro as .EM, the end macro. In its usual obscure way, mm calls its end macro .)q. If you are writing your own package, you can call it anything you like. You can either edit the existing end macro, or simply add to it using the .am (append to mucro) request.

All that ms does with this macro is to process and output any diversions that have not been properly closed. (This might happen, for example, if you requested a floating keep, but its contents had not yet been printed out.)

The end macro is a good place to output our own special diversions that we’ve saved for the end. What we need to do now is to add some code for processing our list of review notes:

.de EM
.br
.if \\n (RN=l \{\
\&\c
'bP
.
.
.ce
\f3NOTES TO REVIEWERS\f1
.sp 2
Reviewers, please address the following questions:
.sp
.ev 2
.nf
.rN
.ev
.\}
.if \\n(pN=1 \{\
.br
\&\c
'bP
.
.ce
\f3Notes To Myself:\f1
.sp 2
.ev 2
.nf
.pN
.ev
.\}
..

(Note: we have found that to print anything from the .EM macro in the standard ms package, it is necessary to invoke .NP explicitly following a page break. However, when using our simplified version of this package as shown in the last chapter, our .EM does not need a .NP.) The list collected by the .Rn macro is printed on a new page, looking something like this:

NOTES TO REVIEWERS

Reviewers, please address the following questions:

images Page 3-1: Why can’t I activate the bit pad before opening a font file?

images Page 3-7: Is there a size restriction on illuminated letters?

A Diverted Table of Contents

Given the preceding discussion, it should be easy for you to design a diverted table of contents. The magic .tC macro we kept invoking from our headings might look something like this:

.de tC  \" table of contents; $1=sect number;
.                             $2 = title; $3=type
.if "\\$3"\\*(cH"\{\
.da SL           \" Divert and append to section list
.sp 3
\\*(cH \\$1:   \\$2
.sp 1.5
.da
.\}
.if "\\$3"Ah"\{\
.da SL           \" Divert and append to section list
.sp 1.5
.da
.\}
.if "\\$3"Ah"\{\
.da SL           \" Divert and append to section list
.br
\\$1       \\$2\\a\\t\\* (NN
.br
.da
.\}
.if "\\$3"Eh"\{\
.da sL           \" Divert and append to section list
.br
\\$1     \\$2\\a\\t \ \ * (NN
.br
.da
.\}
.if "\\$3"Figure" \{\
.da fL           \" Divert and append to figure
\\$1  \\$2\\a\\t\\*(NN
.da
.\}
.if "\\$3"Table" \{\
.da tL           \" Divert and append to table list
\\$1  \\$2\\a\\t\\* (NN
.da
.\}
..

The diversion sL is set up to handle the main heading (chapter, appendix, unit, or section) and two levels of subheadings (A-heads or B-heads). The diversions fL and tL are set up to compile lists of figures and tables, respectively.

In the end macro, to print the table of contents, you have to cause a break to a new page, print: introductory captions, and so on, and then follow by outputting the collected diversion of each type. The following example shows the code to print:

.br              \" Automatically invoke diverted toc
\&\c             \" by including these lines in EM macro
'bP              \" Or place in own macro
.ta \\n(LLu-5n \\n (LLuR
.ce
\f3Table of Contents\fR
.sp 2
.nf               \" Process in no-fill mode
\\t\f3page\fP
.sL
.rm sL            \" Clear diversion
.                 \" Add code here to output figure
.                 \" and table list diversions

We set two tab stops based on the default line length (\n (LLu). The second tab stop is used to set a right-adjusted page number in the right margin. The first tab stop is used to run a leader from the entry to the page number. The escape sequences that output the leader and tab (\a and \t) were specified in the .tC macros. (And to protect the escape sequence inside a diversion an extra backslash was required.)

Now we can obtain a table of contents each time we format the document. The format of the table of contents shows the hierarchical structure of the document:

images

When Diversions Get Too Big

One of the major problems with collecting a table of contents in a diversion is that, with a large document, the diversions quickly grow too large for the formatter to handle. It will abort with a message like “Out of temp file space.”

The solution is to break up your diversions based on the number of entries they contain. One way to do this is to base the name of the diversion on a number register, and do some arithmetic to increment the name when the diversion has been added to a certain number of times.

For example, instead of just diverting to a macro called .sL, we could divert to one called xn, where n is a number register interpolation generated as follows:

.de tC
       .
       .
       .
.nr xX +1
.nr x0 \\n(xX/100+1
.da x\\n(x0
       .
       .
       .

Each time .tC is called, register xX is incremented by 1, and its value, divided by 100, is placed into another register, x0. Until the value of register xX exceeds 100—that is, until .tC has been called 99 times—x0 will be equal to 1. From 100 to 199, x0 will be equal to 2, and so on.

Accordingly, the actual macro into which output is diverted—represented as x\\n(x0—will first be xl, then x2, and so on.

When it comes time to output the collected entries, instead of calling a single diversion, we call the entire series:

.x1
.x2
.x3
.x4

Here, we are assuming that we will have no more than 400 entries. If there are fewer entries, one or more of these diverted macros may be empty, but there’s no harm in that. If there are more than 400, the contents of .x5 (et al) would still have been collected, but we would have failed to print them out. We have the option of adding another in the series of calls in the end macro, or rebuking the user for having such a large table of contents!

Writing to Standard Error

Although we’ve answered one of the objections to a diverted table of contents by the register arithmetic just shown, there is another, more compelling reason for not using this approach for large documents: there is no way to save or edit the table of contents. It is produced on the fly as part of the processing run and must be recreated each time you print the document.

For a very large document, such as a book, this means you must format the entire book, just to get the table of contents. It would be far preferable to produce the table of contents in some form that could be saved, so the tables from each chapter could be assembled into a single large table of contents for the entire book.

(Incidentally, producing a table of contents for a large document introduces some other issues as well. For example, you may want to have an overall table of contents that shows only top-level headings, and individual chapter table of contents that give more detail. Working out the macros for this approach is left as an exercise for the reader.)

The best way to produce a table of contents for a large book is simply to write the entries to standard error using .tm, and rely on an external program to capture and process the entries.

In ditroff, you can instead use the .sy request to execute the echo command and redirect the entries to a file. An example of this method might be:

.sy echo \\$1 \\$2\a\t\\*(NN >> toc$$

However, this approach causes additional system overhead because it spawns echo subprocesses. Also, because it does not work with otroff, we have used the more general approach provided by .tm.

Our .tC macro might look like this:

.de tC  \" Standard error; table of contents;
.       \" $1=sect number; $2=title; $3=type
.if "\\$3"\\*(cH"\{\
.tm ><CONTENTS:.sp 3
.tm ><CONTENTS:\\*(cH \\$1\\$2
.tm ><CONTENTS:.sp 1.5
.\}
.if "\\$3"Ah" .tm ><CONTENTS:\\$1   \\$2\a\t\\*(NN
.if "\\$3"Bh" .tm ><CONTENTS:\\$1     \\$2\a\t\\*(NN
.if "\\$3"Figure" .tm ><FIGURE:\\$1   \\$2\a\t\\*(NN
.if "\\$3"Table" .tm ><Table:\\$1   \\$2\a\t\\*(NN

Instead of diverting the section lists to separate macros from the lists of figures and tables, we send all entries out to standard error.

To capture this output in a file, we simply need to redirect the error output:

$ ditroff -Tps ... 2> toc

To do this, we will use our format shell script, which was introduced in Chapter 12, and will be revisited in the next (and final) chapter.

Because actual error messages might be present in the output, we prefix a label indicating the type of entry, for example:

><CONTENTS:
><FIGURE:
><TABLE:

It will be up to some outside program to separate the different groups of entries and subject them to further processing. We’ll use a sed script to separate the entries in the table of contents from the figure lists, table lists, and index entries. (In the next chapter, we’ll look at the post-processing of these entries.) Now let's look at a macro to generate index entries that will also be written to standard error.

Indexes

A simple index can be handled in much the same way as a table of contents. A macro for a simple index might look like this:

.de XX
.                    \" Section-page number set up
.                    \" by Se macro in string NN
.tm INDEX:\\$1\t\\*(NN
..

You might also want to have a macro that doesn't print the page number, but is just used for a cross-reference:

.de XN  \" Cross-reference Index entry, no page number
.tm INDEX:\\$1
..

You might also want a macro pair that will index over several different pages:

.de IS               \" Index macro
.                    \" Interpolate % for page number
.ie \\n(.$=1 .tm INDEX:\\$1, \\n%
.el \{\
.nr X\\$2 \\n%
.ds Y\\$2 \\$1 \}
.if \\n(.t<=1P .tm *\\$1* near end of page
.if \\n(n1<1.2i .tm *\\$1* near top of page
..
.de IE               \" Index end macro
.ie !\\n(.$=1 .tm IE needs an argument!
.el .tm INDEX:\\*(Y\\$1, \\n(X\\$1-\\n%
.if \\n(.t<=1P .tm *\\*(Y\\$1* near end of page
.if \\n(nl<l.2i .tm *\\*(Y\\$1* near top of page
..

The .IS macro prints out an entry, just like .XX. However, in addition, it saves the argument into a string, and takes a letter or digit as an optional second argument. This second argument is used to define a number register and string that will be saved, and not printed until the index and macro is called with the same argument. The index and macro print the starting number, followed by a hyphen and the current page number.

All of this discussion still avoids one major issue. The real trick of indexing is what you do with the raw output after you have it, because a great deal of sorting, concatenation, and reorganization is required to rearrange the entries into a meaningful order. Fortunately or unfortunately, this topic will have to wait until the next chapter.

Get UNIX° TEXT PROCESSING now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.