Drawing Boxes

From Open Watcom

Jump to: navigation, search

Contents

Introduction

This page explores the box-drawing behavior of wgml 4.0. It includes the device library blocks that are used in the actual drawing as well as the control words and layout/document tags that are used to cause boxes to be drawn.

This initial version records what is known so far. Additional work is needed and will eventually be done.

The Required :BOX Block

The :BOX block, which is a required part of the :DEVICE block, defines the line-drawing characters and the font they are to be taken from.

The :ABSOLUTEADDRESS, :HLINE, :VLINE, and :DBOX Blocks

These blocks are all optional, but that does not mean that they can be used arbitrarily.

While attempting to get wgml 4.0 to interpret the :VLINE block, I discovered that :DRIVER block without an :ABSOLUTEADDRESS block but with the :HLINE, :VLINE, and :DBOX blocks would produce an "Abnormal program termination" -- but only when used to process a document specification which caused :VLINE to be interpreted.

This table shows every occurrence in the source files available to me:

Source File  :ABSOLUTEADDRESS  :HLINE  :VLINE  :DBOX
hpldrv.pcd   Yes
hplpdrv.pcd  Yes               Yes     Yes
hplpldrv.pcd Yes               Yes     Yes
mltdrv.pcd   Yes               Yes     Yes
psdrv.pcd    Yes               Yes     Yes     Yes
x2700drv.pcd Yes               Yes     Yes

No other source file available to me has any of these blocks in it.

Two obvious deductions can be drawn:

  • An :ABSOLUTEADDRESS block is a prerequisite the other three blocks.
  • The :HLINE and :VLINE blocks always appear together.

Section "15.9.15 VLINE Block" of the WGML Reference describes the intended context of :VLINE in this way:

The special symbols %y_size and %thickness are defined prior to 
processing the vline block. The symbol %y_size is set to the height 
of the vertical line, from the top edge to the bottom edge. The 
symbol %thickness is set to the value specified by the vline blocks 
thickness attribute. WATCOM Script/GML positions to the bottom left 
corner of the line before creating the rule line, and assumes the 
current point of the device is set to the top left corner of the 
line when finished.

In other words, a :VLINE block is expected to draw the line in an upward direction. Since a device that does not define :ABSOLUTEADDRESS has no ability to move the print position upwards (negative values of the attribute skip of the :NEWLINE block are not allowed, see the discussion here), it is not surprising that attempting to use :VLINE without :ABSOLUTEADDRESS causes a problem.

An :HLINE block, in contrast, is expected to draw the line from left to right, and so does not require :ABSOLUTEADDRESS -- provided wgml 4.0 always starts it on the correct line and does not attempt to go back up to print out text.

The :DBOX block starts at the bottom left corner and so would, in practice, have to go back up to draw the top and sides of the box, although it does not produce an "Abnormal program termination" in the absense of an :ABSOLUTEADDRESS block.

Clearly, which blocks are present depends on the characteristics of the device. Many devices have none of them, presumably because they produce their output one line at a time.

Control Word BX

This is an example of a four-section box drawn using control word BX:

.in 2
.tb set >
.tb 28
.bx 1 26 62
%binary(3)>the appropriate graphic appears
.br
%binary1(4)>the appropriate graphic appears
.bx
%binary2(5)>the appropriate graphic appears
.br;
%binary4(6)>the appropriate graphic appears
.bx off
.tb set
.tb
.in

Because control word BX only draws the box itself, control words IN and TB are also needed to position the text so that it appears within the box sections. This is quite common. There are, of course, many control words, and this example is not intended to exhaust the number of control words that may be useful in drawing boxes.

Control word BX always used the BOX characters with character devices. With line-drawing devices (or, at least, with PS), these rules apply:

  • The DBOX block is never used; if it is not defined, this has no effect on how the box is drawn.
  • If both of the HLINE and VLINE blocks are defined, they are both used.
  • If the HLINE block is not defined, then the BOX characters are used for the horizontal lines and no vertical lines appear, whether the VLINE block is defined or not.
  • If the HLINE block is defined, but the VLINE block is not defined, then horizontal lines (with no descenders) are drawn using the HLINE block but no vertical lines appear.

Thus, vertical lines only appear when both the HLINE and VLINE blocks are defined.

Box Scope

wgml 4.0 tracks whether a BX box is being processed or not; this gives two states, which will be called inside a box and outside of any box.

Processing begins outside of any box. In this state, these BX lines:

BX | BX ON | BX OFF | BX CAN | BX DEL | BX NEW | BX SET | BX CHAR

(with no column list) are all ignored.

When outside of any box, any BX line with a column list changes the state to inside a box. In this state, the BX lines have the effects shown in the next section.

A BX OFF line terminates the current box and reinstates any prior box if the current box was nested. If the box terminated was not nested, then the state reverts to outside of any box.

A BX OFF line with a column list outside of any box starts and terminates a box. That is, the horizontal line plus any stubs are drawn but then wgml 4.0 is outside of any box.

A box that is never closed continues to produce vertical lines to the end of the file. An open box from before the TITLEP tag will produce vertical lines all the way through the Index (with two passes). In PS, these lines extend into a top running title, the first footnote on a page, and generate a negative length at the end of the file; in TASA, they were seen to interact with a FIG in that the FIG appeared to cancel any columns from the BX box that the FIG used.

This may seem a little odd, but the fact is that the program has no way of knowing where the box ends except by processing a BX OFF line and so must presume that everything else it encounters in part of the box. The extended lines for the PS device and the interaction with FIG for the TASA device are interesting. This turned out to be a part of processing a new page. In multi-column text, presumably, it would be part of processing each column.

This section may require revision as control word BX is explored further.

The Box Operands

This section attempts to describe the normal or intended behavior of the box operands. A few notes on odd behavior are also provided.

