5: Streams

All character output in Glk is done through streams. Every window has an output stream associated with it. You can also write to files on disk; every open file is represented by an output stream as well.

There are also input streams; these are used for reading from files on disk. It is possible for a stream to be both an input and an output stream. [Player input is done through line and character input events, not streams. This is a small inelegance in theory. In practice, player input is slow and things can interrupt it, whereas file input is immediate. If a network extension to Glk were proposed, it would probably use events and not streams, since network communication is not immediate.]

It is also possible to create a stream that reads or writes to a buffer in memory.

Finally, there may be platform-specific types of streams, which are created before your program starts running. [For example, a program running under Unix may have access to standard input as a stream, even though there is no Glk call to explicitly open standard input. On the Mac, data in a Mac resource may be available through a resource-reading stream.] You do not need to worry about the origin of such streams; just read or write them as usual. For information about how platform-specific streams come to be, see section 11.1, "Startup Options".

A stream is opened with a particular file mode:

[In the stdio library, using fopen(), filemode_Write would be mode "w"; filemode_Read would be mode "r"; filemode_ReadWrite would be mode "r+". Confusingly, filemode_WriteAppend cannot be mode "a", because the stdio spec says that when you open a file with mode "a", then fseek() doesn't work. So we have to use mode "r+" for appending. Then we run into the other stdio problem, which is that "r+" never creates a new file. So filemode_WriteAppend has to first open the file with "a", close it, reopen with "r+", and then fseek() to the end of the file. For filemode_ReadWrite, the process is the same, except without the fseek() -- we begin at the beginning of the file.]

[We must also obey an obscure geas of ANSI C "r+" files: you can't switch from reading to writing without doing an fseek() in between. Switching from writing to reading has the same restriction, except that an fflush() also works.]

For information on opening streams, see the discussion of each specific type of stream in section 5.6, "The Types of Streams". Remember that it is always possible that opening a stream will fail, in which case the creation function will return NULL.

Each stream remembers two character counts, the number of characters printed to and read from that stream. The write-count is exactly one per glk_put_char() call; it is figured before any platform-dependent character cookery. [For example, if a newline character is converted to linefeed-plus-carriage-return, the stream's count still only goes up by one; similarly if an accented character is displayed as two characters.] The read-count is exactly one per glk_get_char_stream() call, as long as the call returns an actual character (as opposed to an end-of-file token.)

Glk has a notion of the "current (output) stream". If you print text without specifying a stream, it goes to the current output stream. The current output stream may be NULL, meaning that there isn't one. It is illegal to print text to stream NULL, or to print to the current stream when there isn't one.

If the stream which is the current stream is closed, the current stream becomes NULL.

void glk_stream_set_current(strid_t str);

This sets the current stream to str, which must be an output stream. You may set the current stream to NULL, which means the current stream is not set to anything.

strid_t glk_stream_get_current(void);

Returns the current stream, or NULL if there is none.

5.1: How To Print

void glk_put_char(unsigned char ch);

This prints one character to the current stream. As with all basic functions, the character is assumed to be in the Latin-1 character encoding. See section 2, "Character Encoding".

void glk_put_string(char *s);

This prints a null-terminated string to the current stream. It is exactly equivalent to

for (ptr = s; *ptr; ptr++)
    glk_put_char(*ptr);

However, it may be more efficient.

void glk_put_buffer(char *buf, glui32 len);

This prints a block of characters to the current stream. It is exactly equivalent to

for (i = 0; i < len; i++)
    glk_put_char(buf[i]);

However, it may be more efficient.

void glk_put_char_stream(strid_t str, unsigned char ch);
void glk_put_string_stream(strid_t str, char *s);
void glk_put_buffer_stream(strid_t str, char *buf, glui32 len);

These are the same functions, except that you specify a stream to print to, instead of using the current stream. Again, it is illegal for str to be NULL, or the reference of an input-only stream.

void glk_put_char_uni(glui32 ch);

This prints one character to the current stream. The character is assumed to be a Unicode code point. See section 2, "Character Encoding".

void glk_put_string_uni(glui32 *s);

This prints a string of Unicode characters to the current stream. It is equivalent to a series of glk_put_char_uni() calls. A string ends on a glui32 whose value is 0.

void glk_put_buffer_uni(glui32 *buf, glui32 len);

This prints a block of Unicode characters to the current stream. It is equivalent to a series of glk_put_char_uni() calls.

void glk_put_char_stream_uni(strid_t str, glui32 ch);
void glk_put_string_stream_uni(strid_t str, glui32 *s);
void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len);

5.2: How To Read

glsi32 glk_get_char_stream(strid_t str);

This reads one character from the given stream. (There is no notion of a "current input stream.") It is illegal for str to be NULL, or an output-only stream.

The result will be between 0 and 255. As with all basic text functions, Glk assumes the Latin-1 encoding. See section 2, "Character Encoding". If the end of the stream has been reached, the result will be -1. [Note that high-bit characters (128..255) are not returned as negative numbers.]

If the stream contains Unicode data -- for example, if it was created with glk_stream_open_file_uni() or glk_stream_open_memory_uni() -- then characters beyond 255 will be returned as 0x3F ("?").

glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len);

This reads len characters from the given stream, unless the end of stream is reached first. No terminal null is placed in the buffer. It returns the number of characters actually read.

glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len);

