Drawing Boxes

From Open Watcom

Revision as of 17:38, 2 November 2012; view current revision
←Older revision | Newer revision→
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.

The Box Operands

Control word BX has several operands prompting specific actions. Exploring these is a bit of a problem: character strings are needed to see what they do but producing them turned out to be a bit of a problem: the only font that displays the 8-bit line drawing characters is Terminal and testing showed that, when the OUTTRANS block was used to emit the Unicode line drawing characters, the characters were not emitted, just the numeric value.

And copying the result from a text file displayed using font Terminal to this page does not work: instead of

┌────────────────────────┬───────────────────────────────────┐

the result is

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿

which is a bit annoying.

However, the topic is both necessary and interesting.

Empty lines are used between the boxing lines to make it clear what each line contains. In an actual box, there would be text lines which, for a character device, would have the vertical line embedded at the appropriate positions.

This set of commands:

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

produces:

┌────────────────────────┬───────────────────────────────────┐

├────────────────────────┼───────────────────────────────────┤

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

as does this set of commands:

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

This example illustrates the use of BX with no operand to establish a set of columns and print out the top line, BX with the same columns or no columns to print out a middle line, and BX OFF to print out a bottom line.

This set of commands:

.bx del
.bx 26
.bx
.bx off

produces:

│

│

│

which would be what what the TSO means by a "box with only one vertical descender".

This set of commands:

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

produces:

┌────────────────────────┐     ┌────────────────────────┐
 
├────────────────────────┤     ├────────────────────────┤

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

as does this set of commands:

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

which illustrates the effect of / and shows that it need not have whitespace on both sides. Further testing shows that it must be preceded by whitespace, and that it 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 set of commands:

.bx del
.bx 1 26 32 62
.bx 3 30 40 65
.bx off

produces:

┌────────────────────────┬─────┬─────────────────────────────┐

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

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

which illustrates the basics of how different sets of box columns positions are merged.

This set of commands:

.bx del
.bx 1 26 
.bx 32 62
.bx off

produces:

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

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

while this set of commands:

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

produces:

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

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

which shows on BX ON can be used to do. Further testing shows that, for this effect to appear, there must be no overlap at all between the two sets of column positions. If overlasp exists, the effect is the same as that shown above whether ON is used or not.

This set of commands:

.bx del
.bx 10 20 
.bx 1 30
.bx off
.bx off

produces:

         ┌─────────┐

┌────────┴─────────┴─────────┐

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

which shows the effect of BX OFF after the last box has been closed, while this set of commands:

.bx del
.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 del
.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 illustrate that BX OFF only closes the most recently-opened box; so some sort of stack of boxes exists. They also illustrate that BX OFF will generate vertical descenders (or lines) for the "background" box.

Further testing with NEW showed that many strange effects can be produced if it is used inappropriately because it will always emit the descenders of the "background" box as shown.

BX OFF was also tested with columns following. The effect is to add them to the box column set and then generate a bottom line.

BX SET was tested; it does indeed appear to be treated like BX NEW except for not generating the line.

For a line-drawing device, of course, things work a bit differently: instead of ascenders and descenders being generated as part of the horizontal line, vertical lines are generated as appropriate.

Splitting Boxes Drawn By Control Word BX

The PS output will be described first.

Testing with CO ON and control word SK with steadily-increasing values showed that the example table shown above would, at some point, end up with its bottom line drawn at the top of the next page with no ascenders while the rest of the box showed horizontal lines at (apparently) their normal length.

Further increases in the SK value showed each line being transferred, with horizontal lines being treated the same as text lines in this regard; however, when the last line on a page was a top line, then vertical descenders were visible and the vertical lines on the next page appeared to be curtailed.

This box showed extra space between the top line and the first line of text, but not between the last line of text and the bottom line.

Testing with a simpler box, with ten lines of text, which used CO OFF instead of BR to place each line of text on its own output line, produced the same result. The same box with CO ON, and enough text to produce two lines, also produced the same result so far as splitting the box was concerned. The text, of course, continued on past the right vertical line, as that was to the left of the right margin.

Testing with a character device shows that the basic behavior is the same; indeed, the only differences are:

  1. No extra space appears above or below any text line.
  2. The bottom lines include ascenders.

So it appears that boxes drawn with BX are split in the simplest possible way, keeping in mind that, for line-drawing devices, top lines occupy visible space and so have descenders, while bottom lines do not and so have no ascenders while, for character devices, both top and bottom lines, being composed of characters, occupy the same height as a text line in the given font and have ascenders and/or descenders as needed.

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

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 three steps:

  1. Prepare the horizontal line for insertion, but do not insert it yet.
  2. Insert all elements that are intended to appear in the box, with the vertical ascenders inserted into each element.
  3. Insert the horizontal line.

When the topmost line of a box is processed, there will be no elements to be inserted in step 2.

Using HLINE and VLINE

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

  1. Prepare the horizontal line for insertion, but do not insert it yet.
  2. Insert all elements that are intended to appear in the box.
  3. Insert the horizontal line.
  4. Prepare and insert the vertical lines.

When the topmost line of a box is processed, there will be no elements to be inserted in step 2. Step 4 will still occur, since the horizontal lines will be separated vertically.

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.

The vertical position is offset by an amount that varies depending on whether or not the box is the first item on the page.

  • At the top of the page, the vertical offset is one-fourth (1/4) of the line height of font "0", rounded up.
  • At any other position, the vertical offset is one-half (1/2) of the line height of the current default font, rounded up.

The line height used to position the first line in the box is the line height of the current default font in either case. In our wgml, when the PS device is being used, the vertical offset is subtracted from the line height to produce the depth of the HLINE, and added to the top_skip and subs_skip values. Testing with a really tiny current default font (2 points rather than 12 points for font 0) caused our wgml to produce the same results as wgml 4.0.

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.

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

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. Since the only affected device, PS, has 1000 horizontal base units per inch, this was not investigated further.

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. Indeed, given how our wgml does its output, the depth assigned to the HLINE element is simply moved to the last VLINE element, so that no change to the vertical position occurs while the vertical positions of the VLINEs are being computed.

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.

Additional Details

The actual output is, of course, controlled by the ABSOLUTEADDRESS, HLINE, and VLINE blocks. These blocks are used together in these patterns:

  1. When an HLINE block is output, a single ABSOLUTEADDRESS block is output first.
  2. When one or more VLINE blocks are to be inserted, they are inserted after the HLINE block is inserted.
  3. When the first VLINE block is output, a single ABSOLUTEADDRESS block is output first.
  4. When a subsequent VLINE block is output, two ABSOLUTEADDRESS blocks, both using the same horizontal and vertical positions, are output first.

The term "output" is used because the ABSOLUTEADDRESS blocks are done during the output to the file. Pattern 2 uses "inserted" because it is the order in which the corresponding document elements are inserted into the growing page that determines the order in which the output occurs.

At least, that is the case so far, with simple boxes. Work continues!

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. The other device (WHELP) provides none of them, and so boxes requested by tags IXHEAD and FIG appear and are drawn by wgml 4.0 using the characters and font prescribed by the BOX block.

Control Word BX

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

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.

Personal tools