Operand Lists

Control word BX is used in the Open Watcom documents with these operands (copied from the TSO):

<h1 </> ... hn>
CAN
NEW <h1 </> ... hn>
OFF <h1 </> ... hn>
ON <h1 </> ... hn>

These operands are not used in the Open Watcom documents but do work in wgml 4.0:

DEL
SET <h1 </> ... hn>

These operands are recognized by wgml 4.0, but nothing happens: the line is treated as if it was a comment:

CHAR <TRM|32T|TNC|38C|GPC|APL>
CHAR HEX

This operand appears in the Open Watcom documents, but only commented out. Since wgml 4.0 does not accept it (nor BEGIN, NO, PURGE, or YES, despite what the TSO says in its introductory sections), this is just as well:

END (commented out)

Column/Box Terminology

The current box is the box currently being drawn, as opposed to any prior boxes. This distinction only makes sense when operators NEW or SET are used.

The columns listed on a BX line are all down columns. They become up columns after that line is processed. Columns which are both "up" and "down" at the same time are called joins. However, only columns which are a part of the current box keep these designations.

Operators NEW and SET require additional types to handle the columns columns which are not a part of the box they define, that is, the new current box. A new column is a column which is hidden within the current box, but which ends vertical lines on the top of the box and starts them on the bottom. A hidden column is a column that is completely hidden when any line belonging to the box is produced. They are related: only new columns can become hidden, and only when another BX NEW or BX SET line is processed. An outside column is a column which is not part of the current box but which is also not overlaid by the current box.

A previous line is the set of columns used by the immediately preceding BX line. Note that the box stack consists, essentially, of previous lines, so that removing the top entry from the stack has the effect of returning an earlier previous line to use.

The current line is the set of columns provided with the BX line being processed. It may, of course, be empty, as, inside of a box, there is no requirement for any BX line to have a column list.

The box line is the column list to be used in processing the current BX line. It is the result of merging the previous line and the current line. It is used to produce, for character devices, the box element, which is nothing more nor less that a doc_element of type "text", and, for line-oriented devices, the HLINEs and VLINEs, which are doc_elements of types "hline" and "vline", respectively.

When a box line contains, or would contain if they existed, both "up" and "down" columns (counting a "join" as one of each) then it is said to separate two boxes, the first or old box (defined by the "up" columns and any "joins") and the second or new box (defined by the "down" columns and any "joins").

The BOX block defines the boxing characters; here are the names and the box-drawing characters available in the font used here:

bottom_join     ┴
bottom_left     └
bottom_right    ┘
horizontal line ─
inside_join     ┼
left_join        ├
right_join      ┤
top_join        ┬
top_left        ┌
top_right       ┐
vertical_line    │

(the left_join and vertical_line characters have been displaced so that they can be clearly seen).

The term stub is applied to an ascender that is not the full height of the box.

A down stub is a stub used by BX OFF for any columns appearing on its line. These would normally be drawn with a "top" character (top_left, top_right, or top_join), but are instead drawn with a "bottom" (bottom_left, bottom_right, or bottom_join) character by character devices; line-drawing devices produce a short vertical line pointing upwards.

An up stub is a stub used by BX (with no operand) and BX ON, in line-drawing devices only, to mark the "up" columns (but not the joins), which they overlay. There is no reason to believe that they are generated, in any sense, for character devices.

Starting a Box

These simple two-line boxes

.bx 1 26 62
.bx off

.bx on 1 26 62
.bx off

.bx new 1 26 62
.bx off

all produce the same box:

┌────────────────────────┬───────────────────────────────────┐
└────────────────────────┴───────────────────────────────────┘

Thus, all three start a box.

This simple two-line box

.bx set 1 26 62
.bx off

produces this box:

└────────────────────────┴───────────────────────────────────┘

where what is happening becomes clear if we insert ".sk 1" between the BX lines:

│                        │                                   │
└────────────────────────┴───────────────────────────────────┘

That is, SET is perfoming its normal action, given that there is no box already in existence, and also start a box. This suggests that ON and NEW are also performing their normal action, given that there is no box already in existence, and only appear to be ignored.

This simple one-line box

.bx off 1 26 62

produces this box:

└────────────────────────┴───────────────────────────────────┘

which nicely illustrates "down" stubs. Here, also, OFF is behaving normally, although there is no box for it to actually close, so it must be presumed to open as well as close a box in this case.

Since CAN and DEL cannot have a column list, and are ignored when outside of any box, they cannot start a box.

Default Operator

When BX is used without an explicit operator, it will be said to use the default operator. This operator provides a basic understanding of how BX works.

A simple three-line box

.bx 1 26 62
.bx
.bx off

produces:

┌────────────────────────┬───────────────────────────────────┐
├────────────────────────┼───────────────────────────────────┤
└────────────────────────┴───────────────────────────────────┘

If the second BX line is identical to the first

.bx 1 26 62
.bx 1 26 62
.bx off

then the same box is produced. Note: a line-drawing device will draw two separate sets of risers, one for the second BX line with a column list and one for the BX OFF line. When the second BX line has no column list, only one set of risers is drawn, when the BX OFF line is processed.

However, if the second line contains different columns, then the explanation of ON begins to make sense:

When a box definition is in effect and SCRIPT encounters a new box definition 
that is specified using only numeric operands, the horizontal rule that closes 
the first box and starts the second box is generated from the leftmost to the 
rightmost of the vertical positions of the two boxes.

Thus, this three-line box:

.bx 1 26 
.bx 32 62
.bx off

produces:

┌────────────────────────┐
└────────────────────────┴─────┬────────────────────────────────┐
                               └────────────────────────────────┘

And this three-line box:

.bx 32 62
.bx 1 26 
.bx off

produces:

                               ┌────────────────────────────────┐
