Improving text layout in interpreters

Hello everyone. This probably won’t interest most people, but ive been looking into ways to improve text layout for IF.

I don’t know if any other interpreters do this, and im sure that web browsers don’t, but the idea is “smarter word-wrap”. So it turns out there are better ways to word wrap than simply taking a new line whenever you reach the right margin.

So here is an example. You can see better side by side, but I’ve indicated the areas changed with red arrows.

This is the standard “greedy” algorithm. The areas indicated have untidy white space at the ends of the lines.

So here is the improved version.

You can see the top text is smoother on the right. Also it managed to improve some at the bottom too.

This is using Knuth’s algorithm. It works by assigning a non-linear penalty to unused white space at the ends of lines. So 3 spaces wasted is more than 3 times one space. Minimising this cost over a paragraph leads to a smoother and more consistent right edge.

But there’s more.

In this side-by-side example;

The left panel is the original, and the middle panel, the result of the Knuth wrap. It managed to improve the consistency of the edge on the top paragraph, but it decided to break the hyphenated “high-stakes”.

I didn’t really like this because, although it’s fine to break at hyphens, it would be nice not to. So to tweak the algorithm, the idea is to introduce:

  • A penalty for breaking a hyphen that wasn’t broken before.
  • A reward for joining a hyphen that was broken before.

The result is the right panel. Which isn’t especially exciting because it put back the unbroken hyphenated “high-stakes” and kept only a small change at the top.

Well, that’s today’s experiment!

8 Likes

Interesting. But can it handle the window be resized?

Sure. You can resize in real time. On web too.

1 Like

With CSS, you can now set text-wrap-style to pretty but it doesn’t work on Firefox at the time of writing (and I suppose the algorithm used is browser-dependant). MDN warns that it’s less performant, but I don’t how much it matters.

I don’t think any web interpreter uses that yet.

I can see an issue, though. If the output is not displayed a paragraph at a time (say, display some text, wait for any key, append some text to the same paragraph), it can make the paragraph reflow, which would be jarring.

5 Likes

In a web browser, FWIW, according to caniuse.com text-wrap-style is supported by all modern browsers. I just tried it in Firefox on this page and text-wrap-style: balance adjusted the rag. There’s also a hyphens property for controlling hyphen wraps, and text-align:justify if you want to eliminate rag entirely. And you can wrap text around an image using float and/or shape-outside.

If you look in the sub-features, you’ll see that pretty is not supported in Firefox yet.

The value balance is intended for small texts like headings, and the browser won’t take it into consideration for texts longer than a few lines because it is expensive. It’s pretty that is intended for body text.

3 Likes

It’s encouraging to know that this, and other options, might be included in browsers. I don’t see why not. I have discovered it can easily run real-time while a window is resized.

There aren’t actually performance problems because you only apply the “smart wrap” to paragraphs at a time. Once you have a new line in your text, your flow is over. So i guess a huge paragraph might give it some work, but that doesn’t really happen. Actually, i guess i should put in a test for that, say a clamp on the max number of lines.

What i actually do is always compute the “greedy” layout first. Then i calculate the “badness” metric for this, which acts as an upper bound. Then i do a heuristic to figure out if a better method is likely to gain anything. If it could, the code proceeds to perform the improved layout (if any).

So, for example, if the greedy max-line-length - min-line-length < threshold, smart wrap won’t likely make a difference. Just a question of choosing the right threshold.

On the point about appending to an existing paragraph. Yes indeed, this could make the earlier parts of of the flow change! Something to watch out for, if that’s done. Currently, i don’t think i ever do that.

In a sense this is very common in IF: it’s what happens when you print a text prompt, await input, and then ensure that the input line is displayed (as typed) after the prompt. That’s one paragraph (from the interpreter’s point of view) and you’ve appended text to it.

Of course, the text prompt is usually very short, so this doesn’t cause any rewrapping.

The issue is that with a generic interpreter like Parchment or Lectrote, you can’t know in advance if appending to a paragraph will happen (you don’t control the story) besides the command prompt mentioned by zarf. Same thing about long paragraphs if performance is an issue.

You have to decide if the better typography is worth it. I’d say yes, because I don’t think it happens often.

(Or you could make it an option in the settings.)

I agree, this happens very rarely outside of the command-prompt case. At least in parser IF.

Nearly always, when game wants to pause and then add some text, there’s a line break in between.

In choice-based IF, things are rather different. It’s a pretty standard trick for a Twine game to modify or extend paragraphs on the screen as the player clicks things. I don’t have a sense for how “smart wrap” would play in that world.

2 Likes

Sorry, im not really getting this point about the input line. Are we talking about multi-line input commands? Because obviously you don’t wrap a single line. Also, you don’t apply smart wrapping to two lines, because;

bla de ba de ba, de blar blaa
what?

The final line is always ignored, because it’s the leftover text. In the above, the line “what?” has a huge badness because most of the line is blank. If you shortened the one before, it would be terrible (although less “bad”).

eg.

bla de ba de ba, de
blar blaa what?

You’d get two half-length (ish) lines. This has a lower badness, but is worse.

On the drip-feeding the UI problem;

The text is displayed once the game has hit a waiting state. This will either be the command line prompt or a “continue” button for more.

Something i haven’t covered yet is the problem of paginating text. In my case, the game sends text at will to the UI and it’s the UIs job to display and paginate it. In other words the UI does the “continue” not the game. This is quite important when you can resize the UI.

So the UI tries quite hard to paginate in a good place. candidates in decreasing preference are:

  1. blank lines.
  2. end of paragraphs
  3. newlines
  4. end of sentences
  5. whatever.

You’d have to try really hard to have no newlines and fall back to 4 & 5. In all other cases, you have a newline which means you can smart wrap text confident that text already shown won’t change on a reflow.

Of course, it’s possible. Just the same as having a monster paragraph of really long words. In which case I think you just get what you get!

In the case of long paragraphs, i skip the smart algorithm if there are >100 lines (which isn’t likely, but possible).

Remember also that both smart wrap and greedy both deliver the same number of lines regardless. And you only have to consider smart wrap for blocks of text between newlines and white space.