Re-printing text that was printed earlier

I’d like to be able to re-print the exact response that was printed on an earlier turn. It would be an out-of-world command (world state not changed).

Something like this:

GET BOX

You pick up the box. It’s really heavy.

You put it in your backpack.

George enters the room.

REPEAT

You pick up the box. It’s really heavy.

You put it in your backpack.

George enters the room.

I’d prefer something that retains italics, boldface, and line breaks. I’m imagining a switch I could toggle, that would start dumping text to a buffer as well as printing it. And then I could just “say” that buffer. But I haven’t had any luck making this work with Inform 7 code. (I’m using 6L38.)

Any thoughts?

1 Like

The hard part is retaining the formatting. That’s an unfortunate side effect of how text styling is implemented in the Z-machine and Glulx.

For everything else, though, you can use Text Capture by Eric Eve. (This is the version for v10, but you can also get versions for previous editions from the same repository.) EDIT: I see now you’ve said you’re using 6L38, so you want this one.

There are three key phrases here: “start capturing text”, “stop capturing text”, and “say the captured text”. Just insert those at appropriate places in the turn sequence and voilà:

First before doing anything: start capturing text.
First every turn: stop capturing text; say the captured text.

Repeating is an action out of world applying to nothing.
Understand "repeat" as repeating.
Carry out repeating: say the captured text.

Internally, this basically works exactly the way you described. In Glulx it involves opening a memory stream; on Z-machine it involves activating output stream 3. The result of both is to print everything to a buffer in memory instead of to the screen until you turn it off again. Annoyingly, neither the Z-machine nor Glulx lets you print to both at the same time in an intuitive way. Edited to correct this paragraph.

1 Like

It looks like during capture, text does not get sent to the screen. (So formatting gets lost, even the first time it’s printed.)

Also, line break behavior seems to change in some cases.

But I may be able to make do with this – thanks.

Thinking a bit more, is there a simple adjustment to the StartCapture code so that it would print both to the screen and to the buffer? That would allow formatting to appear during the first printing.

Ah, that’s a mistake on my part. You are correct, Text Capture doesn’t print to the screen while it’s printing to the buffer. So you need to say the captured text right after you stop capturing.

On Glulx, you could alternatively use an “echo stream” for this, which captures anything being printed to the window while still letting it be printed there. This is used for making transcripts, by default—and annoyingly, each window can only have a single echo stream. So if you use that, you can’t use transcripts.

Okay, I have a proof-of-concept of a solution.

First, we define formatting codes that print a special escape sequence if text capturing is active.

Include Text Capture by Eric Eve.

To say escape code: say "§".
To say ital:
	if text capturing is active, say "[escape code]i";
	otherwise say italic type.
To say rom:
	if text capturing is active, say "[escape code]r";
	otherwise say roman type.
Instead of waiting: say "This is [ital]very[rom] exciting!"

Then, we write a printing routine that watches for these escape codes.

To say (T - text) safely:
	let index be zero;
	while index is less than the number of characters in T:
		let C be character number index in T;
		if C exactly matches the text "[escape code]":
			increment index;
			let D be character number index in T;
			if D exactly matches the text "i":
				say ital;
			otherwise if D exactly matches the text "r":
				say rom;
		otherwise:
			say C;
		increment index.

And then we test it by printing everything twice:

First before doing anything except looking:
	start capturing text;
First every turn:
	stop capturing text;
	say "[captured text]" safely;
	say "[captured text]" safely.

This is, as you would expect, agonizingly slow. As in, several seconds per turn slow. But it shows that the concept works:

image

In other words, this would give us a way of preserving formatting through Text Capture. Meaning italics would no longer be lost during implicit actions, for example.

The big slowdown is in the printing routine, since it’s transmuting and untransmuting the text for every single character. Rewriting it in I6 should fix that.

2 Likes

And voilà, Inform 6 implementation. No noticeable delay any more!

The escape detection rules are a number based rulebook.
Escape detection for 105: say italic type.
Escape detection for 114: say roman type.

To say captured text safely: (- PrintCaptureSafe(); -).

Include (-
[ PrintCaptureSafe len i;
	len = captured_text-->0;
	i = 1;
	@push say__pc;
	say__pc = PARA_NORULEBOOKBREAKS;
	while(i <= len){
		if(captured_text-->i == 167){
			i++;
			FollowRulebook((+ escape detection rules +), captured_text-->i);
		}else{
			glk_put_char_uni(captured_text-->i);
		}
		i++;
	}
	@pull say__pc;
];
-).