This reads characters from the given stream, until either len-1 characters have been read or a newline has been read. It then puts a terminal null ('\0') character on the end. It returns the number of characters actually read, including the newline (if there is one) but not including the terminal null.

It is usually more efficient to read several characters at once with glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling glk_get_char_stream() several times.

glsi32 glk_get_char_stream_uni(strid_t str);

Reads one character from the given stream. If the end of the stream has been reached, the result will be -1.

glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len);

This reads len Unicode characters from the given stream, unless the end of the stream is reached first. No terminal null is placed in the buffer. It returns the number of Unicode characters actually read.

glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len);

This reads Unicode characters from the given stream, until either len-1 Unicode characters have been read or a newline has been read. It then puts a terminal null (a zero value) on the end. It returns the number of Unicode characters actually read, including the newline (if there is one) but not including the terminal null.

5.3: Closing Streams

void glk_stream_close(strid_t str, stream_result_t *result);

typedef struct stream_result_struct {
    glui32 readcount;
    glui32 writecount;
} stream_result_t;

This closes the stream str. The result argument points to a structure which is filled in with the final character counts of the stream. If you do not care about these, you may pass NULL as the result argument.

If str is the current output stream, the current output stream is set to NULL.

You cannot close window streams; use glk_window_close() instead. See section 3.2, "Window Opening, Closing, and Constraints".

5.4: Stream Positions