┌────────────────────────┬─────┴────────────────────────────────┘
└────────────────────────┘

This set of commands:

.bx 1 26 62
.bx 1
.bx 26
.bx 62
.bx 1 26 62
.bx off 1 26 62

produces:

┌────────────────────────┬───────────────────────────────────┐
├────────────────────────┴───────────────────────────────────┘
└────────────────────────┐
                         └───────────────────────────────────┐
┌────────────────────────┬───────────────────────────────────┤
└────────────────────────┴───────────────────────────────────┘

which illustrates how the default operator can be used to produce boxes with unusual forms.

This three-line box

.bx 1 / 5 26 /32 62
.bx
.bx off

produces:

│   ┌────────────────────┐     ┌─────────────────────────────┐
│   ├────────────────────┤     ├─────────────────────────────┤
│   └────────────────────┘     └─────────────────────────────┘

This illustrates, on the left, the "box with only one vertical descender" referred to in the TSO and, in the middle and right, the effect of the vertical separator /, which need not have whitespace on both sides, although it must be preceded by whitespace. It also cannot be used with the first box position on a BX line, even if it is not the leftmost position on the resulting combined set of box column positions.

This three-line box

.bx 1 / 10 26 / 30
.bx 5 20 / 21 35
.bx off

produces:

│        ┌───────────────┐   │
└───┬────┴─────────┬┬────┴───┴────┐
    └──────────────┘└─────────────┘

The middle line shows how the two sets of column definitions are merged into one continuous line.

Operator ON

Operator ON can best be understood by reviewing the default operator.

Thus, the three-line box

.bx 1 26 
.bx on 32 62
.bx off

produces:

┌────────────────────────┐
└────────────────────────┘     ┌────────────────────────────────┐
                               └────────────────────────────────┘

and the three-line box

.bx 32 62
.bx on 1 26 
.bx off

produces:

                               ┌────────────────────────────────┐
┌────────────────────────┐     └────────────────────────────────┘
└────────────────────────┘

which shows the effect of operator ON. This effect only appears when there is a gap between the first and second boxes.

Using the / separator, as in this three-line box

.bx 1 / 10 26 / 30
.bx on 5 20 / 21 35
.bx off

produces

│        ┌───────────────┐   │
    ┌────┴─────────┬┬────┴───┴────┐   
    └──────────────┘└─────────────┘   

This shows that only the first segment of the line which starts further to the right produces a gap; compare this with the same box using the default operator in place of ON above.

If the BX ON line does not contain a column list

.bx 1 26
.bx on
.bx off

the resulting box

┌────────────────────────┐
└────────────────────────┘

appears to be ignoring one of the BX lines; however, if a column list is added to BX OFF

.bx 1 26
.bx on
.bx off 32 62

the resulting box

┌────────────────────────┐
└────────────────────────┘
                               └────────────────────────────────┘

now shows all three lines.

However, all is not as it may seem: since the next element is placed one line further down the page in the second instance, what is happening in that the BX ON with no column list is closing the box. The BX OFF with no column list is ignored; the BX OFF with a column list is processed normally.

When the / separator is used with operator ON with no column list, as in

.bx 1 / 10 26 / 30
.bx on
.bx off

wgml 4.0 produces this box:

│        ┌───────────────┐   │
│        └───────────────┴───┘

while our wgml produces this one:

│        ┌───────────────┐   │
│        └───────────────┘   │

In this case, I am inclined to regard our wgml as behaving correctly, and to treat the behavior of wgml 4.0 as a bug.

Operator NEW

These simple examples will illustrate the basic function of operator NEW:

This set of commands:

.bx 10 20 
.bx new 1 30
.bx off
.bx off

produces:

         ┌─────────┐
┌────────┴─────────┴─────────┐
└────────┬─────────┬─────────┘
         └─────────┘

which illustrates the "box on box" effect which can be produced by NEW, while this set of commnds:

.bx 1 30
.bx new 10 20 
.bx off
.bx off

produces:

┌────────────────────────────┐
│        ┌─────────┐         │
│        └─────────┘         │
└────────────────────────────┘

which illustrates the "box in box" effect which can be produced with NEW.

Both of the last two examples also illustrate nesting: that is, that BX OFF only closes the current box and then restores the prior box. There may be a limit on nesting, but I never encountered one, at least for boxes created by BX NEW with a column list.

Sadly, things are not that simple. This rather more formidable box:

.bx 13 40
.bx
.bx new 5 45
.bx
.bx off
.bx
.bx off

produces

           ┌──────────────────────────┐
           ├──────────────────────────┤
    ┌──────┴──────────────────────────┴────┐
    ├──────┬──────────────────────────┬────┤
    └──────┬──────────────────────────┬────┘
           ├──────────────────────────┤
           └──────────────────────────┘

with wgml 4.0 using the test device TEST01, but produces

           ┌──────────────────────────┐
           ├──────────────────────────┤
    ┌──────┴──────────────────────────┴────┐
    ├──────────────────────────────────────┤
    └──────┬──────────────────────────┬────┘
           ├──────────────────────────┤
           └──────────────────────────┘

when device PS is used; indeed, the PS device shows no signs of the "down" ticks marking the position of lines which are supposed to be overwritten by the new box, not even so much as a positional marker. Our wgml, however, produces the lower box in either case.

Adding one more level to the box causes the inmost box to hide the columns of the box two boxes above it completely. This makes sense since those columns neither begin nor end with the inmost box, unlike the box immediately above it.

For a character device an outer box's vertical ascenders must be drawn on each line; thus, for the second simple example above, the second line of the inner box is literally

 │        └─────────┘         │

This is done by separating the "outside" columns into their own line segment, which suppresses the horizontal characters by making them appear to be one-column boxes.

