Glk extension proposal: CSS

,

@mathbrush Do you have any notion of what we might want/need in a “basic” profile of Glk CSS? Any sense of what Bisquixe users generally do? Any especially compelling use cases that we might have overlooked?

1 Like

Drew Cook’s guide is pretty comprehensive:

(I saw you already included BJ Best’s)

For basic, I’ve mostly seen colors for backgrounds, text, and text highlights, text alignment (like left and right, for text messaging), boxes and borders, span and paragraph styles.

Allowing google font importing has been a big thing. I know that is static, so could be done by editing the main css file, but people that use Bisquixe typically do it to avoid learning how to permanently alter a game’s css.

I just looked up games tagged bisquixe. Drew uses it the most. He’s openly posted source code; here’s his main css text (@kamineko I can remove this if you prefer!)

Summary
this is the initial buffer window rule:
	css-set-fast ".BufferLine; white-space; pre-wrap";
	css-set-fast ".BufferWindow; font-family; Open Sans, sans-serif";
	css-set-fast ".BufferWindow; background; [one of]#020213[or]#e8e8f7[cycling]";
	css-set-fast ".BufferWindow; color; [one of]#e8e8f7[or]#020213[cycling]";
	css-set-fast ".BufferWindow; font-size; 16px";
	css-set-fast ".BufferWindow; scrollbar-color; [one of]#e8e8f7 #020213[or]#020213 #e8e8f7[cycling]";
	say run paragraph on;
	
this is the grid window rule:
	css-set-fast ".GridWindow; font-family; Roboto Mono, monospace";
	css-set-fast ".GridWindow; background; [one of]#dedef7[or]#020212[cycling]";
	css-set-fast ".GridWindow; color; [one of]#020212[or]#dedef7[cycling]";
	css-set-fast ".GridWindow; font-size; 16px";
	say run paragraph on;
	
this is the initial input rule:
	css-set-fast ".Input; background; transparent";
	css-set-fast ".Input; font-size; 16px";
	css-set-fast ".BufferWindow .Input; font-weight; bold";
	css-set-fast ".BufferWindow .Input; font-size; 16px";	
	css-set-fast ".Input; font-family; Open Sans, sans-serif";
	css-set-fast ".BufferWindow .Input; font-family; Open Sans, sans-serif";
	css-set-fast ".Input; color; [one of]#e8e8f7[or]#020213[cycling]";
	css-set-fast ".Style_input; color; [one of]#e8e8f7[or]#020213[cycling]";
	
this is the tall images rule:
	css-set-fast ".BufferLine img.ImageInlineCenter; width; auto !important";
	css-set-fast ".BufferLine img.ImageInlineCenter; height; 75vh !important";
	
this is the wide images rule:
	css-set-fast ".BufferLine img.ImageInlineCenter; width; 100% !important";
	css-set-fast ".BufferLine img.ImageInlineCenter; height; auto !important";	
	
this is the play rule:
	css-set-fast ".interpretercredit; display; none";
	css-set-fast ".coverimage; display; none";
	css-set-fast ".play; background; #4E4E72";
	css-set-fast ".links; display; none";

	
this is the more prompt rule:
	css-set-fast ".moreprompt; color; #020213";
	css-set-fast ".moreprompt; font-weight; bold";
	css-set-fast ".moreprompt; opacity; .85";
	css-set-fast ".moreprompt; font; Open Sans, sans-serif";
1 Like

Sure, share away. Anything I do (good or bad) is public! All of my published games use css.

1 Like

Inform 7 will make it even easier in the next release to include stylesheets, so things like embedding fonts should be done through that and not the CSS API.

I’m travelling on holidays now so I can’t do a detailed look at the “basic” subset yet, though I’d recommend making it quite minimal, and focused on what Adrift or TADS would need.

-iftf-reverse-video should not be set on anything other than spans and paragraphs. We can note that, and also that the inline functions don’t support window or input.

1 Like

Are pts required by Adrift? They’re a pretty legacy unit in CSS these days, so it would make sense to omit them. They’re also just equivalent to 1.333 px. So it just depends whether it’s easier to convert them in the Adrift interpreter or in the Glk library.