You can set the position of the read/write mark in a stream. [Which makes one wonder why they're called "streams" in the first place. Oh well.]

glui32 glk_stream_get_position(strid_t str);

This returns the position of the mark. For memory streams and binary file streams, this is exactly the number of characters read or written from the beginning of the stream (unless you have moved the mark with glk_stream_set_position().) For text file streams, matters are more ambiguous, since (for example) writing one byte to a text file may store more than one character in the platform's native encoding. You can only be sure that the position increases as you read or write to the file.

Additional complication: for Latin-1 memory and file streams, a character is a byte. For Unicode memory and file streams (those created by glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is a 32-bit word. So in a binary Unicode file, positions are multiples of four bytes.

[If this bothers you, don't use binary Unicode files. I don't think they're good for much anyhow.]

void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode);

This sets the position of the mark. The position is controlled by pos, and the meaning of pos is controlled by seekmode:

It is illegal to specify a position before the beginning or after the end of the file.

In binary files, the mark position is exact -- it corresponds with the number of characters you have read or written. In text files, this mapping can vary, because of linefeed conversions or other character-set approximations. (See section 5, "Streams".) glk_stream_set_position() and glk_stream_get_position() measure positions in the platform's native encoding -- after character cookery. Therefore, in a text stream, it is safest to use glk_stream_set_position() only to move to the beginning or end of a file, or to a position determined by glk_stream_get_position().

Again, in Latin-1 streams, characters are bytes. In Unicode streams, characters are 32-bit words, or four bytes each.

A window stream doesn't have a movable mark, so calling glk_stream_set_position() has no effect. glk_stream_get_position() on a window stream will always return zero. [It might make more sense to return the number of characters written to the window, but existing libraries do not support this and it's not really worth adding the feature.]

5.5: Styles

You can send style-changing commands to an output stream. After a style change, new text which is printed to that stream will be given the new style, whatever that means for the stream in question. For a window stream, the text will appear in that style. For a memory stream, style changes have no effect. For a file stream, if the machine supports styled text files, the styles may be written to the file; more likely the style changes will have no effect.

Styles are exclusive. A character is shown with exactly one style, not a subset of the possible styles.

[Note that every stream and window has its own idea of the "current style." Sending a style command to one window or stream does not affect any others.] [Except for a window's echo stream; see section 3.6, "Echo Streams".]

The styles are intended to distinguish meaning and use, not formatting. There is no standard definition of what each style will look like. That is left up to the Glk library, which will choose an appearance appropriate for the platform's interface and the player's preferences.

There are currently eleven styles defined. More may be defined in the future.

Styles may be distinguished on screen by font, size, color, indentation, justification, and other attributes. Note that some attributes (notably justification and indentation) apply to entire paragraphs. If possible and relevant, you should apply a style to an entire paragraph -- call glk_set_style() immediately after printing the newline at the beginning of the text, and do the same at the end.

[For example, style_Header may well be centered text. If you print "Welcome to Victim (a short interactive mystery)", and only the word "Victim" is in the style_Header, the center-justification attribute will be lost. Similarly, a block quote is usually indented on both sides, but indentation is only meaningful when applied to an entire line or paragraph, so block quotes should take up an entire paragraph. Contrariwise, style_Emphasized need not be used on an entire paragraph. It is often used for single emphasized words in normal text, so you can expect that it will appear properly that way; it will be displayed in italics or underlining, not center-justified or indented.]

[Yes, this is all a matter of mutual agreement between game authors and game players. It's not fixed by this specification. That's natural language for you.]

void glk_set_style(glui32 val);

This changes the style of the current output stream. val should be one of the values listed above. However, any value is actually legal; if the interpreter does not recognize the style value, it will treat it as style_Normal. [This policy allows for the future definition of styles without breaking old Glk libraries.]

void glk_set_style_stream(strid_t str, glui32 val);

This changes the style of the stream str.

5.5.1: Suggesting the Appearance of Styles

There are no guarantees of how styles will look, but you can make suggestions.

void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, glsi32 val);
void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint);

These functions set and clear hints about the appearance of one style for a particular type of window. You can also set wintype to wintype_AllTypes, which sets (or clears) a hint for all types of window. [There is no equivalent constant to set a hint for all styles of a single window type.]

Initially, no hints are set for any window type or style. Note that having no hint set is not the same as setting a hint with value 0.

These functions do not affect existing windows. They affect the windows which you create subsequently. If you want to set hints for all your game windows, call glk_stylehint_set() before you start creating windows. If you want different hints for different windows, change the hints before creating each window.

[This policy makes life easier for the interpreter. It knows everything about a particular window's appearance when the window is created, and it doesn't have to change it while the window exists.]

Hints are hints. The interpreter may ignore them, or give the player a choice about whether to accept them. Also, it is never necessary to set hints. You don't have to suggest that style_Preformatted be fixed-width, or style_Emphasized be boldface or italic; they will have appropriate defaults. Hints are for situations when you want to change the appearance of a style from what it would ordinarily be. The most common case when this is appropriate is for the styles style_User1 and style_User2.

There are currently nine style hints defined. More may be defined in the future.

Again, when passing a style hint to a Glk function, any value is actually legal. If the interpreter does not recognize the stylehint value, it will ignore it. [This policy allows for the future definition of style hints without breaking old Glk libraries.]

5.5.2: Testing the Appearance of Styles

You can suggest the appearance of a window's style before the window is created; after the window is created, you can test the style's actual appearance. These functions do not test the style hints; they test the attribute of the style as it appears to the player.

Note that although you cannot change the appearance of a window's styles after the window is created, the library can. A platform may support dynamic preferences, which allow the player to change text formatting while your program is running. [Changes that affect window size (such as font size changes) will be signalled by an evtype_Arrange event. However, more subtle changes (such as text color differences) are not signalled. If you test the appearance of styles at the beginning of your program, you must keep in mind the possibility that the player will change them later.]

glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2);

This returns TRUE (1) if the two styles are visually distinguishable in the given window. If they are not, it returns FALSE (0). The exact meaning of this is left to the library to determine.

glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, glui32 *result);

This tries to test an attribute of one style in the given window. The library may not be able to determine the attribute; if not, this returns FALSE (0). If it can, it returns TRUE (1) and stores the value in the location pointed at by result. [As usual, it is legal for result to be NULL, although fairly pointless.]

The meaning of the value depends on the hint which was tested:

Signed values, such as the stylehint_Weight value, are cast to glui32. They may be cast to glsi32 to be dealt with in a more natural context.

5.6: The Types of Streams

5.6.1: Window Streams

Every window has an output stream associated with it. This is created automatically, with filemode_Write, when you open the window. You get it with glk_window_get_stream(). Window streams always have rock value 0.

A window stream cannot be closed with glk_stream_close(). It is closed automatically when you close its window with glk_window_close().

Only printable characters (including newline) may be printed to a window stream. See section 2, "Character Encoding".

5.6.2: Memory Streams

You can open a stream which reads from or writes into a space in memory.

strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock);

fmode must be filemode_Read, filemode_Write, or filemode_ReadWrite.

buf points to the buffer where output will be read from or written to. buflen is the length of the buffer.

When outputting, if more than buflen characters are written to the stream, all of them beyond the buffer length will be thrown away, so as not to overwrite the buffer. (The character count of the stream will still be maintained correctly. That is, it will count the number of characters written into the stream, not the number that fit in the buffer.)

If buf is NULL, or for that matter if buflen is zero, then everything written to the stream is thrown away. This may be useful if you are interested in the character count.

When inputting, if more than buflen characters are read from the stream, the stream will start returning -1 (signalling end-of-file.) If buf is NULL, the stream will always return end-of-file.

The data is written to the buffer exactly as it was passed to the printing functions (glk_put_char(), etc); input functions will read the data exactly as it exists in memory. No platform-dependent cookery will be done on it. [You can write a disk file in text mode, but a memory stream is effectively always in binary mode.]

Unicode values (characters greater than 255) cannot be written to the buffer. If you try, they will be stored as 0x3F ("?") characters.

Whether reading or writing, the contents of the buffer are undefined until the stream is closed. The library may store the data there as it is written, or deposit it all in a lump when the stream is closed. It is illegal to change the contents of the buffer while the stream is open.

strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock);

This works just like glk_stream_open_memory(), except that the buffer is an array of 32-bit words, instead of bytes. This allows you to write and read any Unicode character. The buflen is the number of words, not the number of bytes.

[If the buffer contains the value 0xFFFFFFFF, and is opened for reading, the reader cannot distinguish that value from -1 (end-of-file). Fortunately 0xFFFFFFFF is not a valid Unicode character.]

5.6.3: File Streams

You can open a stream which reads from or writes to a disk file.

strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock);

fileref indicates the file which will be opened. fmode can be any of filemode_Read, filemode_Write, filemode_WriteAppend, or filemode_ReadWrite. If fmode is filemode_Read, the file must already exist; for the other modes, an empty file is created if none exists. If fmode is filemode_Write, and the file already exists, it is truncated down to zero length (an empty file); the other modes do not truncate. If fmode is filemode_WriteAppend, the file mark is set to the end of the file.

[Note, again, that this doesn't match stdio's fopen() call very well. See section 5, "Streams"]

If the filemode requires the file to exist, but the file does not exist, glk_stream_open_file() returns NULL.

The file may be read or written in text or binary mode; this is determined by the fileref argument. Similarly, platform-dependent attributes such as file type are determined by fileref. See section 6, "File References".

When writing in binary mode, byte values are written directly to the file. (Writing calls such as glk_put_char_stream() are defined in terms of Latin-1 characters, so the binary file can be presumed to use Latin-1. Newlines will remain as 0x0A bytes.) Unicode values (characters greater than 255) cannot be written to the file. If you try, they will be stored as 0x3F ("?") characters.

