Over the last few days, I’ve been experimenting with how ChoiceScript displays different kinds of vertical whitespace. Having searched the forums, I don’t think anyone’s investigated this topic in great depth yet and I thought it would be a good idea to post my results here.
If you’re new to ChoiceScript and need help navigating the (sometimes confusing) mix of options for creating paragraph breaks, I hope this can serve as a useful tutorial! And if you’re a veteran of ChoiceScript and want to gain a much deeper grasp of the tools at your disposal, then you might find my experimental results intriguing. There’s a lot going on under ChoiceScript’s hood, and it’s not immediately obvious what that stuff is.
Last but not least, I’m trying to investigate whether *line_break
commands can cause problems for screenreaders. If you use screenreaders yourself or know people who do, I’d be very interested in hearing about your experiences. I intend to modify this post as I learn more about how various screenreaders cope with different kinds of line breaks.
- The Basics
ChoiceScript has three ways it generates vertical whitespace: blank lines of code, the *line_break
command, and the [n/]
character. Under the right circumstances, each of them will behave differently from the others.
Blank lines of code are what you get when you press the enter/return key twice on your keyboard. In the following code, a blank line is used to induce a paragraph break between the two lines of text:
A line of text.
A second line of text.
Note that only pressing the enter/return key once will not induce a paragraph break. A blank line of code is necessary.
Any line of code that solely contains spaces/tabs is treated like a blank line of code. Furthermore, if an indented block ends with a blank line of code, ChoiceScript treats that blank line as if it lay outside the indented block, regardless of whether the blank line contains indenting:
A line of text.
*if (false)
This text won't be printed, but the blank line below it will still induce a paragraph break.
A second line of text.
(Try highlighting the above block of code to see the invisible indenting, then notice how it doesn’t cause the blank line to be treated as if it were inside the *if
statement.)
The *line_break
command generates a <br>
tag. In isolation, these can “move your cursor” to a new line:
A line of text.
*line_break
A second line of text.
… but that’s just in isolation. In conjunction with other commands, <br>
tags can behave very strangely. We’ll discuss that soon.
As you might guess, two *line_break
s in a row create a blank line of text:
A line of text.
*line_break
*line_break
A second line of text.
This result is visually distinct from the paragraph break produced by a blank line of code. However, for reasons we’ll also discuss soon, you should probably avoid using *line_break
s this way.
The [n/] character is an inline version of *line_break
, similar to the \n
character of many programming languages. Typing [n/]
in the text of your story will produce a <br>
tag at its location:
A line of text.[n/]
A second line of text.
You can put the [n/]
character anywhere there’s story text–even as a stand-alone line of code:
A line of text.
[n/]
A second line of text.
… although you shouldn’t, in your own stories, put it on its own line this way. While several examples in the rest of this post will use [n/]
s on their own line, this is purely to explore how [n/]
characters behave. In your own writing, it’s good practice to use [n/]
s on the same line as other text.
At the time of this post, the [n/]
character doesn’t possess nearly as much formal documentation as other features of ChoiceScript. It isn’t, for example, mentioned in the ChoiceScript tutorial on CoG’s website. Nevertheless, the [n/]
character is a useful tool to have in your pocket.
One last thing: notice that the whitespace produced by both *line_break
and [n/]
can be highlighted, while the paragraph breaks induced by blank lines cannot. The latter is a result of the CSS margin properties of paragraph tags, while the former is an explicit whitespace character. In ChoiceScript, the convention is to use blank lines for everyday paragraph breaks, not *line_break
or [n/]
. You should not try to substitute *line_break
s or [n/]
s in place of a blank line for creating a paragraph break, even if it may seem tempting. Using blank lines for some paragraph breaks and *line_break
s for others is a bad idea, and produces visually inconsistent results.
However, while you shouldn’t use *line_break
or [n/]
to create paragraph breaks, they do have some important use cases. They are best employed in text that utilizes stanzas (e.g. poetry), or when printing the multiple lines of a mailing address.
- Testing and Results
Unfortunately, whitespace in ChoiceScript is a lot more complicated than it appears. The descriptions I gave above don’t do the underlying complexity justice.
In order to understand how ChoiceScript really induces vertical whitespace, I set up a few enormous testing suites in which I combined blank lines of code, *line_break
commands, and [n/]
characters to see how they interacted. The results completely changed how I view whitespace in ChoiceScript.
Testing suite #1 tests 7 different ways of combining blank lines of code and *line_break
commands, and doesn’t utilize any [n/]
characters.
Testing suite #1
Let's investigate various kinds of line breaks.
^^ This is an example of pressing the enter-key character to create a blank line of code. This is the normal way paragraph breaks are generated. (1)
*line_break
^^ This is an example of a single *line_break. (2)
*line_break
*line_break
^^ This is an example of a double *line_break, with each *line_break on a separate line. (3)
*line_break
^^ This is an example of a single *line_break after an empty line. (4)
*line_break
^^ This is an example of an empty line after a single *line_break. (5)
*line_break
^^ This is an example of a *line_break sandwiched between two blank lines. This is the most "generous" a naive coder can be in making sure a line break shows up properly. (6)
*line_break
*line_break
^^ This is an example of a *line_break, followed by a blank line, followed by another *line_break, followed by another blank line. (7)
Testing suite #2 performs the exact same 7 tests, but uses a [n/]
character in every place that testing suite #1 used a *line_break
command.
Testing suite #2
Now, let's do the same thing with inline linebreaks!
^^ This is an example of pressing the enter-key character to create a blank line of code. This is the normal way paragraph breaks are generated. (1)
[n/]
^^ This is an example of a single inline linebreak. (2)
[n/]
[n/]
^^ This is an example of a double inline linebreak, with each inline linebreak on a separate line. (3)
[n/]
^^ This is an example of a single inline linebreak after an empty line. (4)
[n/]
^^ This is an example of an empty line after a single inline linebreak. (5)
[n/]
^^ This is an example of an inline linebreak sandwiched between two blank lines. This is the most "generous" a naive coder can be in making sure an inline linebreak shows up properly. (6)
[n/]
[n/]
^^ This is an example of an inline linebreak, followed by a blank line, followed by another inline linebreak, followed by another blank line. (7)
The first thing to note is that the results of these two testing suites aren’t the same. Specifically, test (4) produces a different outcome in suite #1 and suite #2. Why?
The second thing to note is that having more than one blank line of code in a test only seems to produce visual changes when the new blank line is placed at the start. Is this always true? And if so, why?
The third thing to note is that within a testing suite, some tests that we’d expect to come out looking different instead look the same. In testing suite #1, for example, tests (4) and (7) are visually identical, yet the code for (4) is a blank line followed by a *line_break
:
*line_break
… while the code for (7) is this:
*line_break
*line_break
Once again: why?
- Interpreting the HTML and feeling confused
Let’s examine the HTML from testing suite #1:
… and the HTML from testing suite #2:
For those who aren’t familiar with HTML, a <p>
opens a new paragraph and a </p>
closes the current paragraph.
It looks like every single time we use a *line_break
or a [n/]
in our ChoiceScript code, a <br>
tag shows up. But they’re located in some weird places! Sometimes our <br>
tags end up inside of paragraph tags, and other times they end up outside of them. To add to the confusion, whether a <br>
tag is inside a paragraph only sometimes impacts how it visually appears. For example, test (6) produced visually identical results in both testing suite 1 and 2, but only in testing suite 2 is the <br>
tag enclosed in its own unique paragraph. Meanwhile, test (4) produces visually distinct results across testing suites, and the <br>
tag is located in a paragraph only in testing suite #2.
Fortunately, these results do make sense. There’s a few simple rules which govern how ChoiceScript handles whitespace, and they explain all the messiness of our tests.
- What’s happening at a deeper level?
I reverse-engineered ChoiceScript’s behavior over the course of several other tests (of which there were too many to post here). I’ve come up with a formalization that you can use to predict how ChoiceScript will interpret your whitespace.
For the sake of this formalization, let’s divide lines of ChoiceScript into three categories: commands, text, and blank lines. Commands are anything that starts with an asterisk (e.g. *comment
s, *goto
s, *set
s, and of course *line_break
s). Text is any line in your story that ChoiceScript will print (e.g. Today I went to the ${storeType}, and *broke into the line* [n/] to see the cashier. Behold my wordplay.
). Blank lines are any lines that solely contain spaces/tabs.
As ChoiceScript converts your code into HTML, it keeps track of a state variable for whether it’s currently inside of a paragraph block. Then, based on whether it’s currently parsing a line with a command, a line of text, or a blank line, it behaves as follows:
*if (command)
Perform the command. If the command is *line_break, that means printing a <br> tag.
*elseif (text)
First, if we're not currently in a paragraph, open a new one with a <p> tag.
Second, print the text. Any [n/] characters in the text are converted into <br> tags.
*elseif (blankline)
If we're currently in a paragraph, close it with a </p> tag.
This reveals a lot of (potentially counter-intuitive) implications for how ChoiceScript processes and produces whitespace:
First, since [n/]
is “text” and *line_break
is a “command,” [n/]
and *line_break
are not perfect substitutes. Yes, [n/]
may be an inline version of *line_break
, but there are circumstances (see test (4)) where using [n/]
on a line all to itself will produce a different result than a *line_break
in the same position. For example, this code produces a <br>
tag between paragraphs 1 and 2:
This is paragraph 1, now prepare for a line break!
*line_break
This is a paragraph 2, we just printed a line break!
… while replacing the *line_break
with a [n/]
character instead produces a <br>
tag at the start of the inside of paragraph 2:
This is paragraph 1, now prepare for a line break (character)!
[n/]
This is a paragraph 2, we just printed a line break (character)!
This produces visually distinct outcomes:
Second, blank lines don’t just mean “end our current paragraph and start a new one.” They more specifically mean “end the current paragraph (unless there is no ‘current paragraph’).” ChoiceScript only starts a new paragraph when it encounters the next line of text. As a result, multiple consecutive blank lines will have the same behavior as a single blank line. More broadly, a single blank line followed by any number of blank lines intermixed with some number N *line_break
commands, regardless of the ordering of these blank lines and *line_break
s, will have the same behavior as a single blank line followed by N *line_break
s.
Third, *line_break
s and [n/]
s don’t always produce a visible line break. For example, following a line break character with a blank line like so
First line of text.
[n/]
Second line of text.
will look like a normal paragraph break, as if the [n/]
wasn’t even there. Meanwhile, a blank line followed by a line break character
First line of text.
[n/]
Second line of text.
will stitch vertical whitespace to the top of the second paragraph. This is because, to a first approximation, a <br>
tag causes the current line to take up vertical space and moves the cursor to a new line, but doesn’t cause that new line to take up vertical space until it contains either text or another <br>
tag.
Test of the above principle
First paragraph here, followed by a normal paragraph break.
[n/]
Second paragraph here.
---
First paragraph here.
[n/]
Second paragraph here, with vertical whitespace stitched to the top.
However, if we add a second [n/]
when attempting to stitch vertical whitespace to the bottom of the first paragraph, it produces symmetric visual outcomes.
First paragraph here, with vertical whitespace stitched to the bottom.
[n/]
[n/]
Second paragraph here.
---
[n/]
Second paragraph here, with vertical whitespace stitched to the top.
“Stitching” vertical whitespace like this to the bottom of paragraph 1 is also possible with two repeated *line_break
commands. However, stitching vertical whitespace to the top of paragraph 2 requires the use of a [n/]
character. This is due to the fact, discussed earlier, that [n/]
characters count as “text” while *line_break
s count as “commands.”
One last thing to note: Thanks to the specific configuration of ChoiceScript’s CSS, repeated <br>
tags outside of any paragraph are displayed identically to repeated <br>
tags in their own little paragraph. This is why test (6) looks the same in both suites, despite each producing different HTML.
- Screenreaders
Now you know how to make all kinds of vertical whitespace using ChoiceScript! But now we have to ask: should you?
The ChoiceScript wiki claims that
I don’t have any visual disabilities, and don’t have a lot of firsthand experience with screenreaders. But for the purposes of this post, I opened Microsoft Narrator (a built-in screenreader for Windows devices) and experimented with it.
Here’s how it works: there’s a keyboard combination that prompts Microsoft Narrator to read the next chunk of text aloud to the user. After pressing it, you listen for a brief period. Then, once Narrator finishes reading the current chunk of text, you press the keyboard combination again to proceed to the next chunk of text.
Using multiple *line_break
s in a row didn’t “break” Narrator. It did, however, pose an inconvenience. Regardless of whether my <br>
tags were located in or outside of paragraph blocks, regardless of whether they were used in immediate succession, regardless of anything, they would always be treated as their own chunk of text to read aloud.
Microsoft Narrator reads the <br>
tags as if they were the phrase “space, separator.” That means the code
This is a paragraph.
*line_break
This is also a paragraph.
produces the following experience for the user:
- Narrator says “This is a paragraph.”
- User prompts Narrator for more.
- Narrator says “space, separator.”
- User prompts Narrator for more.
- Narrator says “This is also a paragraph.”
… And adding more *line_break
s makes this even more annoying. The code
This is a paragraph.
*line_break
*line_break
*line_break
This is also a paragraph.
produces the following horrible experience:
- Narrator says “This is a paragraph.”
- User prompts Narrator for more.
- Narrator says “space, separator.”
- User prompts Narrator for more.
- Narrator says “space, separator.”
- User prompts Narrator for more.
- Narrator says “space, separator.”
- User prompts Narrator for more.
- Narrator says “This is also a paragraph.”
To make this worse, Microsoft Narrator says the phrase “space, separator” very slowly. You can skip to the next chunk of text early, of course–but I imagine that dealing with these <br>
tags when you’re trying to read a story is kind of an immersion killer.
By contrast, a normal paragraph break created via the code
This is a paragraph.
This is also a paragraph.
is a vastly less agonizing experience:
- Narrator says “This is a paragraph.”
- User prompts Narrator for more.
- Narrator says “This is also a paragraph.”
For this reason alone, I’d recommend keeping your *line_break
s and [n/]
characters to an absolute minimum. Ideally, in order to avoid them breaking the flow of your story, they should only ever be used in places where the reader isn’t consuming narrative-related text, like the stat screen.
(As mentioned earlier, if you must use them in a narrative-related part of your text, they are best restricted to printing poetry or mailing addresses with multiple lines.)
I don’t know whether screenreaders other than Microsoft Narrator operate differently. And I especially don’t know why the ChoiceScript wiki says that multiple *line_break
s in particular will “break screenreaders,” since (1) there’s more than one screenreader on the market and multiple *line_break
s might only break some of them, and (2) my experience was that multiple consecutive *line_break
s didn’t “break” Microsoft Narrator so much as “make it very frustrating to use.”
I’ve heard some talk around the forum that this might have been an old issue that has since been patched, but I don’t know for sure. Therefore, I want check in with the visually-impaired members of the CoG community to find out if avoiding multiple *line_break
s is still a guideline worth following, and edit the ChoiceScript wiki accordingly!
- If you use a screenreader, what screenreader do you use?
- Do multiple consecutive
*line_break
commands break your screenreader? If so, how exactly does it break? - Do
*line_break
commands affect your reading experience in other ways, positive or negative?
Hope this post can generate some positive discussion about these issues!