… and windows, right? (I think it’s primarily going to be used on windows, especially text grid windows.)

I think that if we make it minimal, then we’ll really need a mechanism for detecting which features are/aren’t available.

When we discussed this upthread back in August, you pointed out that it would be difficult for RemGlk to implement a glk_css_supports function, because it would have to return its values asynchronously. Currently, Glk’s only way of calling you back with an answer is with an event, which would be an extremely painful API for measuring which CSS properties (and values?!) are/aren’t supported.

It seems like we have three options:

  1. Query for what’s supported (and somehow solve the problem of getting results back asynchronously)
  2. Provide some amount of metadata at startup that provides whatever information the author needs. (But surely not a complete list of all properties and values that the current browser supports…?)
  3. Have a basic set of properties, with no way to query what values actually are supported. (But I think that means that the “basic set” needs to be pretty big, since there won’t be any good way of expanding it, such that games can know that the expansion happened.)

So, here’s my suggestion: we’d have two separate mechanisms to access CSS-support metadata.

  • glk_css_supports: It would have its own gestalt gestalt_CSSSupports, and would only be available in non-remote environments, where the list of supported CSS properties/values is easily determined synchronously. AsyncGlk would not implement glk_css_supports.
  • glk_gestalt(gestalt_WebBrowser, 0): A gestalt that declares that the UI is powered by a browser. RemGlk can easily provide that metadata up front; authors can reasonably assume that if gestalt_WebBrowser is available, then they can safely use any CSS feature that has reached Baseline: Widely Available.

So, if an author wants to test whether border-radius is available, they can check gestalt_WebBrowser, and, if that’s true, they can assume that border-radius is available. Otherwise, they can check gestalt_CSSSupports and call glk_css_supports("border-radius", 13, "5px", 3); if it’s true, then they know that border-radius is available.

That way, we can keep the official “basic” profile small-ish, but authors can still detect whether it’s safe to use stuff outside that profile.

For what it’s worth, speaking as the guy who’s wrangling the PRs for Spatterlight and Gargoyle, I don’t feel a lot of need to keep the CSS Basic profile ultra minimal. There are only a handful of non-browser Glulxe interpreters under active development (Spatterlight, Gargoyle, WinGlulxe, Fabularium). I’ve been focused on Spatterlight and Gargoyle, but I think it wouldn’t be a lot of work for me to cook PRs for Windows Glk and Fabularium, too. I’ll probably file those PRs once the Spatterlight PR and the Gargoyle PR have both merged (and shipped, perhaps).

I wouldn’t want to add dozens more CSS features to the “basic” profile, but I’d be game to add a few more, if we thought it would make a difference.

FWIW, a lot of what I see in the tag:bisquixe games looks doable in the current set of basic CSS we have.

Probably the biggest gap would be border-radius, plus maybe gradients, and perhaps transitions. Maybe I could add border-radius to basic…? But, maybe not.

I see the text messages in Studio, which are quite complicated; beyond anything that I’d consider for “basic” CSS. It uses :before, content, clip and border-radius. That should work just fine in browser-based interpreters like Parchment/Lectrote, but implementing all of that in Spatterlight and Gargoyle is not likely.

Can you think of any other (simpler?) boxes/borders that folks have used in that might still be worth supporting?

Adding that to Spatterlight and/or Gargoyle doesn’t feel right. People use interpreter apps in order to play offline. My hunch is that the right approach for offline interpreters is to add font support to the Blorb standard, plus a glk_declare_font_face() API, and then the Inform IDE would have to know what to do with them.

In any case, doing network I/O is definitely outside what I’d consider “basic” CSS support!

@kamineko Why pre-wrap?

Adrift does use pt, but, of course, Scarier could just multiply by 4/3.

I would describe pt as a “convenience feature”, along with named colors and named font sizes.

My feeling is that the convenience features are trivial to implement in half a dozen Glk libraries, and that they will pay off for years. Authors probably won’t understand why they can write “12px” but not “12pt” in their CSS styles, and why should they have to worry about that?

I will say that Adrift implicitly assumes that very long words will be wrapped, e.g. in Skybreak! when you use the L command to launch your ship, it prints a message like this:

ZZZZOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOMMMMMMMMMMMMMMMMMMMMMMM!!!

It turns out that Spatterlight and Gargoyle automatically wrap this word, but browsers default to letting the word overflow. I was planning to use overflow-wrap: break-word in Scarier, which Spatterlight and Gargoyle would ignore, to make Parchment wrap the word properly.

1 Like

I recall needing to change the white space setting because the default CSS was consuming white space that I had intentionally added. There are other settings that honor user-specified white space; perhaps one of those would have worked as well. As an author, though, if I add something to a text, I want to see it on the screen.

Do you think pre-wrap should be the default?

Dialog specifically collapses runs of whitespace both on web and on Z-machine, on the assumption that it’s probably not what the author wanted. Likewise for multiple paragraph breaks in a row.

But I think that ship has long since sailed for I7, and fixing its spacing behavior at this point will have to happen at the language level, not the CSS level.

Collapsing white space would break Portrait With Wolf, as well as some status screens in my current works in progress. There will always need to be an easy way to override this behavior.

My experience after 5 years of making things with Inform is that authors need more, not less, control over layout.

EDIT:

for comparison's sake
>sp
 
 
[ENTITY: OEW]
 
You have gathered the following ineffectual primeoid magics:
 
     APHOSIS     |   DESCRIPTION     |   GRAMMAR     |   GLYPH  
     -----------------------------------------------------------
     NIHTSEAH    |   detect aphosis  |   SEE         |     A    
     FLEGECORS   |   insect curse    |   PLAGUE      |     P    
     HYLFEOLL    |   mountainfall    |   ROCKFALL    |     R    
     SLYMFEOLL   |   slimefall       |   SLIME       |     SL   
 
[/ENTITY: OEW]

vs

>sp
 
 
[ENTITY: OEW]
 
You have gathered the following ineffectual primeoid magics:
 
APHOSIS | DESCRIPTION | GRAMMAR | GLYPH
-----------------------------------------------------------
NIHTSEAH | detect aphosis | SEE | A
FLEGECORS | insect curse | PLAGUE | P
HYLFEOLL | mountainfall | ROCKFALL | R
SLYMFEOLL | slimefall | SLIME | SL
 
[/ENTITY: OEW]
2 Likes

I think you probably don’t want/need a blanket rule setting white-space: pre-wrap for that.

Glk includes support for style_Preformatted, which uses a monospace font by default, and preserves whitespace.

In HTML, you’d typically use a <pre> tag for an ASCII table like that, which would have white-space: pre as part of the browser’s stylesheet.

(In 2024, white-space was refactored into white-space-collapse and text-wrap-mode, but those aren’t considered “widely available” yet.)

So, rather than just always setting white-space-collapse: preserve on all buffer lines (which is what you’re doing by setting white-space: pre-wrap), I think you could just use style_Preformatted as needed, and let the browser collapse spaces in ordinary proportional text.

Personally, I’m more interested in text-wrap-mode: nowrap. On mobile, I bet your primeoid table has word wrap, which probably makes the table quite hard to read.

Nonetheless, against Dannii’s intuition to keep CSS Basic as minimal as possible, I’d be mildly in favor of adding white-space-collapse and text-wrap-mode to CSS Basic, including supporting white-space as a shorthand for those.

Should there be a Glk API for tables? buttons? lists?

… but, really, in HTML, you wouldn’t normally use a <pre> tag to make an ASCII table. You’d use a proper <table> instead.

I note that Glk currently has no support for several standard elements of HTML 3. Glk doesn’t support bulleted / numbered lists, tables, or buttons.

(You can do hyperlinks in Glk that act like buttons, and perhaps with border-radius you could style the hyperlinks to look like simple buttons, but there’d be no way to say “make these look like system UI buttons”, which is the default behavior of <button>.)

Maybe there should be a Glk API for tables. Then, authors could then use the Glk CSS API to style them…? … and buttons? And bulleted/numbered lists…?

Yep, you’re right. My mistake.

So far as the mobile table goes: I would just write a different screen for mobile. That’s the plan anyway. The alternative would be to have no table for the bulk of my audience base, which is desktop.