The PS device draws only VLINEs associated with a horizontal line belonging to the current box, which might suggest that the "outside" columns can be omitted. However, since different VLINEs, drawn as part of the same set, can have different heights, a field was added to the box column to contain the additional height, and that field is only available if the column is present. The special designator for "outside" columns was created to reconcile these requirements.

Our wgml produces the same output as wgml 4.0 for boxes such as those shown above. More complicated situations, including splits over pages, use of / to split various lines, use of NEW with no column list, have not been worked on. All of them should, eventually, be -- and any that are actually needed for the Open Watcom documents will be.

Work on a test file, originally created to test TB, drawn from the Open Watcom documents, shows that NEW is not only used in the documents, but used with the / horizontal split character as well. So that feature, at least, will need to be implemented in our wgml.

Operator SET

These boxes should be compared with the first two shown here.

This set of commands

.bx 10 20 
.bx set 1 30
.bx off
.bx off

produces:

│        ───────────         │
└────────┬─────────┬─────────┘
         └─────────┘

while this set of commands

.bx 1 30
.bx set 10 20 
.bx off
.bx off

produces:

┌────────┬─────────┬─────────┐
│        └─────────┘         │
└────────────────────────────┘

the second of which might actually be something worth doing in some contexts.

More complicated boxes, much to complicated to draw here, show much the same in character devices, where the vertical risers are output on each line, but with the PS device, something odd happens: the VLINES from the BX SET start one element-depth higher than expected.

If the previous output was an HLINE from, say, a BX command with no operator and no column list, then the VLINES appear to originate at the same vertical position as the HLINE. If, however, the prior line was a text line, then the vertical position is higher than that on which the text line was printed.

Exploring this use of element-depth only produced further puzzles: it was possible, in some cases but not all, to get the VLINE start positions correct, but then the subsequent elements were sometimes positioned properly and sometimes not. Attempts to correct this certainly helped clarify the code, but did not work in all cases. The criteria for when to do what was never clear: it could have been "only the first SET", "only the first included box, whether created by NEW or SET", or something else entirely. Since the Open Watcom documents do not use operator SET, work was halted at this point.

Operator OFF

Operator OFF performs several functions:

  1. It removes the top element from the boxing stack.
  2. If the stack is now empty, it sets the appropriate state flag to "outside of any box".
  3. It draws the final risers.
  4. It draws stubs for any columns listed after the BX OFF.
  5. It draws the horizontal line closing the box.

For character devices, this is pretty straitforward; indeed, the only difference between the stubs and the risers is that the risers meet descenders in the prior line, while the stubs to not.

For line-drawing devices, the risers will be of the appropriate height while the stubs will be one element-depth in height. However, at the top of a page, where the natural height of the VLINE would be "0", positional markers are output. Also, for operator OFF, the pattern of HLINE/VLINE output is unique: each individual HLINE segment is drawn, then its associated VLINEs are drawn, and then the next HLINE segment is drawn. Other operators draw the HLINE segments as a single entity and all the VLINEs as a separate entity.

When closing an internal box, character devices produce a box line with risers for each visible column as well as the characters closing the current box; line-drawing devices draw only the risers that meet the HLINEs being output.

Operators CAN and DEL

These are synonyms; either can be used with the same result.

Operators CAN and DEL perform many of the same functions as operator OFF:

  1. They remove the top element from the boxing stack.
  2. If the stack is now empty, they set the appropriate state flag to "outside of any box".
  3. They draw the final risers.

They cannot be used with a column list, and they do not draw HLINEs (or character-based box lines) at all.

Currently, operator CAN works about as well and shows many of the same problems (in complex boxes) as SET or NEW. While CAN is used in wgml 4.0, it is possible that it is being used as a precaution, to ensure any prior box is closed before proceding with the current box. This is, of course, not without consequence if a prior box is, in fact, open, so apparently it does exactly nothing.

Operator CAN creates a box with no bottom line. What possible use this may have I do not know.

Work on a test file, originally created to test TB, drawn from the Open Watcom documents, shows that CAN is indeed used in the documents to close interior boxes. So that feature, at least, will need to be implemented in our wgml.

Splitting Boxes Drawn By Control Word BX

Boxes drawn with BX are split vertically in the simplest possible way, keeping in mind that line-drawing devices have some peculiarities of their own.

For character-mode devices, the rules are:

  1. The text lines are split exactly as normal text is split.
  2. Each horizonal line is treated as a line of text (which, for a character device, it is).
  3. Blank lines at the bottom of a page are reduced in number to exactly the number needed to fill up the page; additional blank lines are ignored (that is, they do not appear at the top of the next page).

For line-drawing devices, or at least, for the PS device, things are not that simple.

  1. Vertical lines at the bottom of a page are drawn from the point where the horizontal line would be positioned, even if the first line is text. This means that text can appear slightly lower than the vertical lines. No attempt is made to cover an integral number of blank lines.
  2. Vertical lines at the top of a page are drawn to the point where the horizontal line would be positioned, even if the first line is text. This means that text can appear slightly higher than the vertical lines.
  3. When the last line of the box appears at the top of the page, the normal risers are not drawn, but positional markers do appear to mark where they are.
  4. The criterion for moving most horizontal lines to the next page are not the same as those for text lines.

To clarify that last point:

  1. Text lines are still split exactly as normal text is split.
  2. The first horizontal line is still treated as if it were a line of text.
  3. Intermediate horizontal lines and the final horizontal line must have more than the element-depth (rather than the height of the default font), plus any applicable skip, for that horizontal line to stay on the current page.

It is no longer clear when this works. Testing while working with operator NEW produced a box which began with these four lines:

before initial bx line (stack = 0)
.bx 13 40
after initial bx line (stack = 1)
.bx

