11.10 Document rendering primitives

Dynamic HTML has enabled the presentation of user interfaces as documents the user can interact with. None of the traditional GUI components can deal with the mixture of properly formatted text with (interactive) graphics. XPCE provides support using a set of primitive classes that realise a box-model based on TeX . This basic model is enhanced using a Prolog library providing HTML-based primitives. First we will introduce the basics.

Before discussing the library we show a small example using the primitives directly.

parbox :-
        send(new(W, window), open),
        send(W, display, new(P, parbox(W?width)), point(10,10)),
        send(W, resize_message, message(P, width, @arg2?width-20)),
        
        send(P, alignment, justify),
        send_list(P,
                  [ append(grbox(box(40,40), left)),
                    cdata('This is the central class of the '),
                    cdata('document-rendering support.  It is '),
                    cdata('a subclass of '),
                    cdata('device', style(underline := @on)),
                    cdata(', placing '),
                    cdata('hbox', style(underline := @on)),
                    cdata(' objects as words within a paragraph. '),
                    cdata('Parbox devices have a <->width, realise '),
                    cdata('alignment and adjustment and can place text '),
                    cdata('around '),
                    cdata('floating ', style(font := italic)),
                    cdata(' objects.')
                  ]).

Figure 29 : A parbox floating text around a box

In line 4, we forward changes to the width of the window to the parbox to force reformatting the text if the width of the window changes. Line 6 asks for a straight right-margin. Line 8 appends a box that is left aligned, the text floating around it. The remaining lines append text. The method `parbox->cdata' breaks the argument into words and adds instances of tbox for each word, separated by instances of hbox with appropriate rubber for each sequence of white-space, doing much the same as an HTML browser processing CDATA text.

Defining a document from Prolog text using these primitives is not attractive. This is the motivation for using a library.

11.10.1 The rendering library

The directory \bnfmeta{pcehome}/prolog/lib/doc contains the document-rendering library, providing HTML-oriented primitives for document rendering. The translation process can be extended by defining predicates for two multifile predicates.

The central library is library(doc/emit). This library defines the predicate emit/3:

emit(+ListOfTerm, +PBox, +Mode)
This predicate takes a list of processing instructions and applies these on PBox, an instance of class pbox, a subclass of parbox supporting this library. Mode provides an instance of doc_mode, a data-object holding style-information.

ListOfTerm consists of the following instructions:

Before handed to the above, emit/3 calls the multifile predicate doc:emit/3 passing the whole list as you can see from the definition below. This allows the user-hook to process sequences of instructions.

emit(Term, PB, Mode) :-
        doc:emit(Term, PB, Mode), !.

11.10.2 Predefined objects

The file library(doc/objects) defines a number of globally named instances of the box classes. Appending these objects is like executing an action. These objects are:

@br Nul-dimension hbox with rubber to force a line-break.
@nbsp Non-breaking space.
@hfill Rubber hbox for alignment and centering.
@space_rubber Rubber used for hbox objects representing a space. This rubber allows for a line-break.
@h<n>_aboveSpace above HTML section-headers.
@h<n>_belowSpace below HTML section-headers.

11.10.3 Class and method reference

This section provides a partial reference to the classes and methods defining the document-rendering library. For full information, please use the ClassBrowser and check the source-code.

pbox ->show: Content:prolog, Mode:[doc_mode]
Calls emit/3 using Content and Mode. If mode is omitted, a default mode object is created.
pbox ->event: Event:event
Handles clicking a button or footnote and showing balloons from buttons after trying to pass the event to one of the embedded graphicals.
pbox <-anchor: Name
tuple(Box, Index) Return the Box and index thereof in the `parbox<-content' vector that starts the named anchor (see section 11.10.3.2.
doc_mode ->initialise:
Creates a default document-rendering mode. This mode has the following properties:

vfontnew(vfont)The virtual font for text
link_colourdark_greenText-colour while rendering buttons
parsephbox(0,8)Skip 8 pixels between paragraphs
parindenthbox(0,0)Do not indent paragraphs
space_modecanonicalCollapse spaces
alignmentjustifyMake a right-margin
base_url''URL for HTML hyper-links
doc_mode ->set_font: Att:name, Value:any
Set an attribute of <-vfont and update <-style and <-space to reflect the new font-settings.
doc_mode ->colour: Colour
Set the colour of <-style.
doc_mode ->underline: Bool
Set underline mode in <-style.

11.10.3.1 Class vfont

The class vfont realises virtual fonts, Microsoft also calls these logical fonts. XPCE font objects are read-only. When dealing with incremental font manipulation it is necessary to have a font that can be manipulated. Therefore, vfont defines a number of slots to represent the font attributes regardless of the fonts existence. At any time the user can request the best matching font using `vfont<-font'. The mapping between virtual font attributes and physical fonts can be programmed by adding clauses to the multifile predicate vfont:font_map/7. This class is defined in library(doc/vfont) where you find further programming details.

11.10.3.2 Rendering actions

The action subsystem processes actions (\Action) from emit/3, providing a hook for adding new actions. Before doing anything else, the hook doc:action/3 is called:

doc:action(+Action, +PBox, +Mode)
Execute Action. The actions are passed from emit/3 after stripping the backslash. If this hook succeeds the action is considered handled.

The built-in actions are:

ignorespaces
Tells emit/3 eat all input until the first action or non-blank character.
space(SpaceMode)
Tells emit/3 to preserve white-space or render it canonical. Default is canonical.
pre(Text)
Add verbatim text.
par
Start a new paragraph. This is the action-sequence parskip, followed by parsep.
parskip
Inserts <-parsep from the current mode, surrounded by two line-breaks (@br).
parindent
Insert the <-parindent from the current mode.
group(Group)
Use emit/3 on Group on a <-clone of the doc_mode object. This is essentially the same as a TeX group, scoping changes to the mode such as font-changes, etc.
setfont(Attribute, Value)
Set an attribute of the font. Fonts are maintained using the Prolog defined class vfont (virtual font) that allows for independent changes to font attributes. When text needs to be rendered, a close real font is mounted. Defined attributes are: family, encoding, slant, weight, fixed and size. See \bnfmeta{pcehome}/prolog/lib/doc/vfont.pl for details.
ul
Switch on underlining. Normally this should be inside a group to limit the scope.
colour(Colour)
Set the text-colour.
list(Options, Content)
Produce a list-environment. The option class(Class) defines the subclass of lbox used to instantiate, default bullet_list. The Content is passed to emit/3, using the created list-object as 2nd argument.

When using a bullet_list or enum_list Content must translated into a sequence of li commands. Using a definition_list, a sequence of dt and dd commands is expected.

li(Content)
If we are processing a bullet_list or enum_list, start a new item using `<-make_item', then emit Content to the new item.
dt(ItemTitle)
If we are processing a definition_list, create a new label from ItemTitle.
dd(ItemContent)
Create a pbox for the item body and emit ItemContent to it.
title(Title)
Get the <-frame of the pbox and send it the given title using `frame->label'.
body(Options)
Apply options to the window as a whole. Defines options are bgcolour(Colour), background(Image) and text(Colour).
button(Message, Content, Balloon)
Add an active area (hyper-link) to the document. When depressed, Message is executed. When hoovering, Balloon is reported as status (see section 10.7). Content is emitted inside a group after setting the default colour to `doc_mode<-link_colour' and underlining to @on.
anchor(Label, Content)
Label some content. This has no visual implications, but the the anchor can be located using `pbox<-anchor'.
parbox(Content, Options)
Add a sub-parbox. Defined options are:
width(Width)
Define the width of the sub-box.
rubber(Rubber)
Define the shrink- and stretchability of the sub-box.
align(Alignment)
Define text-adjustment (left,center,right,justify) within the box.
valign(VAlign)
Define the vertical alignment of the box (top, center, bottom.
auto_crop(Bool)
If @on, tell the pbox its <-area is determined by the content rather than the specified width. Text may be formatted left-to-write without wrapping by defining a wide parbox and using this option.
table(Options, Content)
Create a tabular layout using the class doc_table, a device holding a table. See also section 11.5. The options and commands are modelled after HTML-3. Table-related commands are tr, td, col, thead and tbody. Defined options are:
align(Align)
Graphical alignment, allowing placement as left or right floating object or centered placement.
width(Width)
Give the table a specified width.
bgcolor(Colour)
Set the default background colour for the rows, columns and cells.
cellpadding(IntOrSize)
Specify the space inside a cell around its content.
cellspacing(IntOrSize)
Specify the space between cells.
tr
Open the next table-row.
tr(Options, Content)
Open a new row and emit Content therein. Options are applied to the row. See class table_row for details.
td(Options, Content)
Add a table-cell and emit Content into it. Options are applied to the new cell. See class table_cell for details.
col(Options)
Handle an HTML-3 col element, specifying parameters for the next column. Defined Options are span(Span) to apply these settings to the next Span columns and width(Spec), where Spec is an integer (pixels) or a term *(N), defining the weight for shrinking and stretching relative to other columns using this notation. The following defines the second column to be twice as wide as the first:
        [ \col(*(1)),
          \col(*(2))
        ]
tbody(Options)
Start a row-group. See `table_row->end_group'. Options is currently ignored.
thead(Options, Content)
Handle a table-head. It expects a number of rows in Content. While processing Content it sets the default cell alignment to center and font to bold.
footnote(Content)
Add a footnote-mark. Pressing the mark shows a popup-window holding the text of the footnote.
preformatted(Text)
Adds text in a tbox to the parbox without checking the content. The current style is applied to Text