Inform doesn’t have built in support for making ASCII tables, though that might apply to some other systems you are working on. I think it would be great if it did. I think a common idea is that it’s either form-feed printing or jump all the way to vorple, but there’s a lot of space in the middle I think, if someone were to try and offer more HTML features

Does this count? (It might not!)

Example Location is a room. "You have gathered the following ineffectual primeoid magics:[paragraph break][fixed letter spacing] APHOSIS | DESCRIPTION | GRAMMAR | GLYPH[line break] -----------------------------------------------------------[line break] NIHTSEAH | detect aphosis | SEE | A[line break] FLEGECORS | insect curse | PLAGUE | P[line break] HYLFEOLL | mountainfall | ROCKFALL | R[line break] SLYMFEOLL | slimefall | SLIME | SL[variable letter spacing]".

It renders like this in Parchment:

Preformatted
An Interactive Fiction by Dan Fabulich
Release 1 / Serial number 260921 / Inform 7 v10.1.2

Example Location
You have gathered the following ineffectual primeoid magics:

 APHOSIS     |   DESCRIPTION     |   GRAMMAR     |   GLYPH
 -----------------------------------------------------------
 NIHTSEAH    |   detect aphosis  |   SEE         |     A
 FLEGECORS   |   insect curse    |   PLAGUE      |     P
 HYLFEOLL    |   mountainfall    |   ROCKFALL    |     R
 SLYMFEOLL   |   slimefall       |   SLIME       |     SL

At the risk of throwing everything into CSS “Basic”, I’ve got one more that I think would be really good to add: light-dark() for color and background-color.

The light-dark() CSS function accepts two colors or two images and returns a color or an image based on the active color scheme, without needing a prefers-color-scheme media feature.

For example, you could write color: light-dark(black, white) and that would use black text in light mode, and white text in dark mode.

(It’s not widely available in browsers yet, but it will be considered widely available in November.)

So, uh, my shopping list is now:

  • light-dark() support for color and background-color
  • white-space-collapse: collapse, preserve
  • text-wrap-mode: wrap, nowrap
  • white-space as a convenience synonym for white-space-collapse and text-wrap-mode
  • overflow-wrap: normal, break-word
  • text-decoration-line: line-through
  • font-family: cursive
  • border-style: solid

Sorry, I thought you were referring to something like a table element or some kind of grid. Yes, inform will let you count spaces manually (though this was generated programmatically so that wouldn’t help here).

Changed my mind again (again).

Basic can stay really basic, even removing all convenience features, as long as we lock in a good strategy for detecting what is/isn’t supported. (I’m still liking my approach to support glk_css_supports only on non-remote platforms, and to have a gestalt_WebBrowser for browser-based platforms.)

  • I do still want to add font-family: cursive to the list (because font families can’t be feature-tested)
  • And light-dark() feels really important
  • And I think basic should include border-style: solid just to ensure we can actually test this thing.

If we can agree on a solid API for CSS feature detection, we could whittle the definition of “Basic” down to:

color                   /* <color>, light-dark(<color>, <color>) */
background-color        /* <color>, light-dark(<color>, <color>) */
-iftf-reverse-video     /* reverse | none */
font-weight             /* normal | bold | 400 | 700 */ /* 400 = normal, 700 = bold */
font-style              /* normal | italic | oblique */
font-size               /* <%> | <length> (positive only) */
font-family             /* <family-names> */
text-decoration-line    /* none | underline */
text-align              /* left | right | center | justify */
margin-left             /* <length> (non-negative) */
margin-right            /* <length> (non-negative) */
text-indent             /* <length> (non-negative) */
border-style            /* solid */

Value types

Type Grammar / examples Notes
<color> #rgb, #rrggbb, #rrggbbaa Browsers support lots of other ways of specifying colors: rgb(), hsl(), named colors, etc. but Basic only requires these. Transparency support is required; #00000000 should be transparent, not opaque black.
<length> 12px, 2em, 1.5em, 0px, 0 Basic only requires support for non-negative px and em units, plus unitless 0. Browsers support dozens of other units of length.
<%> 0%, 100%, 50%, 12.5%
<generic-family> serif, sans-serif, monospace, cursive Browsers support other generic families, but Basic only requires support for these.
<family-names> 'OCR A extended', "Courier New", Courier, monospace Comma-separated list of family names, including <generic-family> names. Quotes required for names with spaces.