Only the initial text line behaved as expected. The first BX line was not output unless there was enough room on the page for the following text line. The following BX line was similarly delayed. The effect is to add an additional "default-height" to the criteria for when to end a page, when a BX line is to be drawn. Note that this was correct behavior per wgml 4.0.

When the first BX line was changed to add the various explicit operators, only SET behaved as indicated above. The other operators required space for a second text line. When the second BX line was changed to add the various explicit operators, ... <<research continues>>

Horizontal splits are specified by the use of '/' in the box position list. When done sensibly, the effect is to draw two or more boxes adjacent to each other horizontally. Note that the use of '/' with NEW has yet to be implemented, which poses a problem as this does happen in the Open Watcom documents.

The Layout/Document Tags

There are three tags which can cause wgml to draw boxes if a particular attribute is given the value "box":

Tag             Type      Attribute
:FIG            Document  frame
:FIG            Layout    default_frame
:IXHEAD         Layout    frame

The :FIG tags pertain to figures; the :IXHEAD tag pertains to index headings.

The observed behavior (so far, with limited testing) is:

  • If :DBOX is present, then it is used.
  • If :DBOX is not present, but :HLINE and :VLINE are present, then :HLINE and :VLINE are used to draw the box for figures but not for index headings (no box appears at all around the index headings in this case). Note that :VLINE, in the limited testing performed, was only used here when it was used with .bx.
  • If neither :DBOX nor :HLINE/:VLINE are present, then the characters specified by :BOX are used for both figures and index headings.

It is surprising that :IXHEAD behaves differently from :FIG when :DBOX is not provided but :HLINE and :VLINE are.

This is the :FIG used for testing:

:FIG frame=box width='6i' place=inline.
%x_address() returns the current horizontal print position
%y_address() returns the current vertical print position
:FIGCAP.Sample caption with multiple words
:eFIG.

There are, of course, many other options possible, all of which the layout code will most likely need to implement. This page, however, is concerned with drawing boxes, not with other options.

Text Line Formatting

While exploring how text lines were output by wgml 4.0, boxing was tested using each of control word BX and tags FIG and IXHEAD. This produced results which turned out to be more generally applicable, and which are discussed to a very limited extent here; however, this section will accumulate more details as they are discovered.

If the box is drawn using BOX block characters then, whether the box is created by control word BX, tag FIG, or tag IXHEAD, each vertical line BOX block character added to the text is placed in its own text_chars instance. Horizontal lines containing no spaces are placed in a single text_chars instance.

The boxes produced by tag IXHEAD only contain a single character which, by necessity, is placed in a single text_chars instance.

When tag FIG is used, the text_line struct for the text itself contains one text_chars instance for each tab in actual use which controls all of the text starting at that tab stop, including the embedded spaces. This text is never right-justified, even when right justification is turned on. Text given with tag FIGCAP is processed normally.

Control word BX, as such, has no effect on output line formatting, as such. Instead, all of the normal factors (concatenation on/off, justification on/off, whether the current word will fit on any line or must be split, and so on). This can produce a variety of effects, which can be confusing; however, commenting out the BX control word lines (so that the box is not drawn) will show that the text is formatted the same way as it is when the box is drawn.

It usually happens that control word TB is used in conjunction with control word BX. This adds all of the effects associated with control word TB:

  1. Control word BR may not be needed, provided user tab stops are created and used in a way that causes a break to be generated without control word BR being present.
  2. Text following a tab character will, when appropriate, continue on the same line even if the right margin or even the right edge of the page is passed. For character devices, this causes the line to wrap; for the PS device (and probably all similar devices), the text is truncated at the right page edge when displayed.

The second point means that an extremely long "word" which contains no tab character is split at the right margin while the same "word", if preceded immediately by a tab character, is not split. It also applies to lines composed of words with spaces in between them.

For a character device, vertical ascenders are inserted into each text line included in the box. Each such ascender is placed in its own text_chars instance. The effect is that ascenders only appear when a space character would otherwise be produced.

However, this is not what is actually happening: instead, text_chars instances containing ascenders are only placed between the existing text_chars instances. If there is no "between" -- that is, if an existing text_chars instance writes anything, even a space character, in the position where a vertical ascender would normally be found, then no vertical ascender appears. This can easily happen when concatenation is turned OFF within the box.

For the PS device, and so presumably for any device using the VLINE block, the vertical line appears even if it ends up running through a text character.

Of course, in actual documents, such as the Open Watcom documents, each box would be designed so that all of the text was displayed in the correct location and none of it overlapped the vertical lines or ascenders.

Drawing Boxes

These section describes generally how boxes are drawn. PS and WHELP are taken as models of two different types of devices: line-drawing devices, which define the ABSOLUTEADDRESS block and at least the HLINE block and character devices, which use the characters and font prescribed by the BOX block, respectively. Since PS is the only line-drawing device available, it is possible that some aspects of the process described are actually PS-specific augmentations. There are several character devices available, and they all appear to work the same way; TASA was, in fact, used for testing as its output is less cluttered and generally clearer than WHELP's output is.

As noted above, for line-drawing devices, while tags FIG and IXHEAD will use the DBOX block if it is available, control word BX will not do so but will instead use the HLINE and VLINE blocks if the HLINE block is available. When control word BX uses the HLINE block, then it either uses the VLINE block (if it is available) or no vertical ascenders appear (if it is not available).

These notes are based on the behavior seen with the sample .bx and sample :FIG shown above.

Reference is made to the minimum left margin, which is computed as discussed here.

The term "insert" is used because, in our wgml, the elements are inserted into the current page, which is not output until it is full.

Using the BOX Characters

Drawing a box with the BOX block characters involves two steps:

  1. Insert any and all elements that are intended to appear in the box, with the vertical ascenders inserted into each line of each element.
  2. Insert the horizontal line.