When writing in text mode, character data is written in an encoding appropriate to the platform; this may be Latin-1 or some other format. Newlines may be converted to other line break sequences. Unicode values may be stored exactly, approximated, or abbreviated, depending on what the platform's text files support.

strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock);

This works just like glk_stream_open_file(), except that in binary mode, characters are written and read as four-byte (big-endian) values. This allows you to write and read any Unicode character.

In text mode, the file is written and read using the UTF-8 Unicode encoding. Files should be written without a byte-ordering mark. This ensures that text-mode files created by glk_stream_open_file() and glk_stream_open_file_uni() will be identical if only ASCII characters (32-127) are written.

[Previous versions of this spec said, of glk_stream_open_file_uni(): "In text mode, the file is written and read in a platform-dependent way, which may or may not handle all Unicode characters." This left open the possibility of other native text-file formats, as well as richer formats such as RTF or HTML. Richer formats do not seem to have ever been used; and at this point, UTF-8 is widespread enough for us to mandate it.]

To summarize:

However, if the fileref was created via a prompt (glk_fileref_create_by_prompt), the player may have selected format options that override these rules.

[See also the comments about text and binary mode, section 6, "File References".]

5.6.4: Resource Streams

You can open a stream which reads from (but not writes to) a resource file.

[Typically this is embedded in a Blorb file, as Blorb is the official resource-storage format of Glk. A Blorb file can contain images and sounds, but it can also contain raw data files, which are accessed by the following functions. A data file is identified by number, not by a filename. The Blorb usage field will be 'Data'. The chunk type will be 'TEXT' for text resources, 'BINA' or 'FORM' for binary resources.]

[For a 'FORM' Blorb chunk, the stream should start reading at the beginning of the chunk header -- that is, it should read the 'FORM' and length words before the chunk content. For 'TEXT' and 'BINA' chunks, the stream should skip the header and begin with the chunk content. This distinction is important when embedding AIFF sounds or Quetzal saved games, for example.]

[Note that this FORM distinction was added to the Glk 0.7.4 spec in July 2012, several months after the spec went out. This is bad form, no pun intended, but I don't think it'll cause headaches. No games use the resource stream feature yet, as far as I know. A Glk library written in the interregnum of early 2012 will fail to recognize FORM chunks, and if a game tries to use one, glk_stream_open_resource will return NULL.]

[If the running program is not associated with a Blorb file, the library may look for data files as actual files instead. These would be named "DATA1", "DATA2", etc, with a suffix distinguishing text and binary files. See "Other Resource Arrangements" in the Blorb spec: http://eblong.com/zarf/blorb/. The stream should always begin at the beginning of the file, in this case; there is no BINA/FORM distinction to worry about.]

strid_t glk_stream_open_resource(glui32 filenum, glui32 rock);
strid_t glk_stream_open_resource_uni(glui32 filenum, glui32 rock);

Open the given data resource for reading (only), as a normal or Unicode stream. [Note that there is no notion of file usage -- the resource does not have to be specified as "saved game" or whatever.]

If no resource chunk of the given number exists, the open function returns NULL.

As with file streams, a binary resource stream reads the resource as bytes (for a normal stream) or as four-byte (big-endian) words (for a Unicode stream). A text resource stream reads characters encoded as Latin-1 (for normal) or UTF-8 (for Unicode). [Thus, the difference between text and binary resources is only important when opened as a Unicode stream.]

When reading from a resource stream, newlines are not remapped, even if they normally would be when reading from a text file on the host OS. If you read a line (glk_get_line_stream or glk_get_line_stream_uni), a Unix newline (0x0A) terminates the line.

res = glk_gestalt(gestalt_ResourceStream, 0);

This returns 1 if the glk_stream_open_resource() and glk_stream_open_resource_uni() functions are available. If it returns 0, you should not call them.

5.7: Other Stream Functions

strid_t glk_stream_iterate(strid_t str, glui32 *rockptr);

This iterates through all the existing streams. See section 1.6.2, "Iterating Through Opaque Objects".

glui32 glk_stream_get_rock(strid_t str);

This retrieves the stream's rock value. See section 1.6.1, "Rocks". Window streams always have rock 0; all other streams return whatever rock you created them with.


Up to top Previous chapter Next chapter