Now, it turns out, I’m gonna go ahead and support everything on my shopping list in Spatterlight and Gargoyle, but the spec doesn’t have to say that, as long as authors have a way to detect whether a feature is implemented or not. I’m going to add all of the convenience features I identified upthread, plus white-space, overflow-wrap, text-decoration-line: line-through (strikethrough), and whatever else strikes my mood.

1 Like

From an author’s perspective, how is something like this utilized?

1 Like

Let me begin by saying that I think I’m a bit more comfortable reaching for I6 code than most Inform authors, so I’m going to start by addressing the question in I6, and then consider the question in Inform 7 code.

Fundamentally, Inform Glulx games can do anything Glk allows, and they are also confined by what Glk allows. Here’s the Glk specification.

Glk defines a bunch of functions that you can call from I6 code (which you can write in an Inform extension); those functions do a bunch of things. Some of those functions, by design, are not available in all interpreters, so you’re supposed to check if they’re “available” before you use them, using a Glk “gestalt.” For example, Basic Hyperlinks has code like this:

To say set link (N - a number):
	(-  if (glk_gestalt(gestalt_Hyperlinks, 0)) glk_set_hyperlink({N}); -)

To say end link:
	(-  if (glk_gestalt(gestalt_Hyperlinks, 0)) glk_set_hyperlink(0); -)

That stuff in the parentheses is I6 code, directly calling Glk functions defined in the spec. glk_set_hyperlink() is defined in Section 9.1.

glk_gestalt() is defined in Section 1.7. Each gestalt has a code number, which is some number that authors and interpreters all agree on in advance. glk_gestalt() allows the interpreter to say “I do/don’t support hyperlinks.”

The code above lets users of Basic Hyperlinks (Inform authors) write code like this:

Mossy Bank is a room. "The shore here is made up of round rocks,
very heavily grown over with a slippery grey-green moss. To the
[set link 1]north[end link] is a small shack."

Fundamentally, an Inform Glulx game’s entire job is to communicate with Glk. Inform’s internals and the extension library are designed to protect you from having to think about that, but it is the ultimate truth underlying all Inform code: somewhere down there, somebody’s turning the code you write into Glk function calls, writing printed text to the buffer with glk_put_buffer(), reading text that the user wrote with glk_get_line_stream().

The Glk specification also allows us to define “Glk extensions,” with new functions that you might want to call, with a new gestalt code that authors can check. The idea is that Glulx interpreters (Spatterlight, Gargoyle, Parchment, Lectrote, Windows Glulxe, Fabularium, Frotz) would add support for a Glk extension and publicly agree on a gestalt code. Then, in an Inform extension, you’d write:

Constant gestalt_CSSBasic = $1110;

To decide whether glk css is supported:
	(- ( glk_gestalt( gestalt_CSSBasic, 0 ) ) -).

The magic number 1110 is written in Dannii’s CSS specification. Dannii selected that number arbitrarily, out of a reserved block of numbers that Zarf assigned him, to ensure that no two extensions will use the same gestalt number.

Support for these functions can be tested with gestalt_CSSBasic (Gestalt code 0x1110).

Furthermore, each function in an extension is also assigned a standardized number that the interpreter(s) and the author agree on in advance. There’s a fundamental function in Inform, glk(), that accepts a function number and list of I6 function parameters, and calls the Glk function with those parameters, whatever they may be.

Dannii declared that css_inline_set has function code 1113, so you can write this in I6:

[ glk_css_inline_set csstarget prop proplen val vallen;
	glk($1113, csstarget, prop, proplen, val, vallen);
];

Then, I6 code can call glk_css_inline_set. You should check the gestalt before you try calling function 1113, but if the gestalt says CSS Basic is available, then you know it will work.

Almost all of this thread has been about figuring out what the Glk CSS functions should do, what they should be called, and how they should work. The idea is that once something becomes possible in I6 code, anyone can make a little Inform extension to make it nicer to use in Inform 7.