Elements, such as GRAPHIC, which are replaced by blank lines in a character device will be replaced by blank lines containing the appropriate vertical ascenders, while elements like BINCLUDE will simply insert their data without any ascenders.

Using HLINE and VLINE

Drawing a box with the HLINE and VLINE blocks involves two steps:

  1. Insert any and all elements that are intended to appear in the box.
  2. Insert the horizontal line and vertical lines.

Since the horizontal and vertical lines are drawn independently of the box contents, any element that produces output visible within the box will be enclosed by the lines. GRAPHIC, in particular, will appear to be inside the box if positioned correctly.

Positioning the HLINE Output

The start position used with an HLINE block is adjusted both vertically and horizontally to be within the space occupied by the column or line, rather than at either boundary.

These are the formulas, which are used by wgml 4.0 so long as the default font is "0", which our wgml uses in all cases:

vertical-offset = current-font-line-height / 2
if( current-font-line-height % 2 ) {
    vertical-offset++
}
top-skip += vertical-offset
subs-skip += vertical-offset
element-depth = current-font-line-height - vertical-offset

The top-skip and subs-skip which are altered are those which are otherwise appropriate for the current HLINE. The effect is to draw the HLINE inside the space which would be occupied by a text line at the same position.

This is not affected by the LPI setting. Testing with different values of the number of vertical base units per inch did have an effect; however, further testing showed that this effect was actually the result of converting the font height, in points, into vertical base units, and not of the number of vertical base units per inch as such.

The horizontal position (which is based on the first box column only) is computed this way:

horizontal-offset = ((hbus/in)/10)/2

This is, in other words, one-half of what the width of a column is when the CPI is 10. The net horizontal position is then calculated using the formula:

net-horizontal-position = left-margin + box-column-position - horizontal-offset
if( net-horizontal-position < 0 ) {
    net-horizontal-position = horizontal-offset
}

and is then compared with the minimum horizontal position:

min-horizontal-position = left-margin + horizontal-offset

to produce the horizontal position for the HLINE:

horizontal-position = max( net-horizontal-position, min-horizontal-position )

Because the horizontal position used with the VLINE is computed differently, the horizontal and vertical lines drawn by the PS device can fail to meet properly in wgml 4.0, and in our wgml as well.

Positioning the VLINE Output

The start position used with a VLINE block is adjusted both vertically and horizontally to be within the space occupied by the column or line, rather than at either boundary.

The vertical position of a VLINE block is computed exactly the same way as it is for the HLINE block.

The horizontal position of a VLINE block is computed for each box column in turn. The computation is much simpler that that for the HLINE block: the value

net-horizontal-position = left-margin + box-column-position - horizontal-offset

is always used. This can result in a horizontal position that is not only different from that used with the HLINE but also which is less than the minimum left margin; indeed, it can be negative value which, being outside of the PS clipping window defined by the page edges, causes the line to not be drawn although the normal VLINE output appears in the PS file.

The length used by the VLINE blocks is increased by 10 hbus, which appears to be a constant. Examination of how the VLINE block was defined suggested that this was the thickness of the line, but further testing showed that it does not vary with the thickness, or with the CPI, or with the number of horizontal base units per inch.

Using DBOX

For the DBOX block, the sequence noted is very simple:

  1. Use the ABSOLUTEADDRESS block to locate the print head to the lower left corner of the box.
  2. Use the DBOX block to draw the box from that point.

Implementation Notes

Our wgml is intended to be a drop-in replacement for wgml 4.0 in the Open Watcom document build system. As such, it will be used with two devices (PS and WHELP). One of these devices (PS) provides all four of the ABSOLUTEADDRSS, HLINE, VLINE and DBOX blocks, and so boxes requested by tags IXHEAD and FIG appear and are drawn by wgml 4.0 using DBOX and the boxes specified by control word BX are drawn using HLINE and VLINE. The other device (WHELP) provides none of them, and so boxes requested by tags IXHEAD and FIG and the boxes specified by control word BX are drawn by wgml 4.0 using the characters and font prescribed by the BOX block.

Control Word BX

The implementation should, when finished recognize all the operands that wgml 4.0 does and treat them in the same way that wgml 4.0 does, provided, of course, that how wgml 4.0 behaves can be made clear enough to replicate. If not, then the implementation will have to do the best it can.

Output Patterns: TASA

Character devices use quite simple patterns.

The basic pattern is:

  1. If there are any, insert each line of the the box content, with text_chars instances containing the horizontal line character from the BOX block inserted in the proper positions, into the currnet page.
  2. Insert the horizontal line, in the form of a text_line with a single text_chars instance, into the current page.

When the box is split horizontally, the only change is:

The text_line used for the horzontal line now contains one text_chars instance for each separate 
horizontal line.

When the box is split vertically, the only change is:

The number of blank lines emitted is reduced, if necessary, to exactly the number needed to fill
up the current page so that the next item, a box content or a horizontal line, will be assigned 
to the next page in the normal manner.

When BX CAN or BX DEL appears inside a box, the only change is:

A blank overprint line (a text_line with no text_chars intances) replaces the horizontal line, and 
it is still an overprint line even if it occurs at the top of a page.

Note that, normally, an overprint line at the top of a page becomes the first line of the page, and does not overprint anything.

Output Patterns: PS

Line drawing devices, or, at least, PS are rather more complicated than character devices.

The actual output consists of the result of interpreting the ABSOLUTEADDRESS, HLINE, and VLINE blocks provided by the device driver. However, the order in which these blocks are interpreted is controlled by the order in which the HLINE and VLINE document elements are inserted into the current page. "HLINE" and "VLINE" will be used to refer to either the blocks or the document elements, or both, as the context requires.

Boxes that fit entirely on a single page will be considered first, as they provide the basic framework for drawing boxes.