And to test it:

First before doing anything except looking:
	start capturing text;
First every turn:
	stop capturing text;
	say captured text safely;
	say captured text safely.

image

I think the extra paragraph breaks come because I invoked a rulebook, but I set PARA_NORULEBOOKBREAKS, so…dunno. I’ll tinker and see if I can fix that.

3 Likes

Great! Thanks for working on this!

Here it is, all polished up as an extension. Compatible with both Z-machine and Glulx, though not with Glulx Text Effects; the only formatting it supports is Inform’s built-in “bold type”, “italic type”, “roman type”, “fixed letter spacing”, and “variable letter spacing”.

From the user’s end, this should work exactly the same as Text Capture.

Adding more formatting, if you need it, is straightforward: just make your formatting command print [escape code] followed by a single character, and write an “escape detection rule” that imposes the appropriate formatting when it sees that single character. So if someone needs more Glulx text styles (I know I sometimes use alert style for bold italics) it’s not hard to implement that.

The only thing missing is FyreVM support; Text Capture has a section with special FyreVM code, but I don’t know enough about that to feel confident modifying it (and certainly can’t test any modifications). So if you need FyreVM, stick to Eric Eve’s version.

Formatting Capture.i7x (5.4 KB)

4 Likes

Oh, I should also say, this is only compatible with version 9.3, not with version 10. Not much needs to change for version 10 (just specify that some replacements apply to Basic Inform rather than the Standard Rules) but a long-standing hatred between the IDE and my monitor makes it extremely difficult for me to test on different versions. So that’s going to have to wait a while longer.

That wouldn’t actually be too hard to support. You could intercept calls to glk_set_style/glk_set_style_stream and then record the style number.

I think your extension is an excellent start! But to suggest a few improvements, I don’t think 167 is a good escape character (even though it can be changed it can’t be lowered when defined that way.) It’s Ë in ZSCII and § in unicode, so those could be used in stories. What about using ESC itself (27)? It would definitely be fine to print in Glulx. I think it might be okay to use in Z-Code, though some interpreters (or perhaps Inform 6 itself) might complain because it’s defined as only an input code, if so something else less likely to be used than Ë could be chosen.

For Z-Code it would also be good to record reverse mode and colours. For Glulx it would in theory be possible to record inline images, though hardly anyone uses them, so it probably wouldn’t be worth adding unless someone requests it. The garglk formatting extension functions are a little more likely to be used but they could also be supported via intercepting the glk function calls. But if these all came into scope then it probably needs more than single character escape codes. That’s not an issue though, once an escape code is seen it can have a little parser that consumes as many characters as needed (the different codes don’t need to be all the same length.)

Lastly it would be good if these formatted texts could be used and sent like any other text (even written/read to files!) So rather than just having the phrase “say the captured text” it would be good to intercept printing texts at a lower level, perhaps indicating that the text has formatting by beginning with an escape code. Or perhaps a different I7 text kind. And if they can be written to files then it might be wise to write up a full versioned spec for this little formatting language.

That’s actually a lot of suggestions haha. Don’t feel you need to do any of it. But I love this idea, so I might work on it myself in the future.

1 Like

That was my first attempt, actually! But Borogove’s Glulx interpreter (Quixe I think?) complained about a non-printable character being sent to an output stream. Code 167 was chosen basically at random to get the proof-of-concept working, but I agree it’s not an ideal choice. (It could be improved slightly by printing any unrecognized escape sequences literally, but “§i” is still something that might theoretically appear in game text.)

Another thought was backslashes, but ASCII-art maps love drawing lines with those.

Absolutely; it shouldn’t be too hard to integrate this with Basic Screen Effects and Glulx Text Effects (are those still the standard “basic formatting tools” extensions?), all it would take is a bunch of escape detection rules.

That would also let formatted text be saved to transcripts, which would be nice for let’s plays. Or it could make it easier to hook up dumbfrotz to a pipeline that turned format codes into, say, Markdown; that would let bots like Floyd more easily format their output for platforms that support it, like Discord.