11.10.4 Using the ``doc/emit'' library

In section 11.10.1 and section 11.10.3.2 we have seen the definition of the basic rendering library infrastructure. It uses concepts from TeX and HTML-3, introducing primitives for grouping, attribute settings, lists, tables and whitespace-handling.

The emit/3 predicate as described above is not intended for direct use though. It is hard to imagine a good syntax for defining significant amounts of formatted text in a Prolog text-file. In some cases it is feasible to define a suitable set of new commands and use emit/3 directly from Prolog. In most cases you'll want to use tokens from an external source using an external markup language.

One option to arrive at a token-list is using the XML/SGML parser included in SWI-Prolog. It can be used either with a domain-specific DTD, in which case you need to define the translations by hand or using an HTML DTD, in which case the library library(doc/html) defines the required translations.

We will illustrate this in a small example. First the code to show HTML inside a window is below. In line 1 we load the always-needed document rendering infra-structure and register the doc search-path to reflect the \bnfmeta{pcehome}/prolog/lib/doc directory. Next we import emit/3 and load the HTML-rendering extensions to doc:emit/3 and doc:action/3.

:- use_module(library('doc/load')).
:- use_module(doc(emit)).
:- use_module(doc(html)).
:- use_module(library(sgml)).

show_html(File) :-
        send(new(P, picture), open),
        send(P, display, new(PB, pbox), point(10,10)),
        send(P, resize_message, message(PB, width, @arg2?width - 20)),
        
        load_html_file(File, Tokens),
        send(PB, show, Tokens).

Here is the HTML code loaded and the visual result.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">

<html>  
  <head>  
    <title>Document rendering</title>
  </head>  
<body>
<h1>SWI-Prolog SGML-DOM</h1>

<p>
SWI-Prolog 4.0 provides a library for loading XML, SGML and
HTML files and convert them to a complex Prolog term.  This
term has the format

<p>
<pre>
element(Name,
        ListOfNameValue
        ListOfContent)
</pre>

<p>
Where <var/ListOfNameValue/ reflects the attribute-list of the
element and <var/ListOfContent/ is mixed list of atoms representing
<em/CDATA/ and <b>element/3</b> terms representing nested elements.
</body>
</html>

Figure 30 : Rendering HTML code

In general you do not want to render plain HTML using XPCE/Prolog as it is far less flexible than real browsers dealing with errornous HTML, the implementation of HTML is incomplete and it supports Java nor Javascript.

It has proved to be worthwile using the extensibility of SGML and this layout to render domain-specific documents, often using HTML elements for the basic layout.