For a simple boxes, that is, boxes of the form:

.bx c1 c2 ... cn
.bx
.bx off

the pattern is:

  1. The first BX line produces a single ABSOLUTEADDRESS block followed by an HLINE block.
  2. The second BX line produces these actions:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
  3. The VLINE blocks are drawn in conjuction with the BX OFF line, and so their height is the entire height of the box, computed as noted above.
  4. The BX OFF line produces these actions:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
    3. Generate one VLINE block per box column
      1. When the first VLINE block is output, a single ABSOLUTEADDRESS block is output first.
      2. When each subsequent VLINE block is output, two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, are output first.

If all three lines have the same column numbers given explicitly, that is, the box is of the form:

.bx c1 c2 ... cn
.bx c1 c2 ... cn
.bx off c1 c2 ... cn

the pattern is:

  1. The first BX line produces a single ABSOLUTEADDRESS block followed by an HLINE block.
  2. VLINE blocks are drawn in conjuction with both the second BX line and the BX OFF line, and their height is the height to the prior BX line, computed as noted here.
  3. The subsequent BX line produces these actions:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, followed by one VLINE block; this is done for each box column.
    3. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
  4. The BX OFF line produces these actions:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
    3. Generate one VLINE block per box column
      1. When the first VLINE block is output, a single ABSOLUTEADDRESS block is output first.
      2. When each subsequent VLINE block is output, two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, are output first.

For each BX line between the first BX line and the BX OFF line, what happens depends on whether or not that BX line was followed by a column list:

  • If the list is present, then the VLINES are produced followed by the HLINE, and the height is the height to the most immediately preceding BX line which produced VLINEs (or to the top of the box, if no VLINEs have, as yet, been drawn).
  • If the list is not present, then only the HLINE is produced.

However, this is not a very realistic situation, being merely a reduced form of the general case, highlighting what happens to vertical lines that continue across box section boundaries.

The general case, which shows the effect of the default operator, uses boxes of the form:

.bx c1 c2 ... cn
.bx d1 d2 ... dm
.bx off c1 c2 ... cn

This is the pattern seen in the general case:

  1. Each BX line between the first BX line and the BX OFF line that lists columne divides the box into two box sections, the upper box and the lower box.
  2. The first BX line produces a single ABSOLUTEADDRESS block followed by an HLINE block.
  3. VLINE blocks are drawn in conjuction with both the second BX line and the BX OFF line, and their height is the height to the prior BX line, computed as noted here. This may be called normal VLINEs.
  4. VLINE blocks are also drawn in conjunction with both the seoond BX line and the BX OFF line for each column which terminates with the HLINE. These may be called stub VLINEs, as their height is the element-depth.
  5. The subsequent BX line produces these actions to terminate the upper box:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, followed by one normal VLINE block; this is done for each box column.
    3. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
  6. Generate an additional stub VLINE for each column which terminates with the HLINE:
    1. When the first stub VLINE block is output, a single ABSOLUTEADDRESS block is output first.
    2. When each subsequent stub VLINE block is output, two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, are output first.
  7. The BX OFF line produces these actions:
    1. Output the box content appearing between the current BX line and the prior BX line.
    2. Produce a single ABSOLUTEADDRESS block followed by an HLINE block.
    3. Generate one VLINE block per box column:
      1. Columns which terminate with the BX OFF line, including columns that would normally continue on, use normal VLINEs.
      2. Columns which start with the BX OFF line produce stub VLINEs which are drawn upwards from the BX OFF HLINE.
      3. These VLINES are intermingled and output in column order as a set:
        1. When the first VLINE block is output, a single ABSOLUTEADDRESS block is output first.
        2. When each subsequent VLINE block is output, two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, are output first.

Except for the case of BX OFF, the VLINEs for any new columns will be drawn when the next BX line is encountered, at which point they will be the normal VLINEs (and stub VLINEs if appropriate) for the upper box. The BX OFF line is an exception both because it draws its new columns' VLINEs in the wrong direction, and because there will be no subsequent BX line.

For a normal VLINE, the height of the VLINE is incremented by 10, as discussed here. For a stub VLINE, this does not happen, so the height seen in the output is:

stub-height = element-depth - thickness

If the box is broken across a page, the effects noted here will be seen. What happens in terms of ABSOLUTEADDRESS blocks, VLINEs, and HLINEs will be discussed next.

First, though, the positional marker must be described. This occurs when a VLINE has a height of zero (0 hbu). It consists of a single ABSOLUTEADDRESS block using the position of the VLINE's column. This is the case even when they are intermingled with the stub VLINEs, which follow the normal pattern of having one ABSOLUTEADDRESS block before the first and two before each of the others: the intermingled positional markers still become a single ABSOLUTEADDRESS block.

The patterns seen when the box is broken across a page (split vertically) are:

  1. When the first HLINE appears at the bottom of the first page, VLINEs are drawn normally from the bottom of the page, all of them preceded by two ABSOLUTEADDRESS blocks setting the same position. When there is no room below the HLINE, then these VLINEs, being of height 0, become positional markers.
  2. When the box is split so that neither the last line on the first page nor the first line on the next page is an HLINE, the VLINEs are drawn as described here. Those at the bottom of the first page are output last, and they are each preceded by two ABSOLUTEADDRESS blocks setting the same position. Those at the top of the next page follow the above patterns for the unbroken box, except, of course, that they end at the top of the page.
  3. When the second HLINE appears at the bottom of the first page, with no room left below it, then, after the normal VLINEs, the HLINE, and the stub VLINEs, the positional markers representing zero-height normal VLINEs are produced. If there is any room left below it, however small, these will be normal VLINEs, all of which are preceded by two ABSOLUTEADDRESS blocks setting the same position.
  4. When the second HLINE appears at the top of the next page, the positional markers (that is, the normal VLINEs with height 0) appear first, followed by the HLINE, followed by the stub VLINES.
  5. When the final HLINE appears at the top of the next page, the normal VLINEs, being of height 0, become positional markers, and are still intermingled with the stub VLINEs.