What should the Inform 7 extension look like? Whatever we want, I guess?

It’s not very clear to me what Inform 7 authors would find best/easiest. I think some authors would want an API that’s very close to glk_css_inline_set, allowing the author to specify exactly what happens in Glk CSS terms. Off the top of my head, I could imagine a “CSS” extension that lets you write this:

Instead of examining the crystal:
	set CSS "color" to "#f00";
	set CSS "font-weight" to "bold";
	say "The crystal blazes.";
	clear CSS "color";
	clear CSS "font-weight".

I think the default target would be spans, but you could target other stuff like this, perhaps?

set CSS "text-align" of the next paragraph to "center";
say "Chapter One[line break]";

set CSS "color" of the next hyperlink to "orange";
say "[set link 1]Click here[end link]";

set CSS "border-radius" of the next image to "12px";
display the Figure of logo;

But I think many authors would want a say-based API with a hand-picked list of features:

The Crystal Cave is a room. "A [scarlet]crimson[end colour]
crystal sits on a [bold]stone[end bold] pedestal.
[italic]Something whispers.[end italic]

[larger]WARNING:[end larger] [underlined]do not touch[end underline].

[centered]Chapter One[end centered]
Once upon a time..."

There’s room for both approaches, I think. And, more importantly, any Inform author who comes up with a nicer, smoother way of using CSS can publish their approach as an extension, and people might use it. (If it becomes very popular, it might even become part of Inform’s internals, and get documented in Inform’s documentation.)

It’s about making things possible

The arrival of Glk CSS won’t remove all of the limitations of Glk, letting you do anything you can do in Bisquixe or Vorple.

For example, in Glk, you can’t change anything about a window (e.g. the transcript text buffer) once it’s opened. To set a color scheme before the window opens, maybe you’d write something like:

Before starting the virtual machine:
    The background color of the main window is white in light mode and black in dark mode.
    The text color of the main window is black in light mode and white in dark mode.

If you want to change the background color as you play, well, too bad, Glk doesn’t allow that. Instead, you have to close and re-open the main window with Flexible Windows, like this:

After going to the pink room:
	now the background color of the main window is pink;
	close the main window;
	open the main window;
	open the status window;
	try looking.

That erases the entire transcript. :cry:

So, I’ve also been discussing an API to change that. Changing the open window background color in Glk

And, as we’ve been discussing in Boxed quotations in Inform, there’s also no way to make a Glk window pane that floats on top of (or overlaps) another window pane. Neither is there a way to make a Glk window that’s an actual operating-system window, a window that users can resize, minimize, and close.

My hobby lately is to push on the edges of what Glk is capable of, not by exposing all of the web’s features to Inform, as Vorple does, but by adding actual extensions to Glk to make new things possible in interpreters that don’t use web UI.

Once Glk makes more things possible, you can use them in Inform immediately, without waiting for Inform developers to publish a new release.

(That’s lucky, because there was a seven year gap between Inform 9.3 and Inform 10.1. Inform 11 is “coming soon”, but probably not in 2026, which means it will also have at least a four-year gap, possibly more like five or six years, in practice!)

If there was something that worked in Inform 9.3, then it is definitely possible to write I6 in Inform 10 to do it now, because Glk (and its extensions) are what determines what’s actually possible.

2 Likes

Thanks for taking the time to explain this in detail. Some of this I’ve encountered thanks to Bisquixe, for instance the latest version of MDSS will only ask about screen size if the program is running in a Bisquixe interpreter.

I would personally want both phrases and say definitions, more or less as you’ve laid them out.

This is a cool project! I’ll probably always do something for a web release because I used to have students who could only access content on their phones, but I’d love to release similarly styled blorbs with this sort of tech.

Yeah, at this point the proposal is mostly discussing what should be possible, rather than how people should accomplish it. Once the basic capability exists, then I7 extension authors can expose it however they like.

So at the moment, the question is: which CSS properties should authors always[1] assume they have control over? What’s a basic, core set of CSS that we’ll want every interpreter to support?


  1. Not quite always. But mostly always. ↩︎