Is there a straightforward way to do customizable typographic formatting of T3 output that’s portable across interpreters? For the web-based ones you can use markup and twiddle the page’s CSS. But that doesn’t work in, for example, FrobTADs.
I can sorta munge together something using T3’s output filters, but it feels very kludgy. E.g., something like:
#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
pebbleOutputFilter: OutputFilter
tagPattern = static new RexPattern(
'<nocase><langle>(/pebble|pebble)<rangle>')
isActive = true
activate() { isActive = true; }
deactivate() { isActive = nil; }
_filterTagOpen = nil
_filterResultsVector = nil
filterText(ostr, val) {
local idx, str;
// Only filter text if we're currently active.
if(isActive == nil)
return(inherited(ostr, val));
// Get the first occurance of our markup tag.
idx = rexSearch(tagPattern, val);
// Loop until we're out of input or tags.
while(idx != nil) {
// Is this an open or a close? If the tag starts
// with a slash then we're a close, otherwise we're
// an open.
_filterTagOpen = !rexGroup(1)[3].startsWith('/');
// Get the stuff after the tag.
str = val.substr(idx[1] + idx[2]);
// Create a results vector.
if(_filterResultsVector == nil)
_filterResultsVector = new Vector();
// Append the stuff before the tag to the results
// vector. We store things as a two-element array,
// the first element being the matching text and
// the second being a boolean flag indicating
// if it was found inside our markup tags (boolean
// true) or not (nil).
_filterResultsVector.append([
val.substr(1, idx[1] - 1),
!_filterTagOpen
]);
// Update our value to only look at what's after
// the tag we just looked at.
val = str;
// Find the next tag, if any.
idx = rexSearch(tagPattern, str);
}
// If we found matching text, we'll have a results
// vector, which we now check.
if(_filterResultsVector != nil) {
// If str is non-nil, that means we had a little
// bit left over after our last tag match, so we
// add it to the results vector.
if(str != nil)
_filterResultsVector.append([ str, nil ]);
// Now we make a string buffer to dump our results
// vector into.
val = new StringBuffer();
// Go through the vector, checking if each bit was
// found inside or outside our markup tags and
// adding it to the string buffer with appropriate
// formatting.
_filterResultsVector.forEach(function(o) {
// Each element of the vector is a two-element
// array. The first element is the text,
// and the second is a flag indicating if
// it was found inside the markup tags or
// not (boolean true if it was, nil otherwise).
if(o[2] == true) {
val.append(_filterFormat(o[1]));
} else {
val.append(o[1]);
}
});
// Convert the buffer into a string.
val = toString(val);
// Reset the results vector.
_filterResultsVector = nil;
}
return(val);
}
_filterFormat(str) {
local ar, buf, r;
// Split the string at whitespace.
ar = str.split(R'<space>+');
// If we don't have any spaces, we don't have anything to do.
if(ar.length < 2)
return(str);
// buf will hold our line buffer and r will hold our return
// buffer.
buf = new StringBuffer();
r = new StringBuffer();
// Start out with an indentation.
buf.append('\t\t');
// Go through every word(-ish thing) in the string.
ar.forEach(function(o) {
// If we just have a newline by itself, insert a
// line break and reset the line buffer.
if(rexMatch('^<space>*<newline>+<space>*$', o) != nil) {
r.append(toString(buf));
r.append('<.p>\n\t\t');
buf.deleteChars(1);
buf.append('\t\t');
return;
}
// Append the word to the line buffer and add a space.
buf.append(o);
buf.append(' ');
// If we've reached the end of a line, flush the
// line buffer to the return buffer and reset the
// line buffer.
if(buf.length() > 40) {
r.append('\n\t\t');
r.append(toString(buf));
r.append('\n');
buf.deleteChars(1);
buf.append('\t\t');
}
});
// Add anything left over in the line buffer to the return
// buffer.
if(buf.length > 0) {
r.append('\n\t\t');
r.append(toString(buf));
r.append('\n');
}
// Return the return buffer as a string.
return(toString(r));
}
;
startRoom: Room 'Void'
"This is a featureless void. "
;
+me: Person;
+pebble: Thing 'small round pebble' 'pebble'
"This is a bit of random pre-markup text.
<.p>
<PEBBLE>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
\n
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</PEBBLE>
<.p>
This is a bit of concluding post-markup text. "
;
versionInfo: GameID;
gameMain: GameMainDef
initialPlayerChar = me
newGame() {
mainOutputStream.addOutputFilter(pebbleOutputFilter);
runGame(true);
}
;
This defines a new OutputFilter
called pebbleOutputFilter
. It looks for the <PEBBLE></PEBBLE>
markup tags, and will format anything inside those tags as double tab indented text.
So given text formatted like the pebble’s description:
+pebble: Thing 'small round pebble' 'pebble'
"This is a bit of random pre-markup text.
<.p>
<PEBBLE>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
\n
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.
</PEBBLE>
<.p>
This is a bit of concluding post-markup text. "
;
This will output:
Void
This is a featureless void.
You see a pebble here.
>x pebble
This is a bit of random pre-markup text.
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
This is a bit of concluding post-markup text.
>
…which does everything I expect it to, and appears to work cross-interpreter.
The exact typographic formatting it’s doing here isn’t particularly interesting.
And the formatting method is ugly and unoptimized. And could probably be better handled with native HTML tags that are handled by all modern interpreters.
But I’m talking more about the overall filtering mechanism: regex matching tags, using a bespoke line buffer to manage captured text, and so on. Is there a quicker/cleaner/better supported approach to this sort of thing?
What I’m thinking of in broad terms is things like implementing stylistic tags that give different typographic effects for different kinds of in-game text. E.g. formatting text the player reads in books as block quotes, overheard coversations are in italics or something, or even more elaborate stuff like, I don’t know, everything ghosts say is right-justified or something like that.
Clearly everything could just be hard-coded with whatever HTML/other markup is needed to produce the effect. But I want to have a single toggle to turn any typographic effects on and off, for several reasons: testing, to give the player the option to customize the appearance, to have a master toggle (for e.g. screen readers) to turn off all typographic effects, and so on.
This seems like it would be very much the sort of thing that a game engine for implementing all-text games would have a lot of stuff for, but most of the T3 filtering stuff actually seems more about straightforward string substitutions, rather than typographic formatting in general.