Notes on porting HTML TADS
Recent Changes Affecting Porting
Version 2.5.10
- The "link" item class, CHtmlDispLink, has a new method,
is_clickable_link(). This method indicates whether or not the link is
clickable - that is, whether clicking on the link should be treated as
a hyperlink activation or as an ordinary click on the underlying item.
When is_clickable_link() returns TRUE, the platform-specific code
should act as it always has. However, when is_clickable_link()
returns FALSE, the UI code should treat a click on the link as though
the link weren't there at all - so the click should be treated the
same as a click on the underlying display item.
- The TADS2 "osifc" interface includes new optional UI
extensions. These extensions are optional because they're inherently
graphical, and could not readily be implemented on text-only
platforms. HTML TADS ports are generally graphical, though, so most
HTML TADS versions should be able to implement these. The extensions
are defined in a separate file, tads2/osifcext.h, to emphasize that
they're not part of the base osifc set. If you want to implement the
extensions, you should do so directly in your platform-specific code;
there's no provision for these in oshtml.cpp since there's simply no
generic support that the portable HTML TADS code could usefully
provide for these. In addition, if you do implement these, you should
enable TADS 3 VM access to the extended functionality by modifying
your TADS 3 makefile to include tads3/vmbifregx.cpp instead of
tads3/vmbifreg.cpp. The 'x' version includes the "extended" I/O
function set that provides program access to the new interfaces.
Version 2.5.8
- The os_banner API introduces the new concept of a "parent" window.
Each banner window can now optionally have a parent banner specified.
Refer to the TADS 2 porting notes (portnote.txt in the TADS 2
distribution), and the os_banner API documentation (os_banner.htm) for
details. This change requires a change to the interface ot the frame
window method CHtmlSysFrame::create_banner_subwin(), and also will
require changes to the banner window layout algorithm on all
implementing platforms. Note that the addition of the parent window
is mandatory for systems that implement the os_banner API; there is no
provision for systems to support the banner API without also
supporting parent window specifications.
- The os_banner API has a new style flag, OS_BANNER_STYLE_MOREMODE.
When this flag is set, the banner window must display a suitable
"More" prompt, and pause for user input, when text is about to scroll
out of view. In other words, this style flag should make the banner
show a "More" prompt just like the main game window does. This style
implies the auto-vscroll style, because that's the only way text would
ever scroll out of view. The system code manages the "More" prompting
in the HTML interpreters, so this style flag must be handled by the
system-specific CHtmlSysWin implementation. Note that this flag will
never be used except in banner API windows (in other words, it'll
never be used in windows created with the <BANNER> tag).
- The class CHtmlSysFont has a couple of new methods that provide
information on the system-level font. is_fixed_pitch()
returns TRUE if the font is monospaced, FALSE if it's proportionally
spaced. get_em_size() returns the "em" size of the font. On
many systems, the em size is a design property of each font, so it
can't be inferred in a portable fashion; for systems where this
information isn't stored in each font, the implementation should
simply return the height of the font in pixels (i.e., translate the
nominal point size of the font to pixels and return the result).
- The method CHtmlSysImage::draw_image() has a new third parameter,
specifying how to draw the image if the native size of the image
doesn't exactly match the size of the rectangle. In the past, the
handling was poorly specified in the interface comments, but it was
intended that the routine would linearly scale the image up or down
as needed to fill the target drawing rectangle. The new parameter
allows the caller to specify one of three treatments for size
mismatches: clip the image, stretch (scale) it, or tile it. The
comments describe the three modes in more detail. The old version
should have done exactly what the "stretch" mode now does.
- The class CHtmlSysWin has a new method, draw_text_space(), that
draws "typographical spaces," which is to say spaces of arbitrary
width. This is used for special proportional spacing effects where
it's necessary to draw what look like spaces, but not necessarily
in integral multiples of the width of the ordinary space character.
- In CHtmlSysWin::get_font(), when creating the new CHtmlSysFont
object, if the font descriptor specified has the 'default_charset'
member set to TRUE, then the new system font object must store the
ACTUAL character set identifier in the new font object's
'desc_.charset' element. There was no such requirement in the past.
This change is required so that the generic code can obtain the actual
character set information from a font object, so that the generic code
can pass character set ID's back to the system code.
- There's a new function, os_next_char(), that the system code must
implement. Each platform should provide a header for this function
(or, alternatively, define it as a macro) in the platform's
xxx/hos_xxx.h file. This function allows the system to provide
support for multi-byte character sets, if multi-byte character sets
are used locally. For systems that use only single-byte character
sets, this is trivial to implement (it's just a pointer increment).
Most systems that support multi-byte character sets at all provide
OS-level functions to traverse strings with proper MBCS handling,
so this should be easy to implement wherever it's needed.
- The new CHtmlSysFrame method set_nonstop_mode() lets the system
frame receive notification that the caller wants to run in non-stop
mode (or not - the default is NOT non-stop mode). In non-stop mode,
MORE prompting (or any local equivalent) is to be suppressed. This is
used when the UI is running under automated scripting control, which
means we don't want to require any user interaction. System
implementations are free to ignore this, but systems that have
something like a MORE mode (i.e., pausing for user input between each
screen-full of text displayed) should implement this method.
Version 2.5.7
- Several new SYSINFO_xxx codes have been added; the
os_get_sysinfo() routine should be updated for the new codes. In
particular, note that SYSINFO_BANNERS should return result code 1,
SYSINFO_TEXT_COLORS should return SYSINFO_TXC_RGB, SYSINFO_TEXT_HILITE
should return 1, and SYSINFO_INTERP_CLASS should return
SYSINFO_ICLASS_HTML. See osifc.h (in the tads 2 sources) for the
definitions of these constants.
- The new parameterized color HTML_COLOR_INPUT has been added, to
represent the color of command-line text.
CHtmlSysWin::map_system_color() should be updated with the new color
code.
- The new parameterized color HTML_COLOR_HLINK has been added, to
represent the color of hyperlink text when the mouse cursor is hovering
over the hyperlink. CHtmlSysWin::map_system_color() should be updated
with the new color code.
- The new os_banner API, introduced into "osifc" (the basic TADS
portability layer) in TADS 2.5.7, is mostly handled by the
portable HTML TADS code, so ports will get the new banner API without
much extra platform-specific work. We've tried to keep track of
specific changes that were necessary for the Windows version, and
outline them below; we expect that similar changes will be needed
on other systems.
- A few interface changes were necessary in classes defined in
htmlsys.h. The significant ones are described below; any not listed
should be fairly self-explanatory and should be obvious when
compiling.
- The oshtml_set_frame() routine has been renamed to
CHtmlSysFrame::set_frame_obj(). It works the same way; this is
just a superficial name change to make the naming more consistent
with the model.
- The CHtmlSysWin routines create_banner_subwin(),
remove_banner_subwin(), and create_aboutbox_subwin() have been moved
to CHtmlSysFrame. They really belonged there to start with, but the
stream-based design of the original <BANNER>-tag system
misleadingly suggested that they were part of the main text window.
In addition, these routines have been superfically renamed to use
'window' instead of 'subwin', for consistency with the new view of
banners as peers of the main text window, and not mere subwindows of
the main text window.
- The CHtmlSysWin routine set_banner_size() has changed slightly.
The "size is percentage" flags have been replaced with more general
"size units" parameters, which expand the range of possible size
settings by adding a new unit, "characters." The "pixels" unit is the
same as the old percentage==FALSE, and the "percentage" unit is the
same as the old percentage==TRUE. The "characters" unit is new: it
specifies the size in terms of the character width or height, which is
defined as the size of a "0" character in the window's default font.
- There's an additional, more subtle change in set_banner_size():
the meaning of the "percentage" size has changed. In the past, the
size was given as a percentage of the application window size;
now, the size is given as a percentage of the remaining size at
the time the banner is being laid out. So, suppose we have a game
with two banners, the first (in layout order) top-aligned and the
second bottom-aligned, and each with a percentage size of 33. In
the past, the two banners and the main text window would all have
the same size, one third of the total application window area. Now,
the top window, being first in layout order, still gets 33% of the
overall window, since the remaining size is the entire window at the
time it's laid out. We next lay out the bottom banner; this one gets
33% of the remaining size, which is two-thirds of the application window
size; hence, we get 33%*66% = 22% of the total application window.
The main text window gets what's left, which is 46% of the total
application window size.
- CHtmlSysFrame::flush_txtbuf() should now be sure to flush buffers
explicitly for any banner API windows, separately from the main game
window. Fortunately, the portable code makes this pretty easy: simply
run through the list of banner windows, and for each window's
CHtmlSysWin object 'win', call flush_txtbuf(fmt) on the window's
CHtmlFormatter object, where 'fmt' is the parameter of the same name
to CHtmlSysFrame::flush_txtbuf().
- The interface for CHtmlSysWin::create_banner_subwin() has changed
to use the OS_BANNER_STYLE_xxx flags to specify the style of the new
banner window. Note that this adds some new styles that weren't
possible to specify before. The os_banner interface explicitly specifies
that all style flags are optional, so the OS code is not required to
implement any of the new style flags. However, since the HTML
interpreters are generally the most full-featured interpreters
around, and since the GUI platforms where the HTML interpreters run
can easily support all of the new styles, it's highly desirable for
the OS code to implement the new styles. The new features in particular
are that scrollbars can be displayed in banner windows, and banners
can be set explicitly to "auto scroll" mode, so that whenever new
text is printed to a banner, the banner scrolls to show the new text.
- CHtmlFormatter::get_text_array() is now protected, so code in
other classes can no longer access this method. This change was made
because formatters can no longer be assumed to provide text arrays.
Any OS code that depended on being able to access the formatter's text
array should instead use the similar methods exposed directly by the
formatter itself. This refactoring of interfaces allows the
formatter's implementation to be better hidden, allowing more
flexibility in the formatter's internal design.
- On the Windows version, I had to make some adjustments to the way
the banner windows drew their borders when the banner windows became
scrollable. In particular, I had to exclude the border itself from
the scrollable region of the window. (This is a detail specific to
the Windows OS implementation, but I mention it because other
platforms could run into the same sort of thing.)
- Note that "tab alignment" is inherently always available in any
full HTML interpreter, since the portable HTML parser/renderer
handles the <TAB> tag. So, for the style flag
OS_BANNER_STYLE_TAB_ALIGN, you can ignore this flag on window
creation, and simply set it unconditionally in
CHtmlSysWin::get_banner_info().
- The behavior method CHtmlSysWin::start_new_page() has changed
slightly. In the past, this method simply cleared the entire frame,
which included deleting all banner windows. This method should no
longer delete banner windows unconditionally; instead, it should only
delete banners created with <BANNER> tags in the main window.
Most implementations will probably use the main window's formatter
object's remove_all_banners() method to delete the banners, in
which case they will not need any changes: the remove_all_banners()
method will do the right thing automatically.
- The Windows version maintains a "history" of screens that have
been cleared. Each time CHtmlSysFrame::start_new_page() is called,
the Windows implementation saves the outgoing page by "exporting" the
parser object's state to a CHtmlParserState object (this is all done
by invoking portable code). Users can later recall these saved
history pages using a menu or toolbar command to step back through
the list of old pages. The old pages are read-only, obviously. The
new banner model required some changes in the way the Windows version
handles this; these changes are entirely specific to the Windows
implementation, but we mention them in case other platforms have
similar mechanisms that will be affected in similar ways:
- When viewing history, the Windows version now uses a separate
window for the history panel, rather than showing history in the
main window. When switching from the main page to viewing history,
the system makes the main page window invisible (by hiding the
window at the OS level) and makes the history window visible;
the history window is normally kept invisible. The system then
places the history window in exactly the same screen area that the
main window was previously occupying. Switching back from a history
page to the main window reverses this visible/invisible swap. This
change allows the main window to be left intact, so that the system
doesn't have to worry about saving and restoring its state with
respect to command input editing and the like.
- The history window uses a new subclass of the portable formatter
object; the new subclass is CHtmlFormatterHist. The main difference
between this new special formatter and the normal main window
formatter is that CHtmlFormatterHist simply ignores banner windows
embedded in the history. This allows paging through the history
without disturbing the layout of banner windows; it's crucial to
leave banner windows unaffected during history recall because of the
new programmatic access to banners.
- In the Windows interpreter, any routine that adds output to the
main window is now careful to exit "history" mode and return to showing
the main page. In particular, CHtmlSysFrame::display_output() always
jumps to the active page if a history page is being viewed. This is
desirable in case a timed event causes output while the user is viewing a
history page; this change ensures that the effect of the timed event is
immediately apparent to the user.
Version 2.5.6
- Several new parameterized system colors have been added:
HTML_COLOR_LINK and HTML_COLOR_ALINK,
HTML_COLOR_TEXT, HTML_COLOR_BGCOLOR. The
system-specific CHtmlSysWin::map_system_color()
implementation should be updated to recognize these new color codes
and translate them to the appropriate colors; in most cases, these
will be translated according to user preference settings.
- The new member variable face_set_explicitly of the class
CHtmlFontDesc (defined in the portable header htmlsys.h)
allows you to distinguish cases where a typeface name has been explicitly
selected from cases where the typeface is inherited from surrounding
text. The latter case arises in situations such as when a <b>
tag appears: the font descriptor is filled in with all of the
information for the current text (i.e., the text
surrounding the boldface text), then the boldface tag changes the
weight member to select bold text. The face member
is filled in with the name of the typeface from the surrounding text,
but a new typeface isn't being selected - it's merely filled in because
we want all of the same attributes of the surrounding text except for
the weight. This new member has been added so that any system-specific
code that translates parameterized font names (such as "TADS-Input")
will know whether additional attributes associated with the parameterized
font name, such as color and boldness, should be applied to the font
descriptor. When face_set_explicitly is true, all of the
attributes of a parameterized font should be selected; if this member
variable is false, then only the face name should be translated, and the
other attributes should be left alone. On Windows, this affects
the "TADS-Input" font, because this font allows the user to select
(via the "preferences" dialog) the color, bold, and italics settings
for the command input font; CHtmlSysWin_win32::get_param_font()
in win32/htmlw32.cpp uses this information when
performing the parameterized font name translation.
- The new member variables bgcolor and
default_bgcolor of CHtmlFontDesc provide information
on the font's background color, if it has one. If
default_bgcolor is TRUE, then bgcolor should be
ignored; otherwise, bgcolor should be used as the fill color
for the bounding rectangle of text drawn with font.
- A new, more sophisticated timer interface has been added to
CHtmlSysWin. The system-specific window subclass must
implement a few new pure virtual functions to provide this mechanism:
CHtmlSysWin::create_timer(),
CHtmlSysWin::set_timer(),
CHtmlSysWin::cancel_timer(), and
CHtmlSysWin::delete_timer(). Related to this new mechanism
is the new class CHtmlSysTimer. The base type (defined in
htmlsys.h) keeps track of the generic information associated with the
new timer mechanism, and the system code is free to subclass this as
needed to add system-specific information.
- CHtmlSysWin::draw_text() should be updated to use
the new background color information in CHtmlSysFont.
- The Ogg Vorbis compressed audio format has been added to
the standard set of media types. The new system-specific
subclass CHtmlSysSoundOgg must be implemented to provide
Ogg Vorbis playback.
- The MNG animated image format has been added to the standard set
of media types. The new system-specific subclass
CHtmlSysImageMng must be implemented to provide MNG support.
- The application frame class (CHtmlSysFrame) has a new
method that the system-specific code must implement,
get_formatter(), which returns the CHtmlFormatter
object associated with the main HTML window. This should be trivial
to implement in most cases, because the application frame must already
create a formatter object and keep track of it internally.
Architectural Overview
First the good news: most of HTML TADS is portable code; you shouldn't
need to make any changes to the portable parts in order to move HTML
TADS to a new operating system. Now the bad news: it's not all
portable code; some of the code is system-specific, and you'll need to
re-implement this non-portable portion to get HTML TADS running on a
new operating system.
Even though you won't need to make any changes to the portable code,
you'll probably find it helpful to know a little about how it works,
since you'll make use of services in the portable code in the course
of implementing a new system-specific implementation. This section
is a brief overview of the design of HTML TADS.
HTML TADS has the following major components:
- HTML parser: this is implemented in the class CHtmlParser.
The HTML parser reads HTML source code and interprets the embedded
HTML command sequences (which are usually called "markups"). The
parser generates a data structure in memory that represents the
HTML text and commands; this data structure is called the format
list. The format list contains the same information as the
original HTML source code, but in a translated format that is easier
for the computer to use.
The format list is constructed from "tag" objects, which are subclasses
of the class CHtmlTag.
- Formatter: this is implemented by the class CHtmlFormatter.
The formatter reads the format list (which was generated by the parser)
and converts it into another in-memory data structure, the display
list. Whereas the format list corresponds directly to the parsed
HTML code, the display list corresponds directly to the information
that will be displayed on the screen. The display list contains almost
entirely items that appear on-screen, and each item in the display list
has information on its size, position, and appearance. To produce the
display list from the format list, the formatter must figure out where
to insert line breaks, where each item will go in the display window,
and all other details of how the information will appear on the screen.
The display list is constructed from "display" objects, which are
subclasses of CHtmlDisp.
- Text array: this is implemented in the class CHtmlTextArray.
The text array is a simple mechanism that stores the text in the
HTML source. The text array is a specialized memory manager that has
some special properties. In particular, it provides a virtual address
scheme that guarantees that addresses are allocated in monotonically
increasing order; the formatter exploits this feature to simplify its
manipulation of the text underlying the format and display lists.
- Resource cache: this is implemented by the class CHtmlResCache.
The resource cache keeps track of resources (external binary data,
such as JPEG images) that have been loaded. Its function is to minimize
resource memory consumption and load time by re-using resources whenever
possible. Whenever HTML TADS is about to load a resource, it first looks
in the resource cache to see if the resource is present, and if so uses
the original copy of it.
- System HTML window: this is implemented by your non-portable,
system-specific code. HTML TADS defines a portable interface to the
system window, which the formatter uses to obtain information about the
layout of the window and to display information in the window.
- Input buffer: this is implemented by the class CHtmlInputBuf.
This class is a helper for your system-specific window implementation;
you don't need to use it, but it may be helpful. The input buffer handles
the internal (non-user-interface) details of implementing a command line
input editor. It provides services, such as selecting a range of text
or inserting a character, that make it easier to implement a command line
editor; if you use this class, your user interface code must handle actual
user interface events and call the corresponding methods in the input
buffer object. Your operating system or application class framework may
have its own object or service that provides this type of functionality,
in which case you probably won't need to use this object.
- Resource implementations: the classes CHtmlJpeg and CHtmlPng
implement portable operations on JPEG and PNG files, respectively.
Additionally, you must implement system-specific objects that take the
portable data representations of these classes and convert them into
objects that your system can display.
- Property list: the class CHtmlPropList provides an
implementation of a simple property list object. You can use this object
to store user preferences, if it's helpful.
- Resource finder: this is implemented in class CHtmlResFinder.
The resource finder is a mechanism that lets HTML TADS find resources
stored in .GAM files through the TADS resource storage mechanism. The
resource finder works with the TADS file reader to construct a map of
resources stored in the .GAM file; when HTML TADS tries to load a resource
based on an URL, it first looks in the resource finder's list to see if
the resource can be loaded from the .GAM file.
Steps in Porting HTML TADS
The remainder of this document describes the steps you should follow
to port HTML TADS to a new system.
First, port the regular TADS to your system
The first thing you need to do is port TADS to your platform. Since
TADS has already been ported to most platforms, this should just be a
matter of finding the correct set of files for your system, setting
up a build environment, and compiling. There's a makefile or its
equivalent for most platforms as well, so you shouldn't need to
figure out the build commands from scratch.
HTML TADS only uses the TADS interpreter, so you only need to build this
component of the normal TADS at this point.
Obtain the Required Third-party Libraries
On Windows, HTML TADS depends on a number of third-party libraries
to implement some of its functionality. In particular, the image
format support is provided mostly by third-party libraries.
These libraries are not required by the portable code. The
Windows implementation uses them, but you don't necessarily have to on
another platform. The Macintosh version, for example, doesn't use any
of them, because there are better Mac-specific equivalents. We
mention these libraries only because they're highly portable, so
if you don't know of a better option for your system, these are
probably good bets.
If you do use these third-party libraries, it will mean that you'll
need to do a little leg-work to integrate them. In the end, though,
it should save you a lot of effort compared to implementing all of
this functionality from scratch: these libraries are all free,
of high quality, and are already highly portable.
The libraries you might find useful are:
- PNG (Portable Network Graphics). This library provides support
for the PNG image format. You can find the PNG home page, which
has links to lots of information about PNG, including C source
code for the PNG library, at
http://www.cdrom.com/pub/png/
. I'm currently using libpng version 0.95 (also known as 1.0
beta 5), but future versions will probably work as well.
- ZLIB, a data-compression library. HTML TADS doesn't use ZLIB
directly, but the PNG libraries described above need it. You can
find the ZLIB home page at
http://www.cdrom.com/pub/infozip/zlib/; this page has links to
the C source code.
- JPEG, a portable image format. There are several implementations
of JPEG available, so if there's one for your platform that you're
already familiar with, you should use that. The reference
implementation, though, is the Independent JPEG Group's library;
you can find the C source code at
ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/jpegsr6b.zip
(for an MSDOS ZIP file), or
ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
for a Gnu-tools-friendly version. (Apart from the bundling format,
both of these should contain the same source files.)
Note that you should use version 6b or higher - past versions did
not handle conversion of the gray-scale storage format to RGB for
display, which the portable CHtmlJpeg code generally requires.
- Libvorbis (the reference Ogg Vorbis decoder library), at
www.vorbis.com.
- Libmng (the reference MNG library), at
libmng.com.
Each of the libraries above has been widely ported; there's a good
chance there's already a makefile for your platform included in
the archive. If not, each includes documentation that describes how
to port the library to a new platform.
The TADS virtual OS layer
The regular character-mode TADS has a "virtual operating system"
layer of code. This is the interface that TADS uses to perform
system-specific operations. The interface itself is defined portably
-- there's a set of functions that TADS can call from portable code,
and these functions provide the same behavior on all platforms. The
implementations of these functions are different on each platform,
though. These functions provide a virtual OS by providing
system-specific functionality through a standardized, portable
interface.
HTML TADS: a replacement virtual OS layer
HTML TADS is designed to look like a virtual OS layer, from TADS's
perspective. TADS doesn't know much about HTML; as far as TADS is
concerned, it's just generating output the same way it would on DOS
or Macintosh or Unix: TADS puts together a buffer with text it wants
to display, then calls the virtual OS display-text function.
So, to port HTML TADS to a new platform, you should start with a
port of TADS to that platform, then remove a number of the virtual
OS function implementations that you would normally use for that
platform. You remove them because HTML TADS provides new
implementations for them.
For a normal port of TADS, most of the virtual OS functions are
in files with names like osdos.c or osmac.c. Some systems have more
extensive OS layers than others, so some systems have all of their OS
implementation in a single .c file, while others use several files.
There's also a file called osgen.c, which provides some OS-specific
functions that have a common implementation across a number of
systems. Your system's TADS makefile should be helpful in
determining which files are used on your platform.
oshtml.c
To determine which of the normal platform OS functions to remove
from your build, look at the HTML TADS file called oshtml.c. This
file contains the implementation of the HTML TADS replacements for
the normal virtual OS functions. Despite its name, oshtml.c is a
portable file -- it is the same on all platforms. You don't need to
port this file.
Go through the function definitions in oshtml.c; for each one,
you need to remove the corresponding implementation in the normal OS
layer for your system.
htmlsys.h
Now comes the real work. Just as TADS has a virtual OS layer, HTML
TADS has its own virtual OS layer. The functionality of this layer
is defined in the HTML TADS file htmlsys.h.
TADS is written in C, but HTML TADS is written in C++. The TADS
virtual OS layer used C functions; the HTML TADS virtual OS layer, on
the other hand, is defined through a set of C++ classes and methods
on those classes. The file htmlsys.h defines the portable interfaces
to these classes. These classes have names that start with CHtmlSys:
CHtmlSysFont, CHtmlSysWin, and so on.
These classes are not typical C++ classes that define methods and
member variables. Instead, these classes are designed as abstract
interfaces -- the methods defined in these classes do not have
corresponding implementations, but are pure virtual methods that must
be overridden and defined in subclasses.
This is where the new system-specific code for your platform
needs to be written. For your system, you must define a concrete
subclass of each of the CHtmlSysXxx classes. Each of these concrete
subclasses must provide implementations for all of the methods in its
CHtmlSysXxx abstract base class.
The reason that the CHtmlSysXxx classes are designed as abstract
interfaces is so that you can use a third-party class framework to
build your version of HTML TADS, if you want. If your system (or
compiler) has an application framework, there's probably a class in
the framework that corresponds roughly to each of the CHtmlSysXxx
interfaces. To use these interfaces with your framework, you can use
multiple inheritance.
A word on multiple inheritance: A lot of people dislike multiple
inheritance, or have heard that it's a bad thing, or feel that C++'s
implementation of multiple inheritance is flawed; but this is a case
where it's actually quite useful and reasonably straightforward to
use. Since the CHtmlSysXxx classes are abstract, and because they
are stand-alone classes without any base classes, they can be "mixed
in" to other classes without much chance of an inheritance conflict,
and without placing any requirements on the design of the rest of
your class hierarchy. In particular, you won't need to use
C++'s "virtual" inheritance feature, which is probably where most
people's misgivings about C++ multiple inheritance come from.
If you're using a framework, you should find the framework class that
corresponds to each of the CHtmlSysXxx interfaces. Then, you should
create a subclass of your framework class that inherits from both the
framework class and the CHtmlSysXxx interface. For example, suppose
that your framework has a class called TWindow that defines the basic
window type. (If there's a framework subclass of TWindow that would
be more appropriate, such as TScrollingWindow, you should use that as
the base class. You don't have to use the bare window class just
because CHtmlSysWin defines the interface to a window -- make your
framework usage decisions just as though the CHtmlSysXxx interfaces
were not involved.) You'd make a TWindow subclass that also inherits
from CHtmlSysXxx:
class CHtmlSysWin_mac: public TWindow, public CHtmlSysWin
{
...
};
Your framework will probably require that you provide implementations
for some methods inherited from TWindow; you should define these as
you would for any other application.
In addition, you must provide implementations for all of the
functions in CHtmlSysWin. The htmlsys.h header file provides
comments that document the functions that these methods are meant to
provide.
Here's a summary of the classes in htmlsys.h that you will need
to implement.
- CHtmlSysFont: system font object. This provides an
portable interface to a font.
- CHtmlSysFrame: system application frame object. This
object provides a logical container for the HTML display; this object
doesn't have to correspond to anything that's actually displayed, but
rather is a programmatic object that the TADS interpreter uses to
perform operations involving the display. When your application
starts up, you will need to create exactly one object that implements
this interface. The main purpose of this object is to route the
TADS interpreter engine's input and output functions to the main HTML
display window.
Pay careful attention to the comments in htmlsys.h
regarding CHtmlSysFrame::set_frame_obj().
This interface is probably the most flexible (and thus ambiguous)
in terms of how you will go about implementing it, because it doesn't
correspond directly to any display element. In my Windows implementation,
this interface is implemented by a system-specific window object that
serves as the top-level frame window. Since this frame window contains
the main game HTML window, it can easily route the CHtmlSysFrame
functions to the main HTML window. Depending on your implementation
design, it may be more appropriate to implement this interface in
something like an "Application" object, or even in a completely
abstract singleton object that you create purely for this interface.
- CHtmlSysWin: system window object. This is the interface
that HTML TADS uses to control the display. You will generally create
one of these objects to serve as the main game window; additionally,
the formatter may create additional CHtmlSysWin objects to display
banner windows.
- CHtmlSysImageJpeg: system JPEG image resource object.
You must implement interface to provide a display object for JPEG
images. Note that there is a corresponding portable class, CHtmlJpeg,
which handles the details of loading a JPEG image from a file; you
won't need to port CHtmlJpeg. You will need to port CHtmlSysImageJpeg,
which is responsible for converting the portable data representation
that CHtmlJpeg uses into a suitable system-specific object that you
can display on your system.
- CHtmlSysImagePng: system PNG image object. This is the
PNG image equivalent of CHtmlSysImageJpeg. The corresponding
portable class, CHtmlPng, handles the details of reading a PNG file;
you must implement CHtmlSysImagePng to convert the portable representation
used in CHtmlPng into a system-specific display object.
- CHtmlSysImageMng: system MNG image object. This is the
MNG image class.
Note that there also a few classes defined in htmlsys.h that you won't
have to implement. Some of these are portable classes that are used
simply to parameterize some of the methods of the other classes;
they're defined here because their main function is to work with
these classes. Others are base classes for more specialized
interfaces, so don't need to be implemented directly.
- CHtmlFontDesc: font description. This is a portable
class that's used by the portable code to describe a font to the
system code; this class is used to parameterize certain methods
in system objects.
- CHtmlFontMetrics: font metrics. This class is used
as a portable representation of certain information about a font,
and is used to parameterize system object methods.
- CHtmlSysResource: system resource object. This object
provides an interface to system-specific display objects that come
from external resources, such as JPEG images. This is a base
class for more specific interfaces, so you won't need to implement
this interface directly.
- CHtmlSysImage: system image resource object. This is
a subclass of CHtmlSysResource that serves as the base class for
the image interfaces. You won't need to implement this interface
directly; it's a base class for more specific interfaces.
System Object Creation
Since your system code will be providing implementations of these
interfaces in subclasses, the portable code has no way of knowing
what the final subclasses are called. Thus, the portable code can't
ever create a system object directly; instead, your system code
creates all of the system objects.
Your system code will create some system objects automatically.
For example, it will create the CHtmlSysFrame object when the
application starts running (the main application entrypoint is itself
always system-specific), and it will create the main HTML window
(which provides a CHtmlSysWin interface) during initialization as
well.
After startup, the portable
code will call methods in existing system objects to create additional
system objects. For example, when the formatter needs to create a
new banner subwindow, it will call the main HTML window's
create_banner_subwin() method; this method, which is
implemented by your system code, will create an appropriate final
subclass of CHtmlSysWin -- specialized for your system -- and return
a pointer to it.
The TADS Win32 Framework
Rather than using an existing class framework to develop the Win32
version of HTML TADS, I developed my own framework. Although I
designed and implemented this framework specifically for this project,
I designed it to operate as a general-purpose Win32 framework.
Note that I could just as well have used one of the commercially
available third-party C++ frameworks for Windows (such as MFC, the
class library Microsoft includes with Visual C++), but I chose to
develop my own framework instead for a number of reasons; one of the
main reasons is that I wanted to avoid inadvertantly introducing any
framework dependencies in the portable design that might have resulted
from developing HTML TADS around an existing framework.
All of the classes with names starting with CTads (such as CTadsWin
and CTadsStatusline) belong to the Windows framework.
If you're using an existing framework to port HTML TADS to your
system, you won't need to be concerned at all with the CTadsXxx
classes. You don't need to port those classes, because you will
simply replace them with the corresponding classes from your
framework. In fact, you don't even need to replace the CTadsXxx
classes exactly; if your framework is laid out differently from the
CTads framework, you should follow the organization that you'd
normally follow when developing an application with your framework
and ignore the CTadsXxx organization. Remember, your job is to
provide implementations of the abstract interfaces defined in
htmlsys.h -- nothing from the CTads framework is needed by the
portable code.
If you're not using an existing class framework, you can use the
CTadsXxx classes as a starting point. These classes, though, are
completely Win32-specific, so they're filled with Win32 API calls.
In addition, these classes are not even designed to be called from
portable code, so the interfaces these classes expose are themselves
closely coupled to the Win32 API; for example, many of the member
variables and method parameters use Win32 system datatypes.
html_os.h
The normal TADS OS layer has some additional definitions in a header
file, os.h. This file has some configuration information needed by
the portable code, such as the native C types to use for certain
abstract TADS types.
HTML TADS has a corresponding header file, html_os.h. This file is
in the portable directory, but provides platform-specific
definitions. You need to edit this file to provide a set of
definitions for your platform. You should add a section, enclosed in
an appropriate #ifdef for your system, that includes a system-specific
file that you create. For Win32, this included file is called hos_w32.h;
you should create a file appropriate for your system.
You should refer to the hos_w32.h to find the set of macros and
other definitions that you need to include in your platform-specific
header file. Fortunately, this file is considerably simpler than
os.h from TADS; one reason is that HTML TADS uses some of the TADS OS
layer directly, reducing the amount of new OS layer that needs to be
built, and another reason is that C++ is somewhat more standardized
than C was in the days when I started TADS.
Note that html_os.h acts only as a "switchboard" for including
the appropriate platform-specific file. Please observe this
convention, since it will keep the code (both in html_os.h and
in your own system-specific files) easier to read by keeping
each platform's code in its own set of files, rather than concatenated
together into a huge impenetrable set of #ifdef's. For the Win32
definitions, look in hos_w32.h and hos_w32.cpp in the win32
subdirectory.
os_get_sysinfo()
Your system code must define this function:
int os_get_sysinfo(int code, void *param, long *result);
Refer to the base TADS header file osifc.h for a full description
of this function. This is one of the few TADS OS-layer functions
that you must define in your system-specific code. The reason this
function is part of your system-specific code rather than part of
the portable HTML TADS definitions (as are most of the other TADS
OS-layer functions) is that this function returns the specific
capabilities of your version of HTML TADS. Since each port of
HTML TADS may vary in capabilities, HTML TADS cannot at the portable
level know which capabilities are implemented for each platform.
For example, some ports of HTML TADS may support MIDI files but
not WAV files. This function allows the system-specific code
to provide the correct information.
os_dbg_sys_msg()
Your system code must define this function:
void os_dbg_sys_msg(const textchar_t *msg);
This routine displays internal debugging messages on the system console.
You can provide an empty implementation for this function if you wish;
its only function is to help you debug HTML TADS by providing a place
for the system to display internal diagnostic information. When you
compile the system without TADSHTML_DEBUG defined, this function is
not used at all.
Main entrypoint
Once you've provided implementations for the CHtmlSysXxx interfaces,
you're nearly done. The only thing left is that your system code has
control over starting up the application.
The main entrypoint is system-specific, because each GUI system has
its own way of invoking an application and passing start-up paramters
to it. There's no CHtmlSysXxx or other portable interface defined for
the main entrypoint; this is a totally system-specific function, so
you must provide a main entrypoint as appropriate for your operating
system.
Your main entrypoint will undoubtedly have to do some
system-specific work to get the application initialized; you should
follow the normal application initialization protocol for your
system. Among other things, you'll want to parse the command line or
read the start-up parameters, or whatever the equivalent is on your
system.
In addition, your main entrypoint is required to do a few
specific things to get HTML TADS started. You can refer to the
implementation of run_game() in the Windows code (in htmlw32.cpp)
for an example of the start-up code. Here's an outline of the steps
you need to perform:
- Call the function CHtmlResType::add_basic_types() (which
takes no arguments and returns no value). This function initializes
the table of built-in media types (JPEG, PNG, MNG, MIDI, WAV, MPEG Audio,
Ogg Vorbis Audio). The media type table is dynamic, so that new media
types can be added at run-time; because of this, the table must be
initialized with the built-in types at some point during startup. (In
the future, it may be possible to add types to the table by looking
for external plug-in objects in dynamic-link libraries; no system does
this yet, but the table is dynamic to allow for this future
enhancement.)
- Create a parser and a formatter object. You should create a
new CHtmlParser object and start it in literal mode, and you should
create a new CHtmlFormatterInput object connected to the parser:
parser = new CHtmlParser(TRUE);
formatter = new CHtmlFormatterInput(parser);
- Create your application frame object. This must be a class that
inherits from CHtmlSysFrame, but the actual subclass is up to you.
- Connect the parser to your frame object. The application frame object
must remember the parser, so that its get_parser() method can
return the correct object. How you do this is up to your implementation.
- Tell the oshtml layer about the frame object. Call
CHtmlSysFrame::set_frame_obj() with your frame object as the
argument. (You may simply want to do this in the frame object's
constructor, since there's always exactly one such object.)
- Create the main HTML window. The CHtmlSysWin() base class requires
the formatter as a parameter, so you must do this after you've created
the formatter. You'll probably need to store a pointer to the main
window object in your frame object.
- Call formatter_->set_win(this, &margin_rectangle)
from the constructor for your system CHtmlSysWin subclass. (You don't
necessarily have to do this during the constructor, but that's
probably the most convenient place for it.) The margin_rectangle is a
CHtmlRect structure that you initialize with the margins in pixels to
use at each edge of the window: so margin_rectangle.left is the number
of pixels to leave blank along the left edge of the window, etc.
- Determine the name of the .GAM file. You should use os_exeseek()
with the executable file name and type "TGAM" as parameters, to determine if
a .GAM file is embedded in the application's executable file; if so,
you don't need to find a separate .GAM file. Otherwise, you will probably
want to look for a start-up parameter (such as a command line argument),
or prompt the user, to determine the .GAM file's name.
- Have the formatter initialize an appctxdef structure.
This structure is used to let the TADS interpreter system notify the host
application (in this case, your HTML TADS implementation) of certain
events, such as loading a resource from the .GAM file. The formatter
is set up to receive such notifications; you must simply
set up an appctxdef structure accordingly. Here's how you
do this:
appctxdef appctx;
formatter->get_res_finder()->init_appctx(&appctx);
- Set up a C-style main(int argc, char **argv) argument vector.
This vector should contain at least one argument, which is the name of
the application's executable file; if no other arguments are specified,
TADS will try to load the game out of the executable file. Additional
arguments are interpreted as though you were running the normal TADS
interpreter from a command line; if you're specifying the name of the .GAM
file to run, it should be the last argument.
- Invoke os0main2() with the following parameters: argc (number
of elements in argv), argv (array of pointers to argument strings),
trdmain (the address of the trdmain function, which
is the TADS interpreter's main entrypoint), "" (an empty string),
"config.trh" (a string giving the name of the HTML TADS
interpreter's configuration options file), and &appctx (the
address of your appctxdef structure). This calls TADS to
load and run the game. (Note that trdmain is the main TADS
entrypoint; os0main2 is an intermediate routine that processes
arguments.)
os0main2(argc, argv, trdmain, "", "config.trh", &appctx);
If os0main2() returns zero, it means that it successfully
ran the game; a non-zero value indicates an error.
- Close and delete your main window. (Depending on how your OS
works, you may not need to do anything here, since the reason we're
quitting may be that the user has already closed the main window.
On Windows, the code checks to see if the main window still exists,
and closes it via the Windows API if so.)
- In the destructor for your class that implements the
CHtmlSysWin interface, you should include this line:
formatter_->unset_win();
This tells the formatter that its associated window is about to
be deleted; the formatter disentangles itself from the window at
this point to ensure that it doesn't make any references to the
window after the window has been deleted.
- Finally, you must delete the parser
and formatter objects you created. The order of operations is
a little sensitive, because the parser and formatter are mutually
entangled with pointers referencing one another. You should use
these steps:
formatter->release_parser();
delete parser;
delete formatter;
Some assumptions about GUI OS architecture
The portable HTML TADS code makes some assumptions about the
architecture of GUI operating systems. These are true of most of the
GUI systems I've encountered, but it's worth spelling these out in
case (a) you're porting to a non-GUI system, or (b) you're porting to
an unusual GUI system that diverges from the typical patterns found
in Windows, Macintosh, X, etc.
Event orientation: One of the main assumptions we make is
that we're running on an event-driven system. That is, the operating
system maintains an "event" or "message" queue that delivers input to
the application. The application's top-level code path is a loop that
reads an event/message from the queue, carries out appropriate
processing to respond to the event, and loops. The OS generates
events to represent user input-device actions (keystrokes, mouse
movement, mouse clicks, etc) and higher-level GUI changes (window
resizing and moving, redraw needed, etc).
Event models vary considerably by operating system, so the HTML
TADS portable code of necessity leaves it up to the port code to read
and handle events. Instead, the portable code provides handlers that
you call in the course of processing events. So, you have to provide
the "glue" that interacts with the OS-level event system to read and
decode events, but in many cases you can invoke a portable routine
to carry out the processing necessary to respond to a given event.
There are two main styles of event model:
- The "message loop" model: the OS generates events and puts them
in an internal queue. The application sits in a loop, reading
input from this queue and carrying out the appropriate processing.
Mac OS follows this model.
- The "dispatch" model: the application initializes and registers
one or more callback routines with the OS, then calls the OS to
tell it to process events. This API call might not return until the
application terminates. Inside this API call, the OS itself sits
in a loop reading events, and dispatches to the registered application
callbacks each time a relevant event arrives. The X Window System
follows this model.
(It's worth pointing out that Windows is a hybrid of the two models.
On Windows, the application is responsible for reading events, as in
the message loop model, but the application must also register callbacks
with the operating system, and the OS can dispatch directly to
the callbacks in the course of carrying out arbitrary API calls.)
Invalidation drawing model: We assume that drawing is driven
by the OS through the event model. In particular:
- We assume that the OS keeps track of an "invalidation" region
for each window - a list of areas of the window that have been marked
as needing to be redrawn. There are typically two sources of
invalidation: (1) the application can explicitly mark parts of the
window as invalid, which it does whenever it has updated the
underlying data model in such a way that the information on the screen
needs to be changed to match; and (2) the OS itself can mark parts of
the window as invalid, which it does whenever portions of the window
are newly exposed, such as when the user resizes or moves the window,
or when another window that was in front of the window has been moved
or closed, etc.
- We assume that the OS will generate "redraw" or "repaint"
events. A redraw event is an event telling the application that it's
time to redraw a portion of the window that has been marked as
invalid. HTML TADS doesn't contain any code that initiates
drawing, because we assume that the OS will initiate drawing via these
events.
On most GUI systems, the redrawing model is tightly integrated with
the native event system, so HTML TADS doesn't have much choice but to
leave this up to the port code. In most cases, the port code in turn
simply leaves it up to the OS. If your OS doesn't have a native
invalidation/redraw event model, you'll have to provide one of your
own. The simplest way to do this would be to explicitly redraw the
entire contents of each window every time an "input" call of some kind
is performed; you'd do the drawing at the start of the call,
before pausing to read input from the user. "Input" calls are times
when the application will pause for user interaction, so it's a
natural time to make sure everything's up to date on the display.
In practical terms, you can usually implement "redraw" event
handling by doing any necessary OS-level setup, then calling
formatter_-<draw() from your CHtmlSysWin subclass.
The formatter walks through its display list and calls back to
your OS code to do the actual drawing.
Window management: HTML TADS assumes that someone
will be taking care of managing the window system - things like
providing a UI for the user to resize and move windows, etc. We
assume that this "someone" will be doing all of the necessary
invalidations as part of this window management. In almost every
case, it's the OS that handles these things, but some older GUIs
(early PalmOS versions, for example) are rather limited and foist this
stuff off on the application, in which case you'll be responsible for
handling this in your porting code. But on any modern GUI, this stuff
should come for free.