New Glk styles

From IFWiki

This is a very rough draft of the new Glk style system which I have been procrastinating on, wigglesome-wagglesome, for about ten years now. Please comment; that's why I'm throwing it on the wiki.

A new kind of Glk style system

A glimpse through the fog

Our goal is to define a "Glk stylesheet", a subset of CSS. This will typically be provided as a chunk in a Blorb package. A web-based IF interpreter, or a terp which uses an HTML toolkit, will be able to insert the stylesheet directly into its HTML output. (Either with @import or DOM document manipulation.) A standalone IF terp will be able to parse the stylesheet itself, and apply it to the game output; ideally the results will be close to the Web rendering.

This requires three definitions:

  • A strict subset of CSS (version 2.1), which should be understood by all terps. Authors can expect IF terps to handle all of this subset.
  • A less-strict subset of CSS (a superset of the above), which can be parsed by all terps. This includes attributes and selectors which a complete HTML toolkit will be able to handle, but a standalone IF terp may ignore.
  • A document model which web-based IF terps must produce, in order for the stylesheet to be applied properly. A standalone terp should be able to render its output as if it were using this model with the given stylesheet.

An underlying assumption of this model is that the stylesheet is constant throughout the game. It is unpacked and parsed before the game starts running, and the game has no way to modify the stylesheet. From the game's point of view, the world consists of windows, styles, and text; any given span of text appears in one window and has exactly one style.

Another assumption: the game cannot know how the text is eventually rendered on the screen. (Really this has always been true. The old glk_style_measure() and glk_style_distinguish() calls were never very useful, because they could not be determined until after the window appeared, which led to an unfortunate cycle of test-render-test-render.) See "Ints and introspection", below, for more.

Style inheritance

We would like to set up an inheritance tree of named styles, and encourage game authors to build off of that tree rather than inventing entirely new styles. This would maximize the ability of users to customize their interpreters in a meaningful way.

This two-level tree will be available out of the box. The base level is three generic style classes; the next level are the existing Glk styles (plus "Bold", which does not currently exist).

  • body
    • Normal
    • Emphasized
    • Bold
    • Alert
    • Note
    • BlockQuote
    • User1
    • User2
  • head
    • Header
    • Subheader
    • Input
  • fixed
    • Preformatted

(Maybe this tree should be deeper, with "Alert" hanging off "Note", and "Bold" and "Italic" hanging off "Emphasized"? Not sure. Also, I probably want to make "Normal" and "body" the same thing, style number zero. Will fix that later.)

At a basic level, the user might set his terp to use Optima for "head", Times for "body", and Courier for "fixed". More specific customizations would be available if desired. A game could then add a "Subsubheader" style under "head", or add a new subclass of "BlockQuote" for special parser communiques, or whatever. These new styles would be covered by the user preferences, but could also be customized by the game stylesheet.

This plan runs into two difficulties:

  • CSS does not have any notion of style inheritance. (There is a form of property inheritance, but that runs down the DOM tree, not from style class to style class.)
  • The Glk API refers to styles by number. Sharing a textual name between game code, the interpreter, and the stylesheet is a nuisance.

Both of these can be worked around. See below. (But I welcome suggestions for better workarounds.)

Fallback, and hazards of the course

