9 #include <glib/gstdio.h>
10 #include <glib/gi18n-lib.h>
12 /* Internal function: ensure that an fseek() is called on a file pointer in
13 between reading and writing operations, and vice versa. This will only come up
14 for ReadWrite or WriteAppend files. */
16 ensure_file_operation(strid_t str, glui32 op)
18 if(str->lastop != 0 && str->lastop != op)
20 long pos = ftell(str->file_pointer);
22 WARNING_S("ftell() failed", g_strerror(errno));
23 if(fseek(str->file_pointer, pos, SEEK_SET) != 0)
24 WARNING_S("fseek() failed", g_strerror(errno));
26 str->lastop = op; /* Not 0, because we are about to do the operation anyway */
31 **************** WRITING FUNCTIONS ********************************************
35 /* Internal function: write a UTF-8 string to a text buffer window's text buffer. */
37 write_utf8_to_window_buffer(winid_t win, gchar *s)
39 if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
41 ILLEGAL("Tried to print to a text buffer window with line input pending.");
45 // Write to the buffer
46 g_string_append(win->buffer, s);
49 /* Internal function: flush a window's text buffer to the screen. */
51 flush_window_buffer(winid_t win)
54 g_printf("%s\n", win->buffer->str);
56 if(win->type != wintype_TextBuffer && win->type != wintype_TextGrid)
59 if(win->buffer->len == 0)
64 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
67 case wintype_TextBuffer:
69 GtkTextIter start, end;
70 gtk_text_buffer_get_end_iter(buffer, &end);
73 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
75 GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default");
76 GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style);
77 GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->glk_style);
79 start_offset = gtk_text_iter_get_offset(&end);
80 gtk_text_buffer_insert(buffer, &end, win->buffer->str, -1);
81 gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
83 // Player's style overrides
84 gtk_text_buffer_apply_tag(buffer, style_tag, &start, &end);
86 // GLK Program's style overrides
87 gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &end);
90 gtk_text_buffer_apply_tag(buffer, default_tag, &start, &end);
92 // Link style overrides
93 if(win->window_stream->hyperlink_mode) {
94 GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink");
95 GtkTextTag *link_tag = win->current_hyperlink->tag;
96 gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &end);
97 gtk_text_buffer_apply_tag(buffer, link_tag, &start, &end);
100 // GLK Program's style overrides using garglk_set_zcolors()
101 if(win->zcolor != NULL) {
102 gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &end);
105 // GLK Program's style overrides using garglk_set_reversevideo()
106 if(win->zcolor_reversed != NULL) {
107 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &end);
110 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
112 g_signal_emit_by_name(glk, "text-buffer-output", win->rock, win->buffer->str);
116 case wintype_TextGrid:
118 /* Number of characters to insert */
119 glong length = win->buffer->len;
120 glong chars_left = length;
122 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
124 /* Get cursor position */
125 GtkTextIter start, insert;
128 gtk_text_buffer_get_iter_at_mark(buffer, &insert, cursor);
129 /* Spaces available on this line */
130 gint available_space = win->width - gtk_text_iter_get_line_offset(&insert);
132 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
134 GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default");
135 GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style);
136 GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->glk_style);
137 GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink");
139 while(chars_left > available_space && !gtk_text_iter_is_end(&insert))
141 GtkTextIter end = insert;
142 gtk_text_iter_forward_to_line_end(&end);
143 gtk_text_buffer_delete(buffer, &insert, &end);
145 start_offset = gtk_text_iter_get_offset(&insert);
146 gtk_text_buffer_insert(buffer, &insert, win->buffer->str + (length - chars_left), available_space);
147 gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
150 gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
152 // Player's style overrides
153 gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
155 // GLK Program's style overrides
156 gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
158 // Link style overrides
159 if(win->window_stream->hyperlink_mode) {
160 GtkTextTag *link_tag = win->current_hyperlink->tag;
161 gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &insert);
162 gtk_text_buffer_apply_tag(buffer, link_tag, &start, &insert);
165 // GLK Program's style overrides using garglk_set_zcolors()
166 if(win->zcolor != NULL)
167 gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &insert);
169 // GLK Program's style overrides using garglk_set_reversevideo()
170 if(win->zcolor_reversed != NULL) {
171 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &insert);
174 chars_left -= available_space;
175 gtk_text_iter_forward_line(&insert);
176 available_space = win->width;
178 if(!gtk_text_iter_is_end(&insert))
180 GtkTextIter end = insert;
181 gtk_text_iter_forward_chars(&end, chars_left);
182 gtk_text_buffer_delete(buffer, &insert, &end);
184 start_offset = gtk_text_iter_get_offset(&insert);
185 gtk_text_buffer_insert(buffer, &insert, win->buffer->str + (length - chars_left), -1);
186 gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
189 gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
191 // Player's style overrides
192 gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
194 // GLK Program's style overrides
195 gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
197 // Link style overrides
198 if(win->window_stream->hyperlink_mode) {
199 GtkTextTag *link_tag = win->current_hyperlink->tag;
200 gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &insert);
201 gtk_text_buffer_apply_tag(buffer, link_tag, &start, &insert);
204 // GLK Program's style overrides using garglk_set_zcolors()
205 if(win->zcolor != NULL)
206 gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &insert);
208 // GLK Program's style overrides using garglk_set_reversevideo()
209 if(win->zcolor_reversed != NULL) {
210 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &insert);
214 gtk_text_buffer_move_mark(buffer, cursor, &start);
221 g_string_truncate(win->buffer, 0);
224 /* Internal function: write a Latin-1 buffer with length to a stream. */
226 write_buffer_to_stream(strid_t str, gchar *buf, glui32 len)
230 case STREAM_TYPE_WINDOW:
231 /* Each window type has a different way of printing to it */
232 switch(str->window->type)
234 /* Printing to these windows' streams does nothing */
237 case wintype_Graphics:
238 str->write_count += len;
241 /* Text grid/buffer windows */
242 case wintype_TextGrid:
244 gchar *utf8 = convert_latin1_to_utf8(buf, len);
246 /* Deal with newlines */
249 for(i=0; i<len; i++) {
250 if(utf8[i] == '\n') {
252 write_utf8_to_window_buffer(str->window, line);
253 flush_window_buffer(str->window);
255 /* Move cursor position forward to the next line */
257 GtkTextIter cursor_pos;
258 GtkTextView *textview = GTK_TEXT_VIEW(str->window->widget);
259 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
260 GtkTextMark *cursor_mark = gtk_text_buffer_get_mark(buffer, "cursor_position");
262 gtk_text_buffer_get_iter_at_mark( buffer, &cursor_pos, cursor_mark);
263 gtk_text_view_forward_display_line(textview, &cursor_pos);
264 gtk_text_view_backward_display_line_start(textview, &cursor_pos);
265 gtk_text_buffer_move_mark(buffer, cursor_mark, &cursor_pos);
268 line = utf8 + (i < len-1 ? (i+1):(len-1));
272 /* No more newlines left. */
273 write_utf8_to_window_buffer(str->window, line);
277 str->write_count += len;
281 case wintype_TextBuffer:
283 gchar *utf8 = convert_latin1_to_utf8(buf, len);
285 write_utf8_to_window_buffer(str->window, utf8);
289 str->write_count += len;
292 ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
295 /* Now write the same buffer to the window's echo stream */
296 if(str->window->echo_stream != NULL)
297 write_buffer_to_stream(str->window->echo_stream, buf, len);
301 case STREAM_TYPE_MEMORY:
302 if(str->unicode && str->ubuffer)
305 while(str->mark < str->buflen && foo < len)
306 str->ubuffer[str->mark++] = (unsigned char)buf[foo++];
308 if(!str->unicode && str->buffer)
310 int copycount = MIN(len, str->buflen - str->mark);
311 memmove(str->buffer + str->mark, buf, copycount);
312 str->mark += copycount;
315 /* Move the EOF marker if we wrote past it */
316 if(str->mark > str->endmark)
317 str->endmark = str->mark;
319 str->write_count += len;
322 case STREAM_TYPE_FILE:
327 gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len);
328 ensure_file_operation(str, filemode_Write);
329 fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
332 else /* Regular file */
334 ensure_file_operation(str, filemode_Write);
335 fwrite(buf, sizeof(gchar), len, str->file_pointer);
338 else /* Text mode is the same for Unicode and regular files */
340 gchar *utf8 = convert_latin1_to_utf8(buf, len);
343 ensure_file_operation(str, filemode_Write);
344 g_fprintf(str->file_pointer, "%s", utf8);
349 str->write_count += len;
351 case STREAM_TYPE_RESOURCE:
352 ILLEGAL(_("Writing to a resource stream is illegal."));
355 ILLEGAL_PARAM("Unknown stream type: %u", str->type);
359 /* Internal function: write a Unicode buffer with length to a stream. */
361 write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len)
365 case STREAM_TYPE_WINDOW:
366 /* Each window type has a different way of printing to it */
367 switch(str->window->type)
369 /* Printing to these windows' streams does nothing */
372 case wintype_Graphics:
373 str->write_count += len;
376 /* Text grid/buffer windows */
377 case wintype_TextGrid:
378 case wintype_TextBuffer:
380 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
382 write_utf8_to_window_buffer(str->window, utf8);
386 str->write_count += len;
389 ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
392 /* Now write the same buffer to the window's echo stream */
393 if(str->window->echo_stream != NULL)
394 write_buffer_to_stream_uni(str->window->echo_stream, buf, len);
398 case STREAM_TYPE_MEMORY:
399 if(str->unicode && str->ubuffer)
401 int copycount = MIN(len, str->buflen - str->mark);
402 memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32));
403 str->mark += copycount;
405 if(!str->unicode && str->buffer)
407 gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
408 int copycount = MIN(len, str->buflen - str->mark);
409 memmove(str->buffer + str->mark, latin1, copycount);
411 str->mark += copycount;
414 /* Move the EOF marker if we wrote past it */
415 if(str->mark > str->endmark)
416 str->endmark = str->mark;
418 str->write_count += len;
421 case STREAM_TYPE_FILE:
426 gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len);
427 ensure_file_operation(str, filemode_Write);
428 fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
431 else /* Regular file */
433 gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
434 ensure_file_operation(str, filemode_Write);
435 fwrite(latin1, sizeof(gchar), len, str->file_pointer);
439 else /* Text mode is the same for Unicode and regular files */
441 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
444 ensure_file_operation(str, filemode_Write);
445 g_fprintf(str->file_pointer, "%s", utf8);
450 str->write_count += len;
452 case STREAM_TYPE_RESOURCE:
453 ILLEGAL(_("Writing to a resource stream is illegal."));
456 ILLEGAL_PARAM("Unknown stream type: %u", str->type);
461 * glk_put_char_stream:
462 * @str: An output stream.
463 * @ch: A character in Latin-1 encoding.
465 * The same as glk_put_char(), except that you specify a stream @str to print
466 * to, instead of using the current stream. It is illegal for @str to be %NULL,
467 * or an input-only stream.
470 glk_put_char_stream(strid_t str, unsigned char ch)
472 VALID_STREAM(str, return);
473 g_return_if_fail(str->file_mode != filemode_Read);
475 write_buffer_to_stream(str, (gchar *)&ch, 1);
479 * glk_put_char_stream_uni:
480 * @str: An output stream.
481 * @ch: A Unicode code point.
483 * The same as glk_put_char_uni(), except that you specify a stream @str to
484 * print to, instead of using the current stream. It is illegal for @str to be
485 * %NULL, or an input-only stream.
488 glk_put_char_stream_uni(strid_t str, glui32 ch)
490 VALID_STREAM(str, return);
491 g_return_if_fail(str->file_mode != filemode_Read);
493 write_buffer_to_stream_uni(str, &ch, 1);
497 * glk_put_string_stream:
498 * @str: An output stream.
499 * @s: A null-terminated string in Latin-1 encoding.
501 * The same as glk_put_string(), except that you specify a stream @str to print
502 * to, instead of using the current stream. It is illegal for @str to be %NULL,
503 * or an input-only stream.
506 glk_put_string_stream(strid_t str, char *s)
508 VALID_STREAM(str, return);
512 g_return_if_fail(str->file_mode != filemode_Read);
514 write_buffer_to_stream(str, s, strlen(s));
518 * glk_put_string_stream_uni:
519 * @str: An output stream.
520 * @s: A null-terminated array of Unicode code points.
522 * The same as glk_put_string_uni(), except that you specify a stream @str to
523 * print to, instead of using the current stream. It is illegal for @str to be
524 * %NULL, or an input-only stream.
527 glk_put_string_stream_uni(strid_t str, glui32 *s)
529 VALID_STREAM(str, return);
533 g_return_if_fail(str->file_mode != filemode_Read);
535 /* An impromptu strlen() for glui32 arrays */
540 write_buffer_to_stream_uni(str, s, len);
544 * glk_put_buffer_stream:
545 * @str: An output stream.
546 * @buf: An array of characters in Latin-1 encoding.
547 * @len: Length of @buf.
549 * The same as glk_put_buffer(), except that you specify a stream @str to print
550 * to, instead of using the current stream. It is illegal for @str to be %NULL,
551 * or an input-only stream.
554 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
556 VALID_STREAM(str, return);
560 g_return_if_fail(str->file_mode != filemode_Read);
562 write_buffer_to_stream(str, buf, len);
566 * glk_put_buffer_stream_uni:
567 * @str: An output stream.
568 * @buf: An array of Unicode code points.
569 * @len: Length of @buf.
571 * The same as glk_put_buffer_uni(), except that you specify a stream @str to
572 * print to, instead of using the current stream. It is illegal for @str to be
573 * %NULL, or an input-only stream.
576 glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
578 VALID_STREAM(str, return);
582 g_return_if_fail(str->file_mode != filemode_Read);
584 write_buffer_to_stream_uni(str, buf, len);
589 **************** READING FUNCTIONS ********************************************
593 /* Internal function: Read one big-endian four-byte character from file fp and
594 return it as a Unicode code point, or -1 on EOF */
596 read_ucs4be_char_from_file(strid_t str)
598 unsigned char readbuffer[4];
599 ensure_file_operation(str, filemode_Read);
600 if(fread(readbuffer, sizeof(unsigned char), 4, str->file_pointer) < 4)
603 readbuffer[0] << 24 |
604 readbuffer[1] << 16 |
609 /* Internal function: Read one UTF-8 character, which may be more than one byte,
610 from file fp and return it as a Unicode code point, or -1 on EOF */
612 read_utf8_char_from_file(strid_t str)
614 gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
616 gunichar charresult = (gunichar)-2;
617 ensure_file_operation(str, filemode_Read);
618 for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++)
620 int ch = fgetc(str->file_pointer);
623 readbuffer[foo] = (gchar)ch;
624 charresult = g_utf8_get_char_validated(readbuffer, foo + 1);
625 /* charresult is -1 if invalid, -2 if incomplete, and the unicode code
628 /* Silently return unknown characters as 0xFFFD, Replacement Character */
629 if(charresult == (gunichar)-1 || charresult == (gunichar)-2)
634 /* Internal function: Read one UTF-8 character, which may be more than one byte,
635 from a memory stream @str, and return it as a Unicode code point. */
637 read_utf8_char_from_buffer(strid_t str)
640 gunichar charresult = (gunichar)-2;
641 char *buffer = str->buffer + str->mark;
642 size_t maxlen = str->buflen - str->mark;
647 for(foo = 1; foo <= maxlen; foo++)
649 charresult = g_utf8_get_char_validated(buffer, foo);
650 /* charresult is -1 if invalid, -2 if incomplete, and the
651 Unicode code point otherwise */
652 if(charresult != (gunichar)-2)
658 /* Return -1 on EOS */
659 if(charresult == (gunichar)-2)
661 /* Silently return unknown characters as 0xFFFD, Replacement Character */
662 if(charresult == (gunichar)-1)
667 /* Internal function: Read one big-endian four-byte character from memory and
668 return it as a Unicode code point, or -1 on EOF */
670 read_ucs4be_char_from_buffer(strid_t str)
672 glui32 ch = str->buffer[str->mark++];
673 if(str->mark >= str->buflen)
675 ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
676 if(str->mark >= str->buflen)
678 ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
679 if(str->mark >= str->buflen)
681 ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
686 /* Internal function: Tell whether this code point is a Unicode newline. The
687 file pointer and eight-bit flag are included in case the newline is a CR
688 (U+000D). If the next character is LF (U+000A) then it also belongs to the
691 is_unicode_newline(glsi32 ch, strid_t str, gboolean utf8)
693 if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
696 glsi32 ch2 = utf8? read_utf8_char_from_file(str) :
697 read_ucs4be_char_from_file(str);
699 if(fseek(str->file_pointer, utf8? -1 : -4, SEEK_CUR) == -1)
700 WARNING_S("Seek failed on stream", g_strerror(errno) );
701 str->lastop = 0; /* can read or write after a seek */
708 /* Internal function: Read one character from a stream. Returns a value which
709 can be returned unchanged by glk_get_char_stream_uni(), but
710 glk_get_char_stream() must replace high values by the placeholder character. */
712 get_char_stream_common(strid_t str)
716 case STREAM_TYPE_RESOURCE:
719 if(!str->buffer || str->mark >= str->buflen)
722 /* Cheap big-endian stream */
723 return read_ucs4be_char_from_buffer(str);
724 /* slightly less cheap UTF8 stream */
725 return read_utf8_char_from_buffer(str);
727 /* for text streams, fall through to memory case */
728 case STREAM_TYPE_MEMORY:
731 if(!str->ubuffer || str->mark >= str->buflen)
733 glui32 ch = str->ubuffer[str->mark++];
739 if(!str->buffer || str->mark >= str->buflen)
741 unsigned char ch = str->buffer[str->mark++];
747 case STREAM_TYPE_FILE:
752 glsi32 ch = read_ucs4be_char_from_file(str);
758 else /* Regular file */
760 ensure_file_operation(str, filemode_Read);
761 int ch = fgetc(str->file_pointer);
769 else /* Text mode is the same for Unicode and regular files */
771 glsi32 ch = read_utf8_char_from_file(str);
779 ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
785 * glk_get_char_stream:
786 * @str: An input stream.
788 * Reads one character from the stream @str. (There is no notion of a
789 * <quote>current input stream.</quote>) It is illegal for @str to be %NULL, or
790 * an output-only stream.
792 * The result will be between 0 and 255. As with all basic text functions, Glk
793 * assumes the Latin-1 encoding. See <link
794 * linkend="chimara-Character-Encoding">Character Encoding</link>. If the end
795 * of the stream has been reached, the result will be -1.
798 * Note that high-bit characters (128..255) are <emphasis>not</emphasis>
799 * returned as negative numbers.
802 * If the stream contains Unicode data — for example, if it was created
803 * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() — then
804 * characters beyond 255 will be returned as 0x3F (<code>"?"</code>).
806 * It is usually more efficient to read several characters at once with
807 * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling
808 * glk_get_char_stream() several times.
810 * Returns: A character value between 0 and 255, or -1 on end of stream.
813 glk_get_char_stream(strid_t str)
815 VALID_STREAM(str, return -1);
816 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
818 glsi32 ch = get_char_stream_common(str);
819 return (ch > 0xFF)? PLACEHOLDER : ch;
823 * glk_get_char_stream_uni:
824 * @str: An input stream.
826 * Reads one character from the stream @str. If the end of the stream has been
827 * reached, the result will be -1.
829 * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream.
832 glk_get_char_stream_uni(strid_t str)
834 VALID_STREAM(str, return -1);
835 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
837 return get_char_stream_common(str);
841 * glk_get_buffer_stream:
842 * @str: An input stream.
843 * @buf: A buffer with space for at least @len characters.
844 * @len: The number of characters to read.
846 * Reads @len characters from @str, unless the end of stream is reached first.
847 * No terminal null is placed in the buffer.
849 * Returns: The number of characters actually read.
852 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
854 VALID_STREAM(str, return 0);
855 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
856 g_return_val_if_fail(buf != NULL, 0);
860 case STREAM_TYPE_RESOURCE:
865 while(copycount < len && str->buffer && str->mark < str->buflen)
869 ch = read_ucs4be_char_from_buffer(str);
871 ch = read_utf8_char_from_buffer(str);
872 buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
876 /* for text streams, fall through to memory case */
878 case STREAM_TYPE_MEMORY:
883 while(copycount < len && str->ubuffer && str->mark < str->buflen)
885 glui32 ch = str->ubuffer[str->mark++];
886 buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
891 if(str->buffer) /* if not, copycount stays 0 */
892 copycount = MIN(len, str->buflen - str->mark);
893 memmove(buf, str->buffer + str->mark, copycount);
894 str->mark += copycount;
897 str->read_count += copycount;
900 case STREAM_TYPE_FILE:
903 if(str->unicode) /* Binary file with 4-byte characters */
905 /* Read len characters of 4 bytes each */
906 unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
907 ensure_file_operation(str, filemode_Read);
908 size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
909 /* If there was an incomplete character */
913 WARNING("Incomplete character in binary Unicode file");
917 for(foo = 0; foo < count; foo += 4)
919 glsi32 ch = readbuffer[foo] << 24
920 | readbuffer[foo + 1] << 16
921 | readbuffer[foo + 2] << 8
922 | readbuffer[foo + 3];
923 buf[foo / 4] = (ch > 255)? 0x3F : (char)ch;
926 str->read_count += count / 4;
929 else /* Regular binary file */
931 ensure_file_operation(str, filemode_Read);
932 size_t count = fread(buf, sizeof(char), len, str->file_pointer);
933 str->read_count += count;
937 else /* Text mode is the same for Unicode and regular files */
939 /* Do it character-by-character */
941 for(foo = 0; foo < len; foo++)
943 glsi32 ch = read_utf8_char_from_file(str);
947 buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
952 ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
958 * glk_get_buffer_stream_uni:
959 * @str: An input stream.
960 * @buf: A buffer with space for at least @len Unicode code points.
961 * @len: The number of characters to read.
963 * Reads @len Unicode characters from @str, unless the end of stream is reached
964 * first. No terminal null is placed in the buffer.
966 * Returns: The number of Unicode characters actually read.
969 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
971 VALID_STREAM(str, return 0);
972 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
973 g_return_val_if_fail(buf != NULL, 0);
977 case STREAM_TYPE_RESOURCE:
982 while(copycount < len && str->buffer && str->mark < str->buflen)
986 ch = read_ucs4be_char_from_buffer(str);
988 ch = read_utf8_char_from_buffer(str);
989 buf[copycount++] = ch;
993 /* for text streams, fall through to memory case */
995 case STREAM_TYPE_MEMORY:
1000 if(str->ubuffer) /* if not, copycount stays 0 */
1001 copycount = MIN(len, str->buflen - str->mark);
1002 memmove(buf, str->ubuffer + str->mark, copycount * 4);
1003 str->mark += copycount;
1007 while(copycount < len && str->buffer && str->mark < str->buflen)
1009 unsigned char ch = str->buffer[str->mark++];
1010 buf[copycount++] = ch;
1014 str->read_count += copycount;
1017 case STREAM_TYPE_FILE:
1020 if(str->unicode) /* Binary file with 4-byte characters */
1022 /* Read len characters of 4 bytes each */
1023 unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
1024 ensure_file_operation(str, filemode_Read);
1025 size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
1026 /* If there was an incomplete character */
1030 WARNING("Incomplete character in binary Unicode file");
1034 for(foo = 0; foo < count; foo += 4)
1035 buf[foo / 4] = readbuffer[foo] << 24
1036 | readbuffer[foo + 1] << 16
1037 | readbuffer[foo + 2] << 8
1038 | readbuffer[foo + 3];
1040 str->read_count += count / 4;
1043 else /* Regular binary file */
1045 unsigned char *readbuffer = g_new0(unsigned char, len);
1046 ensure_file_operation(str, filemode_Read);
1047 size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer);
1049 for(foo = 0; foo < count; foo++)
1050 buf[foo] = readbuffer[foo];
1052 str->read_count += count;
1056 else /* Text mode is the same for Unicode and regular files */
1058 /* Do it character-by-character */
1060 for(foo = 0; foo < len; foo++)
1062 glsi32 ch = read_utf8_char_from_file(str);
1071 ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1077 * glk_get_line_stream:
1078 * @str: An input stream.
1079 * @buf: A buffer with space for at least @len characters.
1080 * @len: The number of characters to read, plus one.
1082 * Reads characters from @str, until either
1084 * <alt>@len - 1</alt>
1085 * <mathphrase>@len - 1</mathphrase>
1087 * characters have been read or a newline has been read. It then puts a
1088 * terminal null (<code>'\0'</code>) character on
1089 * the end. It returns the number of characters actually read, including the
1090 * newline (if there is one) but not including the terminal null.
1092 * Returns: The number of characters actually read.
1095 glk_get_line_stream(strid_t str, char *buf, glui32 len)
1097 VALID_STREAM(str, return 0);
1098 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
1099 g_return_val_if_fail(buf != NULL, 0);
1103 case STREAM_TYPE_RESOURCE:
1108 /* Do it character-by-character */
1109 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1113 ch = read_ucs4be_char_from_buffer(str);
1115 ch = read_utf8_char_from_buffer(str);
1116 /* Check for Unicode newline; slightly different than
1118 if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1120 buf[copycount++] = '\n';
1125 if(str->buffer[str->mark] == 0x0A)
1126 str->mark++; /* skip past next newline */
1127 buf[copycount++] = '\n';
1130 buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1132 buf[copycount] = '\0';
1135 /* for text streams, fall through to the memory case */
1137 case STREAM_TYPE_MEMORY:
1142 /* Do it character-by-character */
1143 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen)
1145 glui32 ch = str->ubuffer[str->mark++];
1146 /* Check for Unicode newline; slightly different than
1148 if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1150 buf[copycount++] = '\n';
1155 if(str->ubuffer[str->mark] == 0x0A)
1156 str->mark++; /* skip past next newline */
1157 buf[copycount++] = '\n';
1160 buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1162 buf[copycount] = '\0';
1166 if(str->buffer) /* if not, copycount stays 0 */
1167 copycount = MIN(len - 1, str->buflen - str->mark);
1168 char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount);
1169 if(endptr) /* newline was found */
1170 copycount = endptr - buf; /* Real copy count */
1171 buf[copycount] = '\0';
1172 str->mark += copycount;
1175 str->read_count += copycount;
1178 case STREAM_TYPE_FILE:
1181 if(str->unicode) /* Binary file with 4-byte characters */
1183 /* Do it character-by-character */
1185 for(copycount = 0; copycount < len - 1; copycount++)
1187 glsi32 ch = read_ucs4be_char_from_file(str);
1190 buf[copycount] = '\0';
1194 if(is_unicode_newline(ch, str, FALSE))
1196 buf[copycount++] = '\n';
1197 buf[copycount] = '\0';
1200 buf[copycount] = (ch > 0xFF)? '?' : (char)ch;
1205 else /* Regular binary file */
1207 ensure_file_operation(str, filemode_Read);
1208 if( !fgets(buf, len, str->file_pointer) ) {
1213 int nread = strlen(buf);
1214 str->read_count += nread;
1218 else /* Text mode is the same for Unicode and regular files */
1220 /* Do it character-by-character */
1222 for(foo = 0; foo < len - 1; foo++)
1224 glsi32 ch = read_utf8_char_from_file(str);
1231 if(is_unicode_newline(ch, str, TRUE))
1234 buf[foo + 1] = '\0';
1237 buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
1243 ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1249 * glk_get_line_stream_uni:
1250 * @str: An input stream.
1251 * @buf: A buffer with space for at least @len Unicode code points.
1252 * @len: The number of characters to read, plus one.
1254 * Reads Unicode characters from @str, until either
1256 * <alt>@len - 1</alt>
1257 * <mathphrase>@len - 1</mathphrase>
1259 * Unicode characters have been read or a newline has been read. It then puts a
1260 * terminal null (a zero value) on the end.
1262 * Returns: The number of characters actually read, including the newline (if
1263 * there is one) but not including the terminal null.
1266 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len)
1268 VALID_STREAM(str, return 0);
1269 g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
1270 g_return_val_if_fail(buf != NULL, 0);
1274 case STREAM_TYPE_RESOURCE:
1279 /* Do it character-by-character */
1280 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1284 ch = read_ucs4be_char_from_buffer(str);
1286 ch = read_utf8_char_from_buffer(str);
1287 /* Check for Unicode newline; slightly different than
1289 if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1291 buf[copycount++] = '\n';
1296 if(str->ubuffer[str->mark] == 0x0A)
1297 str->mark++; /* skip past next newline */
1298 buf[copycount++] = '\n';
1301 buf[copycount++] = ch;
1303 buf[copycount] = '\0';
1306 /* for text streams, fall through to the memory case */
1308 case STREAM_TYPE_MEMORY:
1313 /* Do it character-by-character */
1314 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen)
1316 glui32 ch = str->ubuffer[str->mark++];
1317 /* Check for Unicode newline; slightly different than
1319 if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1321 buf[copycount++] = '\n';
1326 if(str->ubuffer[str->mark] == 0x0A)
1327 str->mark++; /* skip past next newline */
1328 buf[copycount++] = '\n';
1331 buf[copycount++] = ch;
1333 buf[copycount] = '\0';
1337 /* No recourse to memccpy(), so do it character-by-character */
1338 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1340 gchar ch = str->buffer[str->mark++];
1341 /* Check for newline */
1342 if(ch == '\n') /* Also check for \r and \r\n? */
1344 buf[copycount++] = '\n';
1347 buf[copycount++] = (unsigned char)ch;
1352 str->read_count += copycount;
1355 case STREAM_TYPE_FILE:
1358 if(str->unicode) /* Binary file with 4-byte characters */
1360 /* Do it character-by-character */
1362 for(copycount = 0; copycount < len - 1; copycount++)
1364 glsi32 ch = read_ucs4be_char_from_file(str);
1371 if(is_unicode_newline(ch, str, FALSE))
1373 buf[copycount++] = ch; /* Preserve newline types??? */
1377 buf[copycount] = ch;
1382 else /* Regular binary file */
1384 gchar *readbuffer = g_new0(gchar, len);
1385 ensure_file_operation(str, filemode_Read);
1386 if( !fgets(readbuffer, len, str->file_pointer) ) {
1391 glui32 count = strlen(readbuffer);
1393 for(foo = 0; foo < count + 1; foo++) /* Copy terminator */
1394 buf[foo] = (unsigned char)(readbuffer[foo]);
1395 str->read_count += count;
1399 else /* Text mode is the same for Unicode and regular files */
1401 /* Do it character-by-character */
1403 for(foo = 0; foo < len - 1; foo++)
1405 glsi32 ch = read_utf8_char_from_file(str);
1412 if(is_unicode_newline(ch, str, TRUE))
1414 buf[foo] = ch; /* Preserve newline types??? */
1424 ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1431 **************** SEEKING FUNCTIONS ********************************************
1436 * glk_stream_get_position:
1437 * @str: A file or memory stream.
1439 * Returns the position of the read/write mark in @str. For memory streams and
1440 * binary file streams, this is exactly the number of characters read or written
1441 * from the beginning of the stream (unless you have moved the mark with
1442 * glk_stream_set_position().) For text file streams, matters are more
1443 * ambiguous, since (for example) writing one byte to a text file may store more
1444 * than one character in the platform's native encoding. You can only be sure
1445 * that the position increases as you read or write to the file.
1447 * Additional complication: for Latin-1 memory and file streams, a character is
1448 * a byte. For Unicode memory and file streams (those created by
1449 * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is
1450 * a 32-bit word. So in a binary Unicode file, positions are multiples of four
1454 * If this bothers you, don't use binary Unicode files. I don't think they're
1455 * good for much anyhow.
1458 * glk_stream_get_position() on a window stream will always return zero.
1461 * It might make more sense to return the number of characters written to the
1462 * window, but existing libraries do not support this and it's not really
1463 * worth adding the feature.
1466 * Returns: position of the read/write mark in @str.
1469 glk_stream_get_position(strid_t str)
1471 VALID_STREAM(str, return 0);
1475 case STREAM_TYPE_MEMORY:
1476 case STREAM_TYPE_RESOURCE:
1478 case STREAM_TYPE_FILE:
1479 return ftell(str->file_pointer);
1480 case STREAM_TYPE_WINDOW:
1483 ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1489 * glk_stream_set_position:
1490 * @str: A file or memory stream.
1491 * @pos: The position to set the mark to, relative to @seekmode.
1492 * @seekmode: One of %seekmode_Start, %seekmode_Current, or %seekmode_End.
1494 * Sets the position of the read/write mark in @str. The position is controlled
1495 * by @pos, and the meaning of @pos is controlled by @seekmode. See the
1496 * <code>seekmode_</code> constants below.
1498 * It is illegal to specify a position before the beginning or after the end of
1501 * In binary files, the mark position is exact — it corresponds with the
1502 * number of characters you have read or written. In text files, this mapping
1503 * can vary, because of linefeed conventions or other character-set
1504 * approximations. See <link linkend="chimara-Streams">Streams</link>.
1505 * glk_stream_set_position() and glk_stream_get_position() measure positions in
1506 * the platform's native encoding — after character cookery. Therefore,
1507 * in a text stream, it is safest to use glk_stream_set_position() only to move
1508 * to the beginning or end of a file, or to a position determined by
1509 * glk_stream_get_position().
1511 * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
1512 * characters are 32-bit words, or four bytes each.
1514 * A window stream doesn't have a movable mark, so calling
1515 * glk_stream_set_position() has no effect.
1518 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
1520 VALID_STREAM(str, return);
1521 g_return_if_fail(!(seekmode == seekmode_Start && pos < 0));
1522 g_return_if_fail(!(seekmode == seekmode_End && pos > 0));
1526 case STREAM_TYPE_RESOURCE:
1527 case STREAM_TYPE_MEMORY:
1530 case seekmode_Start: str->mark = pos; break;
1531 case seekmode_Current: str->mark += pos; break;
1532 case seekmode_End: str->mark = str->endmark + pos; break;
1534 g_return_if_reached();
1538 case STREAM_TYPE_FILE:
1543 case seekmode_Start: whence = SEEK_SET; break;
1544 case seekmode_Current: whence = SEEK_CUR; break;
1545 case seekmode_End: whence = SEEK_END; break;
1547 g_return_if_reached();
1550 if(fseek(str->file_pointer, pos, whence) == -1)
1551 WARNING("Seek failed on file stream");
1552 str->lastop = 0; /* Either reading or writing is legal after fseek() */
1555 case STREAM_TYPE_WINDOW:
1556 break; /* Quietly do nothing */
1558 ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);