Unfortunately, digging into the representation of text in the block system is well beyond my current skills. But I would not be at all opposed to formalizing that a bit more and giving the escape codes a proper spec! The question is, if inline formatting codes start to become a standard part of text (passed around, written to files, modified with regexes), at which point does it become easier to just implement them on the interpreter side and bypass Glulx styles entirely?

By all means feel free! This thread made me realize that the very-handy Implicit Actions extension would remove all formatting from the results of implicit actions, since it captures the actions’ output and prints it again after it knows whether the action succeeded or failed.

Then this morning a bolt of inspiration hit and I threw this together on a whim to see if it would actually work at all. Turns out it would!

Huh. That surprises me. Clearly there are nuances that would need to be explored to find out what works reliably across all interpreters.

I’m not sure this kind of system would really suit transcripts. Interpreters could already output to HTML and RemGlk should be used for bots. Something like RTF would be more portable, but if we wanted it to be able to encode all of the Glk formatting options then I think we’d need something new. Which would mean it wouldn’t be useable in external word processors. I think it’s probably better to separate the use cases of transcripts etc from VM-internal text-capture-with-formatting. (When I mentioned writing to files I wasn’t thinking of it being user-readable, just that it could be successfully serialised.)

1 Like

My first attempt worked! (Good sign!)

One other nice feature would be “say unformatted captured text”, which would strip the formatting like it used to.

For example, if you wanted to repeat captured text later as a “memory”, all-italics might feel appropriate, like:

say italic type;
say unformatted captured text;
say roman type;

1 Like

I think when you “say captured text” and no text has been captured, it prints a line break. (Is that intended?)

Also, in some cases the capturing process seems to add an extra line break:

instead of waiting:
    start capturing text;
    say "Hi[paragraph break]";
    stop capturing text;
    say the captured text;

results in an extra break:

> z
Hi

>

I know rules-related line breaks are quirky, so maybe this is hard to avoid. (And I believe this happens with the original Text Capture also.)

Yeah, this happens because the spacing mechanisms of I7 are deeply arcane and I barely understand them. I turned off the mechanism that puts paragraph breaks after a rulebook, so that the escape detection rules wouldn’t destroy the formatting, but there’s something else going on too and I have no idea what.

At some point I’ll attain the gnosis required to make I7 paragraph breaks work and then all will be right.

Also, printing without formatting is definitely a good feature! If you’re on a deadline, you can do that in the current version by disabling the escape detection rules under certain circumstances (“first escape detection rule for a number when Special Scene is happening: rule succeeds”). That will make it ignore all escape codes.

1 Like

I don’t know if this is helpful, but I’ve been playing around and trying to figure out when this “extra line break” appears.

As far as I can tell, it happens when the last rule followed (before “stop capturing text”) either

  1. ends with a paragraph break, or
  2. prints no text

(There’s no urgency to fixing this, but I’m definitely scratching my head about it. Thanks.)

1 Like

Nathanael’s Cookbook has a reference to line break behavior. Dunno if it’ll provide enlightenment on this particular issue.

1 Like

Aha! Okay, that actually pointed to exactly what I need.

The documentation says:

Not all printing is to the screen: sometimes the output is to a file, or to memory, and in that case we want to start the switched output at a clear paragraphing state and then go back to the screen afterwards without any sign of change. The correct way to do this is to push the say__p and say__pc variables onto the VM stack and call ClearParagraphing() before starting to print to the new stream, and then pull the variables back again before resuming printing to the old stream.

So I wasn’t saving and restoring these values properly, and I think neither was Eric Eve.

The main problem is, anything pushed to the VM stack is lost when the routine returns. Which means we can’t push these values in “start capturing text” and then pull them again in “stop capturing text”.

But, since text capture can’t be stacked, I can just save them in global variables.

In no other case should any code alter say__pc except via the routines below.

I was also fiddling with say__pc on my own to avoid line breaks between escape detection rules, but:

(2d) PARA_NORULEBOOKBREAKS suppresses divide paragraph points in between rules in rulebooks; it treats all rulebooks, and in particular action rulebooks, the way activity rulebooks are treated. (The flag is used for short periods only and never across turn boundaries, prompts and so on.)

There’s a single routine that uses this, and I can emulate it to hopefully remove the rule breaks without causing serious damage.

Maybe we should push Graham for an overhaul to the printing system. It seems like too many implementation details need to be handled. It would be better if there was a single variable we could set to turn off paragraph breaks.

2 Likes