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.html) 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
andHTML_COLOR_ALINK
,HTML_COLOR_TEXT
,HTML_COLOR_BGCOLOR
. The system-specificCHtmlSysWin::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 classCHtmlFontDesc
(defined in the portable headerhtmlsys.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 theweight
member to select bold text. Theface
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. Whenface_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()
inwin32/htmlw32.cpp
uses this information when performing the parameterized font name translation. - The new member variables
bgcolor
anddefault_bgcolor
ofCHtmlFontDesc
provide information on the font’s background color, if it has one. Ifdefault_bgcolor
is TRUE, thenbgcolor
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()
, andCHtmlSysWin::delete_timer()
. Related to this new mechanism is the new classCHtmlSysTimer
. 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 inCHtmlSysFont
.- 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 theCHtmlFormatter
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 anappctxdef
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 thetrdmain
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 yourappctxdef
structure). This calls TADS to load and run the game. (Note thattrdmain
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.