Skip to content
Jiri Malak edited this page Feb 20, 2021 · 9 revisions
Table of 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 4 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. The box created by such a BX line is the outermost box.

A BX OFF line terminates the current box and reinstates any prior box if the current box was nested. If the outermost box is terminated, 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 develop a "theory of BX", so to speak: that is, to describe not only the normal or intended behavior of the box operands, but also a theoretical framework which can underlie the code. 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)

A BX line with no operand is said to be using the default operand.

Column/Box Terminology

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).

There are seven column types:

  1. down columns generate the "top" characters (top_left, top_right, or top_join) in character devices; in line-drawing devices they generate vertical lines at the bottom of the page when the box will continue at the top of the next page.
  2. up columns generate the "bottom" characters (bottom_left, bottom_right, or bottom_join) in character devices; in line-drawing devices, they generate vertical lines whenever encountered.
  3. both columns generate the "inside_join" or "vertical_line" characters in character devices; in line-drawing devices, they postpone drawing vertical lines until the column ends.
  4. split columns generate the "inside_join" or "vertical_line" characters in character devices; in line-drawing devices, they act as an "up" column in the current cycle and as a "down" column in the next.
  5. new columns are columns used with operands NEW or SET. They generate the "bottom_join" characters in character devices; in line-drawing devices they generate vertical lines; these columns then become "hid" columns until the current box is ended, at which point they are again "new" columns but now generate the "top" characters (top_left, top_right, or top_join) in character devices;
  6. hid columns are columns which are hidden by the box created by operand NEW or SET. In wgml 4.0, they generate "top_join" characters in character devices; in line-drawing devices, they do nothing. Our wgml currently and deliberately does nothing in character devices as well, using a "horizontal line" character instead, as this makes more sense (hidden lines should be hidden, not marked).
  7. out columns are columns which are outside the box created by operand NEW or SET. In character devices, they generate "vertical_line" characters; in line-drawing devices, they accumulate the height of the vertical line which will be eventually produced when the column becomes an "up" column.

There are three box line types:

  1. The previous line contains the set of columns used by the immediately preceding BX line, plus any "hid" or "out" columns resulting from the use of operator NEW or operator SET. The columns undergo additional processing before the previous line is ready for use.
  2. The current line contains 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.
  3. The box line contains 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 (when appropriate) VLINEs, which are doc_elements of types "hline" and "vline", respectively.

A line segment is a set of one or more columns. Sets with more than one column produce a horizontal line from the first column to the last. No horizontal line is drawn in any gaps between the last column of one segment and the first column of the next. Line segments arise from several causes:

  1. The character '/' has been used in the column list of the current BX line.
  2. A BX line with operand ON can produce one if its columns do not overlap with the existing columns.
  3. All "out" columns are each in their own one-column segment.

