[I6] Word wrapping of string in source code

Maybe you are not sensitive to contemporary art; this can’t be wrong since my graphic is beautiful.

I’m trying to figure out exactly where newlines are printed.

I’ve been able to determine that Frotz in fact does not have this mechanism. My trouble now is figuring out how to get a debugger to stop right when a line break is about to be made so I can see what’s going on.

To be more verbose, I think the pseudocode for the fix should go like this when a line break is being decided:

if (at_end_of_line && this_char == ' ' && next_char == '\n') {
        delete_next_char();
}

@fredrik, could you explain in detail how Ozmoo makes sure this spurious newline doesn’t appear?

We wrote our own screen handling routines, to get full control of the screen. We call this the screen kernal. This was mainly to make sure the statusline doesn’t scroll off the screen, and to control printing in the last column.

If the screen kernal is ordered to print a regular character in the last column, it prints the character and sets a flag called s_ignore_next_linebreak.

Whenever the screen kernal gets a character to print, it it’s a newline and s_ignore_next_linebreak is set, it prints nothing. Regardless of what the character was, the flag is then cleared.

This is all internal to the screen kernal. The text routines, which handle buffered output etc, don’t need to care. We print to a 40 column screen, but it will work just as well whether the buffered output routine was set to limit everything to 35 columns or 40 columns.

This is the behavior of curses’ addch (curs_addch 3x 2023-12-23 ncurses 6.4 Library calls):

If the advance is at the right margin:
o The cursor automatically wraps to the beginning of the next line.

In my version of jzip I use addchnstr/add_wchnstr when I detect the cursor is about to go out of the screen, but it is getting a bit messy: https://github.com/borg323/jzip/blob/master/cursesio.c#L132-L150

Why the: && this_char == ' ' ?

WIDTH 8
String "abc defg^hi"

 0 1 2 3 4 5 6 7  8
|a b c   d e f g| ^ h i

if (at_7 && (next_char == ' ' || next_char == '^')) {
        delete_next_char();
}

That can’t be everything. Dumb Frotz shows the same behavior:

$ ./dfrotz -w 40 wrap.z5 
Using normal formatting.
Loading wrap.z5.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c

d e f

My idea is that we check to see if there’s a space followed by a newline and a line-wrapping newline is to be done. If so, then delete one of the newlines.

Consider the following variation, which I think shows the same problem:

[ main key;
  @split_window 10;
  @set_window 1;
  print "12345678901234567890123456789012345678901234567890";
  print "12345678901234567890123456789012345678901234567890^";
  @set_cursor 3 1;
  print "12345678901234567890123456789012345678901234567890";
  print "12345678901234567890123456789012345678901234567890^";
  print "XYZZY";
  @read_char 1 ->key;
];

I get

12345678901234567890123456789012345678901234567890123456789012345678901234567890
2468024680
12345678901234567890123456789012345678901234567890123456789012345678901234567890
XYZZY24680

but I was expecting

12345678901234567890123456789012345678901234567890123456789012345678901234567890

12345678901234567890123456789012345678901234567890123456789012345678901234567890
XYZZY

Note that on the second (and fourth) line, only every second character is printed. I think this is a combination of the automatic wrapping by the terminal and frotz’s wrapping code.

I think I have something of a solution, which I’ve committed to the spurious_newline branch at https://gitlab.com/DavidGriffith/frotz/-/tree/spurious_newline.

diff --git a/src/common/screen.c b/src/common/screen.c
index 604918d..10631bc 100644
--- a/src/common/screen.c
+++ b/src/common/screen.c
@@ -19,6 +19,7 @@
  */
 
 #include "frotz.h"
+#include <string.h>
 
 extern void set_header_extension(int, zword);
 
@@ -331,6 +332,8 @@ void screen_word(const zchar * s)
 {
 	int width;
 
+	bool last_char_space = FALSE;
+
 	if (discarding)
 		return;
 
@@ -338,6 +341,9 @@ void screen_word(const zchar * s)
 		screen_char(*s++);
 
 	if (units_left() < (width = os_string_width(s))) {
+		if (strrchr((const char *) s, ' ') != NULL)
+			last_char_space = TRUE;
+
 		if (!enable_wrapping) {
 			zchar c;
 			while ((c = *s++) != 0) {
@@ -359,8 +365,8 @@ void screen_word(const zchar * s)
 		if (cwin == 0)
 			Justifiable();
 #endif
-
-		screen_new_line();
+		if (!last_char_space)
+			screen_new_line();
 	}
 	os_display_string(s);
 	cwp->x_cursor += width;

It looks more to me that this is the result of faulty handling of @split_window. Looking into it now.

So far, I can tell that it goes at least as far back as 2.32. Earlier than that, nobody seems to know where the sources are.

It’s generally normal for any fixed-width terminal that printing any character (including a space) in the last character cell on the line will move the cursor to the following line. If the immediately next character is a newline then that will leave a blank line. That’s not a bug, that should be entirely expected.

It could be seen as “nice to have” if a terp is a little smarter and instead allows the cursor position to go “off screen” on the right after printing a character in the final cell (and perhaps also when only subsequently printing spaces), but that’s a rarity rather than an expectation.

(Note that the Glk specification assumes that you are not doing that; it specifies simple cursor wrapping as above in text grid windows, and either the same or proper word wrapping in text buffer windows. The Z-machine spec is more vague, but seems in general to agree.)

A well-behaved story should simply avoid printing anything in the last character cell of a fixed-width row. (The only assurance made in the specs is that printing in the last character of the last row will not cause the status window / another text-grid window to scroll – but also notes that some older interpreters didn’t implement that, so a well-behaved story should still avoid doing it.)

(Having said that, the part where it starts to print 2468024680 might be an actual bug.)

@mirality, are you referring solely to @borg323’s new test program that ends with XYZZY? That one runs correctly under jzip.

I think my solution works for the first and third example programs. The second (by @borg323 ending with XYZZY) so far appears to be unrelated. @auraes is having problems with getting it to work with these two test programs, but I cannot replicate his trouble. Could I get some more people to test this fix?

I have no more the issue of the extra line break for the 2nd print, but the 3rd print no longer has a line break:

[ main;
   print "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c^d e f";
   new_line;
   print "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c
   ^d e f";
   new_line;
   print "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c d e f g h i j";
   new_line;
];

Frotz without fixing: frotz -w 40 wrap.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c

d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f g h i j

Frotz with fixing: frotz -w 40 wrap.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b cdefghij

Can you give me more details about your system? OS, distribution, version, etc? I’m running Debian 10 on a 64-bit Intel processor (amd64 arch). All libraries Frotz needs are installed through APT.

Here’s what I get when running this test:

frotz wrap4.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c d e f g h i j
[Hit any key to exit.]

frotz -w 40 wrap4.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c

d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f g h i j
[Hit any key to exit.]

frotz -w 39 wrap4.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b
c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a
c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b
c d e f g h i j
[Hit any key to exit.]

frotz -w 38 wrap4.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b
c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a
c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b
c d e f g h i j
[Hit any key to exit.]

Lubuntu 20.04.1 LTS x86_64 Intel Haswell (Pentium), QTerminal 0.14.1.

I get the same as you everywhere, but with the spurious_newline fix, I get this:
frotz -w 40 wrap4.z5

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a c
d e f
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b cdefghij

I tried QTerminal and still see no trouble. Next I’ll install Lubuntu 20.04.1 in a VM and try again.

Does anyone else have anything to report?

All my material: wrap.zip (461.7 KB)

The fix has been tested, refined, verified, and pushed to the master branch. Thanks everybody!

3 Likes