In general, then, the only changes when a box is split vertically are:

  • How the VLINE heights are computed.
  • How VLINEs with height 0 appear in the output.

When the box is split horizontally, the only change is:

Each horizontal line becomes a separate HLINE doc_element.

Thus, the code must deal with a linked list of doc_elements specifying HLINEs.

In terms of patterns, for the PS device, the effect is:

  1. For BX lines with no operator, all HLINEs are inserted into the page followed by all VLINEs.
  2. For BX OFF lines, each HLINE is followed by the corresponding VLINEs.

These patterns are applied to the result of the patterns given previously.

Additional Details: PS

With all those HLINEs and VLINEs being output in various orders, an interesting sub-problem arose: how to manage the top_skip, subs_skip and depth fields of the doc_element instances involved. The solution is fairly straightforward:

  1. The skips can only be non-zero in the first doc_element in the set.
  2. The depth can only be non-zero in the last doc_element in the set.

This positions the entire set of HLINE and VLINE doc_elements properly initially, and then positions whatever follows properly, while keeping all of the HLINE and VLINE doc_elements at the same vertical position.

This led to the interesting practice of using the ProcFlags member group_elements and t_doc_el_group to accumulate (or add to the end, since it is also used to accumulate box contents) the HLINEs/VLINEs, so that the depth could be added after the set was completed, which turned out to be very much easier than trying to determine beforehand what the final doc_element might be. This could not be done directly on t_page because the depth might impact whether the final doc_element was moved to the next page or not and so had to be set before it was submitted to t_page itself.

As noted above, the length used by the VLINE blocks is increased by 10 hbus. However, this had to be suppressed for the positional marker VLINE blocks, that is, those of length "0", so that they would be output properly.

Known Discrepancies

This section documents areas in which our wgml and wgml 4.0 do not, and, quite possibly, will never, do things the same way.

Use of BOX Characters

Because the Open Watcom documents only use two devices, PS and TASA, our wgml follows this simplified pattern:

  • If the HLINE block is defined, then the HLINE and VLINE blocks are used.
  • If no HLINE block is provided, then the characters defined by the BOX block are used.

This differs from wgml 4.0 in that, if no HLINE block is defined, then the BOX block characters will be used for vertical as well as horizontal lines. The case where an HLINE block is defined but no VLINE block was provided has not been accomodated, and can be expected to cause a GP-fault.

Default Font Not "0"

The formulas given above are used at all times in our wgml.

While testing, a curious discovery was made: despite the documentation, which our wgml follows, wgml 4.0 always uses font "0" as the font for the PREFACE section, not the font specified by attribute "font" in the DEFAULT LAYOUT section. It is not known, at this time, what other sections, if any, are affected by this.

Moving the test box to the BODY section, I confirmed that wgml 4.0 does use the font specified by the "font" attribute of the DEFAULT LAYOUT section for the BODY section. Using this to make different font numbers the default font for the box revealed some interesting problems.

Under these conditions:

  1. It is not the first page of the document.
  2. The HLINE will appear at the top of the page.
  3. The default font has a line height which is less than the line height of font 0.

the vertical position is offset and the skips and depth of the HLINE are computed by wgml 4.0 in this way:

vertical-offset = font0-line-height / 4
if( font0-line-height % 4 ) {
    vertical-offset++
}
top-skip += vertical-offset
subs-skip += vertical-offset
element-depth = font0-line-height - 2 * vertical-offset

Fonts with a line height greater than that of font 0 were also tested, but the behavior shown by wgml 4.0 could not be reproduced and, indeed, did not make much sense. Then again, the behavior shown doesn't make much sense either, and that is why our wgml uses the formulas shown above at all times.

When the left margin is set to "0", and the default font is not "0", two things happen:

  1. wgml 4.0 emits an empty text_chars instance at the very start of the file after the font change from font 0 to the default font, at least if the specified default font is used in that first section.
  2. The minimum horizontal position is 250 for the PS device; this is the normal minimum left margin for that device plus the horizontal-offset computed above.

If the value of the x_start attribute of the PAGESTART block of the DEVICE block is set to "0" in the PS device, then the net-horizontal-position is less than "0" and the minimum becomes "50". Our wgml certainly does not emit an empty text_chars at the start of the file. If may or may not compute the same value for the minimum horizontal position under the conditions given, depending on whether or not that is the normal result.

For some unexplored reason, when there are 800 horizontal base units per inch, wgml 4.0 shifts 10 horizontal base units to the right; at least the HLINE and the items inside the box are shifted. Since the only affected device, PS, has 1000 horizontal base units per inch, this was not investigated further.

The intended tests, with fonts using different metrics, were carried out by using the FONT command-line operator to change the metrics of font "0". This was helpful in confirming, for example, when a discrepancy of "+1" was from rounding (that is, the difference between vertical-offset and element-depth when the line-height is odd) or an actual, but inexplicable, incrementation.

Aberrant BX OFF Horizontal Lines

Under some conditions, wgml 4.0 will merge different horizontally-separated boxes.

Details will follow, but, in the relevant case, at the top of a page, using dots for spaces to produce the correct alignment, our BX OFF was producing

─┴                             ───────      ┴─

while wgml 4.0 was producing

─┴────────────────────────────────────   ───┴─

That is, it was merging the first two horizontally-separated lines but not the third.

Actually, looking above, this may not be quite the problem I originally thought it was, and more research will be needed on BX ON versus BX in certain situations. This behavior might even be correct, in the sense of resulting naturally from the difference between BX and BX ON.

Personal tools