A boxing pass is the totality of processing done in response to a single BX line. The life-cycle of the box line types can now be specified:

  1. The previous line is initialized at the end of a boxing pass by copying it from the box line of the current boxing pass. During this copy, all "up" columns are skipped, some segments may be restored, and all empty segments are then removed. At the start of the line merger producing the box line for the next boxing pass, all "down" columns are changed to "up" and other adjustments are made as needed (based on the operand of the current BX line). The previous line is cleared just before it is set at the end of the boxing pass, as it will be needed (in it's original form) if a box is split across two pages. At the start of the boxing pass for the BX line that starts the outermost box, the previous line will be empty. At the end of the boxing pass that ends the outermost box, the previous line will be empty.
  2. The current line is created as a result of parsing the BX command. If no column list is provided, the current line will be empty and the previous line will be renamed the box line. The current line will be empty after the line merger producing the box line is finished.
  3. The box line s generated as a result of the merger of the previous line and the current line. Since the previous line will be empty on the boxing pass for the BX line that starts the outermost box, the current line will be renamed on this box pass to be the box line. The box line is cleared at the end of the boxing pass.

The box stack consists of fresh copies of the previous line. As a result, popping the stack has the effect of returning an earlier previous line to use.

When a BX line with either the default operand or operand ON is encountered, and has a column list, then a distinction can be made between the upper box, which is being ended, and the lower box, which is being started.

The current box is the box currently being drawn, as opposed to any prior boxes. Conceptually, the box stack consists of prior boxes, which are pushed to the stack by operand NEW and operand SET, and popped from the stack by operand CAN/DEL and operand OFF (and operand ON with no column list). If neither operand SET nor operand NEW is used inside of a box, then no prior boxes exist. Other than box stack manipulations, all actions done in processing a BX line are done on the current box.

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 (which are, of course, "down" columns). These would normally be drawn with a "top" character (top_left, top_right, or top_join), but are instead drawn with a "bottom" character (bottom_left, bottom_right, or bottom_join) 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. Of course, since there is no box already in existence, operand NEW and operand SET do not push anything to the box stack.

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 Operand

The default operand provides a basic understanding of how BX works. This discussion treats only outermost boxes; when operand NEW or operand SET have been used, this discussion applies only to the current box. Thus, "out" columns that are position outside the boundaries of the current column list are not included in the horizontal line, although, in character devices, they do cause a riser to be produced.

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. This means that a distinction exists between "both" columns that draw a vertical line and those that do not.

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 operand 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.

Operand ON

Operand ON can best be understood by reviewing the default operand. This discussion is also confined to the outermost box, so those remarks apply here as well.

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 operand 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 operand in place of operand 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 because it is outside of any box; the BX OFF with a column list is processed normally: it opens and closes a box. Note: if operand NEW or operand SET have been used, then operand ON will pop the stack and the following BX OFF will pop it again (or close the outermost box).

When the / separator is used with operand 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.

Operand NEW

These simple examples will illustrate the basic function of operand 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. Note that columns 10 and 20 are "new" columns on the middle two lines. Were a BX line inserted between the BX NEW and the first BX OFF, it would draw a line from column 1 to column 30, with columns 10 and 20 being "hid" columns.

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. Note that columns "1" and "30" are "out" columns, and the new box is an inner box.

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, showing how "hid" columns are marked by character devices, but produces

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

when device PS is used; indeed, the PS device does not mark the "hid" columns by 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 putting each "out" column into its 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; however, the VLINEs, when drawn, must extend to where they originated, so a field was added to the box column to contain the additional height, and that field is only available if the column is present.

A BX NEW line with no column list is a bit of a puzzle: since it cannot intersect any existing columns, it would seem to be unable to create "new" or "hid" columns; and either no existing columns or all existing columns becoming "out" would seem equally reasonable. This has not been explored adequately.

Operand 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.

In both cases, it seems likely that "new", "hid", and "out" columns are designated and treated as for operand NEW.

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 operand 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 operand SET, work was halted at this point.

When a more thorough investigation of operands CAN and DEL was required, the code produced was written to include operand SET, since the phenomena observed were puzzling in much the same way as stated above, and so it seemed reasonable that it was not drawing the box line itself which was the cause of the problems, and that is common to all three operands.

Operand OFF

Operand 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 straightforward; 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 operand 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 operands 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.

Operands CAN and DEL

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

Operands CAN and DEL perform many of the same functions as operand 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.

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

Work with the actual boxes in the OW documents required a deeper examination of operands CAN and DEL. These are the results:

  1. If one or more BX CAN lines precede a final BX OFF line, then the box will have a gap in it unless there is text before the first BX CAN line and none on any subsequent BX CAN lines or the final BX OFF line.
  2. These operands will draw risers for columns that used to be hidden but no longer are. If preceded immediately by text, something very odd occurs:
    1. The height of vertical line will be equal to the text height plus the element-depth.
    2. The difference between the horizontal start position of the vertical line and the prior element will be the line height (only), except for the first BX CAN line, for which the difference will include the element-depth.
  3. If not preceded immediately by text, these operands will draw risers but with a "text height" of 0. This can still produce a riser if, for example, an "out" column, having accumulated a height, is now an "up" column and so the riser is finally drawn. The vertical position will be the same as the last BX CAN line which followed a text line.

The discrepancy between the height of the riser and the difference in vertical position is what appears to be causing the confusion.

Box Stack Notes

The box stack preserves not only the previous line being pushed to the stack but also whether or not that line had a column list and whether or not it created an inner box.

The previous line pushed to the stack undergoes one transformation: any "new" column becomes a "down" column, as it will start a new vertical line when popped.

Additional heights for "out" columns are accumulated during box processing and are part of the information pushed to the stack. However, as the new current box is processed, additional height is added to the existing value. When the entry is popped, the current vertical line heights for "out" columns are copied from the top-of-stack entry to the next entry. When the line is finally drawn the additional height will thus reflect all off the inner boxes which affect it.

Merging Box Lines

This section provides a theory of how the previous line and the current line are merged to produce a box line.

Before any merger occurs, if the previous line is not empty, these column type translations are performed on it in all cases:

Original          New
both              both
down              both
hid               hid
new               hid
out               out
up                (removed when prev_line was formed)

and these are performed if operand NEW or operand SET was used with a column list:

Condition         Result
all cases         hid columns are skipped: anything hidden in the prior box will be hidden in this one
column is inside
 any segment      all but "hid" columns become "new": they intersect the box being created
column is outside
 any segment      all but "hid" columns become "out": they are outside the box being created

If any column becomes an "out" column, the fact that the box being created is an inner box is recorded as part of the current top-of-stack entry.

There are three easy cases:

  1. Both the previous line and the current line are empty. This appears to be theoretically possible if the previous line is from operand NEW or operand SET with no column list and the current line also has no column list. What should happen is currently unexplored.
  2. The previous line is empty but the current line is not: the current line is renamed to be the box line. This will always occur with the first line of an outermost box.
  3. The current line is empty but the previous line is not: the previous line is renamed to be the box line. This is actually quite common: it occurs whenever a BX line with the default operand and no column list is used. For character devices, all but "hid" columns produce the appropriate character for their position in the output line. For line-drawing devices, vertical lines are produced for "new" and "up" columns.

Just to clarify the situation, consider this three-line box:

.bx 1 26
.bx 32 62
.bx off

which produces:

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

What does this look like in the merge process?

On the first boxing pass, the current line becomes the box line and looks like this:

┌────────────────────────┐

It then becomes the previous line, and is transformed into this:

└────────────────────────┘

This is the line that must be merged with the current line on the second boxing pass; the current line is

                               ┌────────────────────────────────┐

The result, in this case, is

└────────────────────────┴─────┬────────────────────────────────┐

but, if the operand ON were used, the result would be

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

a line with the two segments. This comes about because the segments are disjoint.

These are some terms that will be helpful in describing how the merger works:

  1. The start of the box line is that column which is furthest to the left of the leftmost columns of the previous line and of the current line.
  2. The joint gaps, if any, are those gaps formed whenever the gap between two segments of the previous line and the gap between two segments of the current line overlap. This gap begins with the column, in either line, which is farthest to the right on the left side of the gap and ends with the column, in either line, which is farthest to the left on the right side of the gap. Note: if the segments on the left side in both lines end with the same column, a joint gap does not exist; however, if the segments on the right side in both lines begin with the same column a joint gap does exist.
  3. The end of the box line is that column which is furthest to the right of the rightmost columns of the previous line and of the current line.
  4. The head of the box line consists of those columns from the start to the leftmost column of the other line. Thus, if the previous line starts further to the left than the current line, the start column is the leftmost column of the previous line and the head extends from that column to the leftmost column of the current line, and vice-versa.
  5. The main body of the box line consists of those columns
  6. The tail of the box line consists of those columns from the rightmost column of the line which ends first to the end. Thus, if the previous line ends further to the right than the current line, the end column is the rightmost column of the current line and the tail extends from the rightmost column of the previous line to that column, and vice-versa.

The Default Operand

When the current line is associated with the default operand, these are the rules:

  1. The first box line segment begins with the start column. This segment continues until the first joint gap, if any. If there are no joint gaps, then it continues to the end column.
  2. Subsequent box line segments extend from the end of the joint gap to the start of the next joint gap, if any. If there are no additional joint gaps, then it continues to the end.

Note that it must be a joint gap: in both the previous line and the current line, it must be bounded on both sides by segments. The head will always be part of the first segment, and the tail will always be part of the last segment (if there is only one segment, both will belong to it).

Example:

previous line: └────────────────────────┘ └──────────┘       └──────────┘   └──────────┘
current line:      ┌────┐ ┌────┐             ┌────┐        ┌──────────────┐
box line:      └───┬────┬─┬────┬────────┘ └──┬────┬──┘     ┌─┴──────────┴─┬─┴──────────┘

Note that the box line continues on after the last segment of the current line, even though there is space between its last column and the first column of the last segment of the previous line. This is not a joint gap because the current line has reached its end.

The default operand is characterized by symmetry: if a simple three-line box, with the first two lines using the default operand with different column lists and the third being BX OFF with no column list, has the first two lines reversed, the resulting boxes are symmetric horizontally.

Operand ON

Neither symmetry nor consistency is achieved with operand ON as implemented by wgml 4.0. Our wgml, however, achieves both by following a simpler set of rules:

  1. Every segment contained completely in the head in whichever line provides the head becomes a segment in the box line.
  2. Joint gaps are processed exactly as they are for the default operand.
  3. Every segment contained completely in the tail in whichever line provides the tail becomes a segment in the box line.

What wgml 4.0 does is more erratic: in some cases, segments in the tail are joined, in others they are not. And then there is this case:

.bx 20 25 / 30 35
.bx on 1 / 10 15 / 30 33 / 40
.bx off

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

Our wgml does this instead:

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

and so can be regarded as having fixed a bug in wgml 4.0.

Operand OFF (with column list)

Operand OFF with a column list behaves rather differently from the default operator and operator ON in these ways:

  1. The test box only requires two lines.
  2. The columns given with operator OFF produce down stubs.

As a result, the box produced is not symmetrical horizontally and, as with ON, exact replication is difficult because some of the results of testing make no sense at all. These rules are used by our wgml:

  1. When the head is provided by the previous line, each segment in the head produces a segment in the box line.
  2. When the head is provided by the current line, things get more complicated:
    1. any segments in the head which have only one column and which precede the first segment with more than one column each produce a segment in the box line
    2. once the first segment in the head with more than one column is found, that segment and all subsequent segments in the head are run together and joined with the first segment of the previous line.
  3. When the tail is provided by the previous line, each segment in the tail produces a segment in the box line.
  4. When the tail is provided by the current line, each segment in the tail produces a segment in the box line.
  5. In the main body, our wgml treats joint gaps in the usual manner.

This isolates the problem to the main body. Part of the problem is inconsistency: situations in which segments overlap sometimes produce a break before one or the other has ended, and sometimes do not. However, in thinking this out, I came to the conclusion that treating joint gaps as they are treated with the default operand (and operand ON) should work best, as that will produce gaps only where there are no vertical lines to be drawn and so cover both the previous line's columns and the current line's columns.

Note that operand CAN (DEL) cannot be used with a column list.

Operand NEW (and Operand SET)

Operand NEW does not, of course, merge in the sense discussed above; rather, it replaces the existing box with a new box. However, the columns of the box being replaced must be accounted for, using the column types "hid", "new", and "out", and so a sort of merge does take place.

In itself, operand NEW works as described; however, if we ask what happens when a BX line with the default operator or operator ON is used inside a box created by operand NEW, the answer is startling:

.bx 1 10 15 20 30
.bx new 10 20
.bx 12 18
.bx off
.bx off

produces:

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

At least approximately: the actual box drawn by PS cannot be drawn using line-drawing characters because that would require a "└" with the cross-bar from "┼" on it's upper stroke; the resulting inner box is much squatter and enough space appears above it to look like a blank line. The box actually drawn in characters is slightly different, and equally puzzling in its own way, but the general form is the same. Using operator ON produces exactly the same result. Inserting text before the BX line with the default operator (or with operator ON) does not improve matters.

So these rules were adopted to produce a more likely result:

  1. The box line is the same as the current line plus these columns in the previous line:
    1. Those which fall outside of any segment in the current line. These are, of course, the "out" columns.
    2. Those which fall inside of any segment in the current line. These are, of course, the "new" columns, which become the "hid" columns after drawing their risers.
  2. BX lines inside a box created by NEW and OUT which have column lists and either use the default operator or have operator ON are treated as usual, except that:
    1. Those "out" columns now included in a segment become "new" columns (and then "hid" columns).
    2. Those "hid" columns now outside of any segment become "out" columns.

The resulting implementation does look much better than the output of wgml 4.0 does.

Note that operand SET is documented to behave the same way as operand NEW, except for not drawing a horizontal line. So far as I can tell, this is correct, and so operand SET merges in the same sense as operand NEW.

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.

Testing with boxtest.gml, a compilation of all the text from the Open Watcom documents using (directly or through a macro) TB or BX revealed an interesting twist: when the last text output before the BX is not in the current font, then the vertical-offset and element-depth are altered, at least, this is the case when the line height of the text is less than the line height of the current font. This holds true at least when the current font is the default font.

The computations start with obtaining the height of the last text output as previous-text-line-height and then continue:

if( previous-text-line-height < current-font-line-height ) {
    vertical-offset += (current-font-line-height - previous-text-line-height) / 2
    element-depth -= (current-font-line-height - previous-text-line-height) / 2
}

whether this holds when the current font is not the default font, and what happens when the text font has a larger line height than the current font, is not, as yet, known.

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.

Clone this wiki locally