The above introduction contains a lot of "should"s, "may"s, and "ideally"s. There are several different axes of variation:

  • Any terp based on a web toolkit will suffer from the rendering ills that all HTML is heir to. WebKit, Firefox, and the minor HTML renderers do not always behave identically. (We try to control this by limiting what's in the strict CSS subset -- hopefully avoiding dangerous options.)
  • Any terp *not* based on a web toolkit will be re-implementing some of HTML, which is hard. (Again, we provide a limited CSS subset, which is intended to be not impossible to implement by hand.)
  • Some terps have limited output channels, e.g., terminal windows (GlkTerm) or unstyled text (CheapGlk). We will try to define a system which can downgrade cleanly.
  • Interpreters may have options to limit game control of output formatting. For web terps, this consists of a user stylesheet and !important declaration (see CSS spec). For other terps (and maybe web terps as well), this may include preferences for "use my colors", "use my fonts", "use larger/smaller fonts", or other possibilities. All of these features are features.
  • Not all games will provide stylesheets. Each interpreter will provide a default appearance (the "user agent stylesheet", in CSS terminology). This will be controlled by a combination of user preferences and implementor whim, as has always been the case with IF interpreters.
  • Old interpreters (that is, old Glk libraries) were created for the old style-hint system. They will not load stylesheets at all. (The old Glk styles will continue to exist in the new system, so the user agent stylesheet will cover this case.)

We will provide copious sample and unit tests, to make sure that interpreter implementations stay on the same page, or at least within the same chapter.

Rules and definitions

The document model

The document is divided into windows, as described in Glk. A window contains text, which is divided into (wrapped) paragraphs, which are divided into spans of different styles.

The HTML structure generated by a web terp may be more complicated. It must follow this model (the classes of which will be referenced by the stylesheet):

  • The game content must exist within a div with class="GlkDoc". (This may be either an immediate child of the body, or embedded in other page content.)
  • Each window must be a div contained within the "GlkDoc" div. (Not necessarily an immediate child.) The window must have these classes:
    • "GlkWindow"
    • "BufferWindow" or "GridWindow", depending on the Glk window type
    • "WindowRock_XYZ", where XYZ is the Glk window rock value. (Unsigned decimal integer.) This number is assigned by the game at window creation time. (The standard Inform library uses 201 for the story window, 202 for the status window, and 203 for the quote-box window.)
  • Text within a window must be a sequence of spans. (Again, these are not necessarily immediate children of the window div.) Each text element must have class="Style_XYZ", where XYZ is the style name. For a substyle, the class attribute must list the style and all of its parent styles.

(Note that paragraphs can be represented in different ways. Parchment currently uses line-break elements between style spans. GlkOte uses a div wrapped around the style spans. I'm not sure whether these two forms are compatible with a single stylesheet. I think they are, but I'll have to test funny cases like background color and paragraph indentation.)

(In this model, the text-align and text-indent properties won't work for individual styles, because they are meaningless for spans. I have not yet figured out how to deal with this.)

The stylesheet model

The stylesheet document follows the syntax of CSS 2.1, with the following limitations:

  • No @charset statements; the stylesheet must use UTF-8.
  • No @import statements.
  • No !important declarations
  • No declarations nested in { blocks }.

A stylesheet that violates these limitations may be entirely ignored. We will provide sample C code that parses stylesheets within this model.

The stylesheet must begin by defining the style inheritance tree, using @-glk-style declarations. (CSS ordains that unknown @-rules must be ignored, so this will not cause web toolkits to choke.) These lines look like:

       @-glk-style NUMBER STYLENAME [ PARENTNAME ]

So, for the tree given earlier:

       @-glk-style 12 body;
       @-glk-style 1 emphasized body;
       @-glk-style 15 bold body;
       @-glk-style 5 alert body;
       ...
       @-glk-style 13 head;
       @-glk-style 3 header head;
       ...
       @-glk-style 14 fixed;
       @-glk-style 2 preformatted fixed;

(Old Glk styles retain their numbers; new styles, including the new base styles, get new numbers.) (Should these default declarations exist in every single game stylesheet? Probably not -- the interpreter can assume them.)

The interpreter (even web terps) will have to parse the stylesheet and internalize this tree. A game call glk_set_style(1) -- emphasized text -- would cause a web terp to generate a span:

       <span class="Style_body Style_emphasized">...</span>

IF interpreters should understand and handle selectors in the following forms:

       .WINDOW { ... }
       .STYLE { ... }
       .WINDOW .STYLE { ... }

A WINDOW class is one of the class values mentioned above: GlkWindow, BufferWindow, GridWindow, WindowRock_XYZ. A STYLE class is one of the class values Style_XYZ.

You can also use multi-class selectors, e.g.:

       .WINDOW1.WINDOW2 .STYLE1.STYLE2.STYLE3 { ... }

There can be at most two levels, window and style. However, each level can consist of any number of class values.

All other selectors (including those that use +, *, [attr=...], #id, element, and :pseudoclass) may be ignored.

Property values which are lengths should be given as pixels (px); other units may be ignored. Colors should be specified in hex (three-digit or six-digit). "Inherited" is always an acceptable value.

IF interpreters should handle the following CSS properties:

  • font-family, font-style, font-weight, font-size, text-decoration
  • color, background-color
  • text-indent, text-align, line-height
  • border, padding (windows only)
  • margin (styles only)

Stylesheets should avoid defining the white-space property, as the interpreter may need to use this for correct layout.

Resolution of selector conflicts

The CSS precedence rules, in our limited stylesheet model, are straightforward:

  • Overriding user preferences (equivalent to "user important declarations" in a user stylesheet) take first precedence
  • The game stylesheet is next (equivalent to "author normal declarations")
  • Generic user preference ("user normal declarations") are next
  • Interpreter factory defaults have least precedence

(These are the CSS rules for cascading, section 6.4.1, simplified by the absence of game important declarations.)

Within the game stylesheet, selectors are sorted by the number of classes in the selector definition. (Example: ".BufferWindow .Style_bold { ... }" is a two-class selector. ".BufferWindow .WindowRock_201 .Style_body.Style_bold { ... }" has four.) Selectors with more classes take precedence. If two selectors have the same number of classes, the one defined later wins.

(If user preferences are defined in terms of a CSS file, the same class-counting rule applies to conflicts within it.)

Implementation and details

Defining new styles

We've said that the interpreter learns style names from the stylesheet. We haven't said how those names (and their inheritance relationships) are coordinated between the stylesheet and the game source code.

Ultimately I imagine a native I7 declaration, analogous to the way images are declared:

       Style Disaster is derived from style Alert.

This would cause the compiler to pick a new number, and generate the appropriate @-glk-style line when copying the author's stylesheet from the Materials folder to the built game.

Until that feature exists (and also in I6) it would be the author's responsibility to define numeric constants in the game code, and @-glk-style rules in the stylesheet, and make sure that they match.

The transition

We want new interpreters to work reasonably with old games, and old interpreters to work (perhaps minimally) with new games.

The Glk library will offer a new gestalt selector: gestalt_StyleModel.

If this selector returns nonzero, the interpreter implements the new style model. The glk_style_measure() and glk_style_distinguish() calls will always return zero (see below).

(Do we want to start by asking interpreters to support both the old model and the new model, with the game picking one at runtime? I am inclined to answer "no", despite the etiquette of the opt-in model. This change will be complicated enough for interpreters without asking them to implement two systems in one library.)

Borders

Borders around specific Glk windows, and between them, are a tricky subject. A text window nearly always needs a margin; however, graphical windows sometimes do not, depending on the layout needs of a given game.

This is sufficiently important that we will add a new flag, winmethod_Border / winmethod_NoBorder, to the glk_window_create() call. The terp should (not "must") use this hint when laying out windows. The default is to have borders between windows.

(GlkOte has more precise and flexible ways to specify inter-window borders. Is it worth exporting these to the glk_window_create() level? Alternatively, should we forget the NoBorder flag and do it all with CSS?)

In addition, windows may define space around their content by using the CSS padding and border properties. These spaces are *inside* the bounds of the rectangular Glk window. Avoid using margin properties on windows, as they have more complex layout rules that a standalone terp may not be able to replicate.

Ints and introspection

A downside of the stylesheet model is that it is difficult to for the game to interrogate the display layer. The old glk_style_measure() call could theoretically be implemented in a web-based terp, using the Javascript getComputedStyle() method. However, it would be difficult to parse this information, and translate it to the necessary numeric form; and then it is unlikely that the game could do anything useful with the results. Similarly, glk_style_distinguish() would be difficult and subjective to implement (as indeed it always has been). Therefore, these two calls are deprecated, and will fail (return 0) on new-style Glk interpreters.

At the same time, the game needs to know (at a minimum) the width of a grid window in characters. (Note that border padding makes this tricky; the number of characters that fit in a grid window will, in general, be smaller than the window width divided by the character width.)

The old definition of glk_window_get_size() should still be satisfiable in this model. For a web terp, this implies that the Javascript interpreter will have to be aware of border padding and font sizes, and arrange the Glk window sizes accordingly. (GlkOte provides an example of one way to do this.)