Implement resource streams
[projects/chimara/chimara.git] / libchimara / strio.c
1 #include <config.h>
2 #include "charset.h"
3 #include "magic.h"
4 #include "stream.h"
5 #include <errno.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <glib.h>
9 #include <glib/gstdio.h>
10 #include <glib/gi18n-lib.h>
11
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. */
15 static void
16 ensure_file_operation(strid_t str, glui32 op)
17 {
18         if(str->lastop != 0 && str->lastop != op)
19         {
20                 long pos = ftell(str->file_pointer);
21                 if(pos == -1)
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));
25         }
26         str->lastop = op; /* Not 0, because we are about to do the operation anyway */
27 }
28
29 /*
30  *
31  **************** WRITING FUNCTIONS ********************************************
32  *
33  */
34
35 /* Internal function: write a UTF-8 string to a text buffer window's text buffer. */
36 static void
37 write_utf8_to_window_buffer(winid_t win, gchar *s)
38 {
39         if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
40         {
41                 ILLEGAL("Tried to print to a text buffer window with line input pending.");
42                 return;
43         }
44
45         // Write to the buffer  
46         g_string_append(win->buffer, s);
47 }
48         
49 /* Internal function: flush a window's text buffer to the screen. */
50 void
51 flush_window_buffer(winid_t win)
52 {
53 #ifdef DEBUG_STYLES
54         g_printf("%s\n", win->buffer->str);
55 #endif
56         if(win->type != wintype_TextBuffer && win->type != wintype_TextGrid)
57                 return;
58
59         if(win->buffer->len == 0)
60                 return;
61
62         gdk_threads_enter();
63
64         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
65
66         switch(win->type) {
67         case wintype_TextBuffer:
68         {
69                 GtkTextIter start, end;
70                 gtk_text_buffer_get_end_iter(buffer, &end);
71                 gint start_offset;
72
73                 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
74
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);
78
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);
82
83                 // Player's style overrides
84                 gtk_text_buffer_apply_tag(buffer, style_tag, &start, &end);
85
86                 // GLK Program's style overrides
87                 gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &end);
88
89                 // Default style
90                 gtk_text_buffer_apply_tag(buffer, default_tag, &start, &end);
91
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);
98                 }
99
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);
103                 }
104
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);
108                 }
109
110                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
111                 g_assert(glk);
112                 g_signal_emit_by_name(glk, "text-buffer-output", win->rock, win->buffer->str);
113         }
114                 break;
115
116         case wintype_TextGrid:
117         {
118                 /* Number of characters to insert */
119                 glong length = win->buffer->len;
120                 glong chars_left = length;
121                 
122                 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
123                 
124                 /* Get cursor position */
125                 GtkTextIter start, insert;
126                 gint start_offset;
127
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);
131                 
132                 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
133
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");
138
139                 while(chars_left > available_space && !gtk_text_iter_is_end(&insert))
140                 {
141                         GtkTextIter end = insert;
142                         gtk_text_iter_forward_to_line_end(&end);
143                         gtk_text_buffer_delete(buffer, &insert, &end);
144
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);
148
149                         // Default style
150                         gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
151
152                         // Player's style overrides
153                         gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
154
155                         // GLK Program's style overrides
156                         gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
157
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);
163                         }
164
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);
168
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);
172                         }
173
174                         chars_left -= available_space;
175                         gtk_text_iter_forward_line(&insert);
176                         available_space = win->width;
177                 }
178                 if(!gtk_text_iter_is_end(&insert))
179                 {
180                         GtkTextIter end = insert;
181                         gtk_text_iter_forward_chars(&end, chars_left);
182                         gtk_text_buffer_delete(buffer, &insert, &end);
183
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);
187
188                         // Default style
189                         gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
190
191                         // Player's style overrides
192                         gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
193
194                         // GLK Program's style overrides
195                         gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
196
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);
202                         }
203
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);
207
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);
211                         }
212                 }
213                 
214                 gtk_text_buffer_move_mark(buffer, cursor, &start);
215         }
216                 break;
217         }
218
219         gdk_threads_leave();
220
221         g_string_truncate(win->buffer, 0);
222 }
223
224 /* Internal function: write a Latin-1 buffer with length to a stream. */
225 static void
226 write_buffer_to_stream(strid_t str, gchar *buf, glui32 len)
227 {
228         switch(str->type)
229         {
230                 case STREAM_TYPE_WINDOW:
231                         /* Each window type has a different way of printing to it */
232                         switch(str->window->type)
233                         {
234                                 /* Printing to these windows' streams does nothing */
235                                 case wintype_Blank:
236                                 case wintype_Pair:
237                                 case wintype_Graphics:
238                                         str->write_count += len;
239                                         break;
240                                         
241                             /* Text grid/buffer windows */
242                             case wintype_TextGrid:
243                                 {
244                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
245                                 if(utf8 != NULL) {
246                                                 /* Deal with newlines */
247                                                 int i;
248                                                 gchar *line = utf8;
249                                                 for(i=0; i<len; i++) {
250                                                         if(utf8[i] == '\n') {
251                                                                 utf8[i] = '\0';
252                                                                 write_utf8_to_window_buffer(str->window, line);
253                                                                 flush_window_buffer(str->window);
254
255                                                                 /* Move cursor position forward to the next line */
256                                                                 gdk_threads_enter();
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");
261
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);
266                                                                 gdk_threads_leave();
267
268                                                                 line = utf8 + (i < len-1 ? (i+1):(len-1));
269                                                         }
270                                                 }
271                                                                 
272                                                 /* No more newlines left. */
273                                                 write_utf8_to_window_buffer(str->window, line);
274                                                 g_free(utf8);
275                                         }
276
277                                         str->write_count += len;
278                                 }
279                                         break;
280
281                                 case wintype_TextBuffer:
282                             {
283                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
284                                 if(utf8 != NULL) {
285                                                 write_utf8_to_window_buffer(str->window, utf8);
286                                                 g_free(utf8);
287                                         }
288                                 }       
289                                         str->write_count += len;
290                                         break;
291                                 default:
292                                         ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
293                         }
294                         
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);
298                         
299                         break;
300                         
301                 case STREAM_TYPE_MEMORY:
302                         if(str->unicode && str->ubuffer)
303                         {
304                                 int foo = 0;
305                                 while(str->mark < str->buflen && foo < len)
306                                         str->ubuffer[str->mark++] = (unsigned char)buf[foo++];
307                         }
308                         if(!str->unicode && str->buffer)
309                         {
310                                 int copycount = MIN(len, str->buflen - str->mark);
311                                 memmove(str->buffer + str->mark, buf, copycount);
312                                 str->mark += copycount;
313                         }
314
315                         /* Move the EOF marker if we wrote past it */
316                         if(str->mark > str->endmark)
317                                 str->endmark = str->mark;
318
319                         str->write_count += len;
320                         break;
321                         
322                 case STREAM_TYPE_FILE:
323                         if(str->binary) 
324                         {
325                                 if(str->unicode) 
326                                 {
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);
330                                         g_free(writebuffer);
331                                 } 
332                                 else /* Regular file */
333                                 {
334                                         ensure_file_operation(str, filemode_Write);
335                                         fwrite(buf, sizeof(gchar), len, str->file_pointer);
336                                 }
337                         }
338                         else /* Text mode is the same for Unicode and regular files */
339                         {
340                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
341                                 if(utf8 != NULL)
342                                 {
343                                         ensure_file_operation(str, filemode_Write);
344                                         g_fprintf(str->file_pointer, "%s", utf8);
345                                         g_free(utf8);
346                                 }
347                         }
348                         
349                         str->write_count += len;
350                         break;
351                 case STREAM_TYPE_RESOURCE:
352                         ILLEGAL(_("Writing to a resource stream is illegal."));
353                         break;
354                 default:
355                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
356         }
357 }
358
359 /* Internal function: write a Unicode buffer with length to a stream. */
360 static void
361 write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len)
362 {
363         switch(str->type)
364         {
365                 case STREAM_TYPE_WINDOW:
366                         /* Each window type has a different way of printing to it */
367                         switch(str->window->type)
368                         {
369                                 /* Printing to these windows' streams does nothing */
370                                 case wintype_Blank:
371                                 case wintype_Pair:
372                                 case wintype_Graphics:
373                                         str->write_count += len;
374                                         break;
375                                         
376                             /* Text grid/buffer windows */
377                             case wintype_TextGrid:
378                             case wintype_TextBuffer:
379                             {
380                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
381                                 if(utf8 != NULL) {
382                                                 write_utf8_to_window_buffer(str->window, utf8);
383                                                 g_free(utf8);
384                                         }
385                                 }       
386                                         str->write_count += len;
387                                         break;
388                                 default:
389                                         ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
390                         }
391                         
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);
395                         
396                         break;
397                         
398                 case STREAM_TYPE_MEMORY:
399                         if(str->unicode && str->ubuffer)
400                         {
401                                 int copycount = MIN(len, str->buflen - str->mark);
402                                 memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32));
403                                 str->mark += copycount;
404                         }
405                         if(!str->unicode && str->buffer)
406                         {
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);
410                                 g_free(latin1);
411                                 str->mark += copycount;
412                         }
413
414                         /* Move the EOF marker if we wrote past it */
415                         if(str->mark > str->endmark)
416                                 str->endmark = str->mark;
417
418                         str->write_count += len;
419                         break;
420                         
421                 case STREAM_TYPE_FILE:
422                         if(str->binary) 
423                         {
424                                 if(str->unicode) 
425                                 {
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);
429                                         g_free(writebuffer);
430                                 } 
431                                 else /* Regular file */
432                                 {
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);
436                                         g_free(latin1);
437                                 }
438                         }
439                         else /* Text mode is the same for Unicode and regular files */
440                         {
441                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
442                                 if(utf8 != NULL) 
443                                 {
444                                         ensure_file_operation(str, filemode_Write);
445                                         g_fprintf(str->file_pointer, "%s", utf8);
446                                         g_free(utf8);
447                                 }
448                         }
449                         
450                         str->write_count += len;
451                         break;
452                 case STREAM_TYPE_RESOURCE:
453                         ILLEGAL(_("Writing to a resource stream is illegal."));
454                         break;
455                 default:
456                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
457         }
458 }
459
460 /**
461  * glk_put_char_stream:
462  * @str: An output stream.
463  * @ch: A character in Latin-1 encoding.
464  *
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.
468  */
469 void
470 glk_put_char_stream(strid_t str, unsigned char ch)
471 {
472         VALID_STREAM(str, return);
473         g_return_if_fail(str->file_mode != filemode_Read);
474         
475         write_buffer_to_stream(str, (gchar *)&ch, 1);
476 }
477
478 /**
479  * glk_put_char_stream_uni:
480  * @str: An output stream.
481  * @ch: A Unicode code point.
482  *
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.
486  */
487 void
488 glk_put_char_stream_uni(strid_t str, glui32 ch)
489 {
490         VALID_STREAM(str, return);
491         g_return_if_fail(str->file_mode != filemode_Read);
492         
493         write_buffer_to_stream_uni(str, &ch, 1);
494 }
495
496 /**
497  * glk_put_string_stream:
498  * @str: An output stream.
499  * @s: A null-terminated string in Latin-1 encoding.
500  *
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.
504  */
505 void
506 glk_put_string_stream(strid_t str, char *s)
507 {
508         VALID_STREAM(str, return);
509         if(*s == 0)
510                 return;
511
512         g_return_if_fail(str->file_mode != filemode_Read);
513
514         write_buffer_to_stream(str, s, strlen(s));
515 }
516
517 /**
518  * glk_put_string_stream_uni:
519  * @str: An output stream.
520  * @s: A null-terminated array of Unicode code points.
521  *
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.
525  */
526 void
527 glk_put_string_stream_uni(strid_t str, glui32 *s)
528 {
529         VALID_STREAM(str, return);
530         if(*s == 0)
531                 return;
532
533         g_return_if_fail(str->file_mode != filemode_Read);
534         
535         /* An impromptu strlen() for glui32 arrays */
536         glong len = 0;
537         glui32 *ptr = s;
538         while(*ptr++)
539                 len++;
540         write_buffer_to_stream_uni(str, s, len);
541 }
542
543 /**
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.
548  *
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.
552  */
553 void
554 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
555 {
556         VALID_STREAM(str, return);
557         if(len == 0)
558                 return;
559
560         g_return_if_fail(str->file_mode != filemode_Read);
561         
562         write_buffer_to_stream(str, buf, len);
563 }
564
565 /**
566  * glk_put_buffer_stream_uni:
567  * @str: An output stream.
568  * @buf: An array of Unicode code points.
569  * @len: Length of @buf.
570  *
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.
574  */
575 void
576 glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
577 {
578         VALID_STREAM(str, return);
579         if(len == 0)
580                 return;
581
582         g_return_if_fail(str->file_mode != filemode_Read);
583         
584         write_buffer_to_stream_uni(str, buf, len);
585 }
586
587 /*
588  *
589  **************** READING FUNCTIONS ********************************************
590  *
591  */
592
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 */
595 static glsi32
596 read_ucs4be_char_from_file(strid_t str)
597 {
598         unsigned char readbuffer[4];
599         ensure_file_operation(str, filemode_Read);
600         if(fread(readbuffer, sizeof(unsigned char), 4, str->file_pointer) < 4)
601                 return -1; /* EOF */
602         return
603                 readbuffer[0] << 24 | 
604                 readbuffer[1] << 16 | 
605                 readbuffer[2] << 8  | 
606                 readbuffer[3];
607 }
608
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 */
611 static glsi32
612 read_utf8_char_from_file(strid_t str)
613 {
614         gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
615         int foo;
616         gunichar charresult = (gunichar)-2;
617         ensure_file_operation(str, filemode_Read);
618         for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) 
619         {
620                 int ch = fgetc(str->file_pointer);
621                 if(ch == EOF)
622                         return -1;
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
626                 point otherwise */
627         }
628         /* Silently return unknown characters as 0xFFFD, Replacement Character */
629         if(charresult == (gunichar)-1 || charresult == (gunichar)-2) 
630                 return 0xFFFD;
631         return charresult;
632 }
633
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. */
636 static glsi32
637 read_utf8_char_from_buffer(strid_t str)
638 {
639         size_t foo;
640         gunichar charresult = (gunichar)-2;
641         char *buffer = str->buffer + str->mark;
642         size_t maxlen = str->buflen - str->mark;
643
644         if(maxlen == 0)
645                 return -1;
646
647         for(foo = 1; foo <= maxlen; foo++)
648         {
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)
653                         break;
654         }
655         str->mark += foo;
656         str->read_count++;
657
658         /* Return -1 on EOS */
659         if(charresult == (gunichar)-2)
660                 return -1;
661         /* Silently return unknown characters as 0xFFFD, Replacement Character */
662         if(charresult == (gunichar)-1)
663                 return 0xFFFD;
664         return charresult;
665 }
666
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 */
669 static glsi32
670 read_ucs4be_char_from_buffer(strid_t str)
671 {
672         glui32 ch = str->buffer[str->mark++];
673         if(str->mark >= str->buflen)
674                 return -1;
675         ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
676         if(str->mark >= str->buflen)
677                 return -1;
678         ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
679         if(str->mark >= str->buflen)
680                 return -1;
681         ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
682         str->read_count++;
683         return ch;
684 }
685
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
689 newline. */
690 static gboolean
691 is_unicode_newline(glsi32 ch, strid_t str, gboolean utf8)
692 {
693         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
694                 return TRUE;
695         if(ch == 0x0D) {
696                 glsi32 ch2 = utf8? read_utf8_char_from_file(str) :
697                         read_ucs4be_char_from_file(str);
698                 if(ch2 != 0x0A) {
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 */
702                 }
703                 return TRUE;
704         }
705         return FALSE;
706 }
707
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. */
711 static glsi32
712 get_char_stream_common(strid_t str)
713 {
714         switch(str->type)
715         {
716                 case STREAM_TYPE_RESOURCE:
717                         if(str->unicode)
718                         {
719                                 if(!str->buffer || str->mark >= str->buflen)
720                                         return -1;
721                                 if(str->binary)
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);
726                         }
727                         /* for text streams, fall through to memory case */
728                 case STREAM_TYPE_MEMORY:
729                         if(str->unicode)
730                         {
731                                 if(!str->ubuffer || str->mark >= str->buflen)
732                                         return -1;
733                                 glui32 ch = str->ubuffer[str->mark++];
734                                 str->read_count++;
735                                 return ch;
736                         }
737                         else
738                         {
739                                 if(!str->buffer || str->mark >= str->buflen)
740                                         return -1;
741                                 unsigned char ch = str->buffer[str->mark++];
742                                 str->read_count++;
743                                 return ch;
744                         }
745                         break;
746                         
747                 case STREAM_TYPE_FILE:
748                         if(str->binary) 
749                         {
750                                 if(str->unicode) 
751                                 {
752                                         glsi32 ch = read_ucs4be_char_from_file(str);
753                                         if(ch == -1)
754                                                 return -1;
755                                         str->read_count++;
756                                         return ch;
757                                 }
758                                 else /* Regular file */
759                                 {
760                                         ensure_file_operation(str, filemode_Read);
761                                         int ch = fgetc(str->file_pointer);
762                                         if(ch == EOF)
763                                                 return -1;
764                                         
765                                         str->read_count++;
766                                         return ch;
767                                 }
768                         }
769                         else /* Text mode is the same for Unicode and regular files */
770                         {
771                                 glsi32 ch = read_utf8_char_from_file(str);
772                                 if(ch == -1)
773                                         return -1;
774                                         
775                                 str->read_count++;
776                                 return ch;
777                         }
778                 default:
779                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
780                         return -1;
781         }
782 }
783
784 /**
785  * glk_get_char_stream:
786  * @str: An input stream.
787  *
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.
791  *
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. 
796  *
797  * <note><para>
798  *   Note that high-bit characters (128..255) are <emphasis>not</emphasis>
799  *   returned as negative numbers.
800  * </para></note>
801  *
802  * If the stream contains Unicode data &mdash; for example, if it was created
803  * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() &mdash; then
804  * characters beyond 255 will be returned as 0x3F (<code>"?"</code>).
805  *
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.
809  *
810  * Returns: A character value between 0 and 255, or -1 on end of stream.
811  */
812 glsi32
813 glk_get_char_stream(strid_t str)
814 {
815         VALID_STREAM(str, return -1);
816         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
817         
818         glsi32 ch = get_char_stream_common(str);
819         return (ch > 0xFF)? PLACEHOLDER : ch;
820 }
821
822 /**
823  * glk_get_char_stream_uni:
824  * @str: An input stream.
825  *
826  * Reads one character from the stream @str. If the end of the stream has been
827  * reached, the result will be -1.
828  *
829  * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream.
830  */
831 glsi32
832 glk_get_char_stream_uni(strid_t str)
833 {
834         VALID_STREAM(str, return -1);
835         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
836         
837         return get_char_stream_common(str);
838 }
839
840 /**
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.
845  *
846  * Reads @len characters from @str, unless the end of stream is reached first.
847  * No terminal null is placed in the buffer.
848  *
849  * Returns: The number of characters actually read.
850  */
851 glui32
852 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
853 {
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);
857         
858         switch(str->type)
859         {
860                 case STREAM_TYPE_RESOURCE:
861                 {
862                         int copycount = 0;
863                         if(str->unicode)
864                         {
865                                 while(copycount < len && str->buffer && str->mark < str->buflen)
866                                 {
867                                         glui32 ch;
868                                         if(str->binary)
869                                                 ch = read_ucs4be_char_from_buffer(str);
870                                         else
871                                                 ch = read_utf8_char_from_buffer(str);
872                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
873                                 }
874                                 return copycount;
875                         }
876                         /* for text streams, fall through to memory case */
877                 }
878                 case STREAM_TYPE_MEMORY:
879                 {
880                         int copycount = 0;
881                         if(str->unicode)
882                         {
883                                 while(copycount < len && str->ubuffer && str->mark < str->buflen) 
884                                 {
885                                         glui32 ch = str->ubuffer[str->mark++];
886                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
887                                 }
888                         }
889                         else
890                         {
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;
895                         }
896
897                         str->read_count += copycount;           
898                         return copycount;
899                 }       
900                 case STREAM_TYPE_FILE:
901                         if(str->binary) 
902                         {
903                                 if(str->unicode) /* Binary file with 4-byte characters */
904                                 {
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 */
910                                         if(count % 4 != 0) 
911                                         {
912                                                 count -= count % 4;
913                                                 WARNING("Incomplete character in binary Unicode file");
914                                         }
915                                         
916                                         int foo;
917                                         for(foo = 0; foo < count; foo += 4)
918                                         {
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;
924                                         }
925                                         g_free(readbuffer);
926                                         str->read_count += count / 4;
927                                         return count / 4;
928                                 }
929                                 else /* Regular binary file */
930                                 {
931                                         ensure_file_operation(str, filemode_Read);
932                                         size_t count = fread(buf, sizeof(char), len, str->file_pointer);
933                                         str->read_count += count;
934                                         return count;
935                                 }
936                         }
937                         else /* Text mode is the same for Unicode and regular files */
938                         {
939                                 /* Do it character-by-character */
940                                 int foo;
941                                 for(foo = 0; foo < len; foo++)
942                                 {
943                                         glsi32 ch = read_utf8_char_from_file(str);
944                                         if(ch == -1)
945                                                 break;
946                                         str->read_count++;
947                                         buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
948                                 }
949                                 return foo;
950                         }
951                 default:
952                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
953                         return 0;
954         }
955 }
956
957 /**
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.
962  *
963  * Reads @len Unicode characters from @str, unless the end of stream is reached 
964  * first. No terminal null is placed in the buffer.
965  *
966  * Returns: The number of Unicode characters actually read.
967  */
968 glui32
969 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
970 {
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);
974         
975         switch(str->type)
976         {
977                 case STREAM_TYPE_RESOURCE:
978                 {
979                         int copycount = 0;
980                         if(str->unicode)
981                         {
982                                 while(copycount < len && str->buffer && str->mark < str->buflen)
983                                 {
984                                         glui32 ch;
985                                         if(str->binary)
986                                                 ch = read_ucs4be_char_from_buffer(str);
987                                         else
988                                                 ch = read_utf8_char_from_buffer(str);
989                                         buf[copycount++] = ch;
990                                 }
991                                 return copycount;
992                         }
993                         /* for text streams, fall through to memory case */
994                 }
995                 case STREAM_TYPE_MEMORY:
996                 {
997                         int copycount = 0;
998                         if(str->unicode)
999                         {
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;
1004                         }
1005                         else
1006                         {
1007                                 while(copycount < len && str->buffer && str->mark < str->buflen)
1008                                 {
1009                                         unsigned char ch = str->buffer[str->mark++];
1010                                         buf[copycount++] = ch;
1011                                 }
1012                         }
1013
1014                         str->read_count += copycount;           
1015                         return copycount;
1016                 }       
1017                 case STREAM_TYPE_FILE:
1018                         if(str->binary) 
1019                         {
1020                                 if(str->unicode) /* Binary file with 4-byte characters */
1021                                 {
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 */
1027                                         if(count % 4 != 0) 
1028                                         {
1029                                                 count -= count % 4;
1030                                                 WARNING("Incomplete character in binary Unicode file");
1031                                         }
1032                                         
1033                                         int foo;
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];
1039                                         g_free(readbuffer);
1040                                         str->read_count += count / 4;
1041                                         return count / 4;
1042                                 }
1043                                 else /* Regular binary file */
1044                                 {
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);
1048                                         int foo;
1049                                         for(foo = 0; foo < count; foo++)
1050                                                 buf[foo] = readbuffer[foo];
1051                                         g_free(readbuffer);
1052                                         str->read_count += count;
1053                                         return count;
1054                                 }
1055                         }
1056                         else /* Text mode is the same for Unicode and regular files */
1057                         {
1058                                 /* Do it character-by-character */
1059                                 int foo;
1060                                 for(foo = 0; foo < len; foo++)
1061                                 {
1062                                         glsi32 ch = read_utf8_char_from_file(str);
1063                                         if(ch == -1)
1064                                                 break;
1065                                         str->read_count++;
1066                                         buf[foo] = ch;
1067                                 }
1068                                 return foo;
1069                         }
1070                 default:
1071                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1072                         return 0;
1073         }
1074 }
1075
1076 /**
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.
1081  *
1082  * Reads characters from @str, until either 
1083  * <inlineequation>
1084  *   <alt>@len - 1</alt>
1085  *   <mathphrase>@len - 1</mathphrase>
1086  * </inlineequation>
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.
1091  *
1092  * Returns: The number of characters actually read.
1093  */
1094 glui32
1095 glk_get_line_stream(strid_t str, char *buf, glui32 len)
1096 {
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);
1100
1101         switch(str->type)
1102         {
1103                 case STREAM_TYPE_RESOURCE:
1104                 {
1105                         int copycount = 0;
1106                         if(str->unicode)
1107                         {
1108                                 /* Do it character-by-character */
1109                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1110                                 {
1111                                         glsi32 ch;
1112                                         if(str->binary)
1113                                                 ch = read_ucs4be_char_from_buffer(str);
1114                                         else
1115                                                 ch = read_utf8_char_from_buffer(str);
1116                                         /* Check for Unicode newline; slightly different than
1117                                         in file streams */
1118                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1119                                         {
1120                                                 buf[copycount++] = '\n';
1121                                                 break;
1122                                         }
1123                                         if(ch == 0x0D)
1124                                         {
1125                                                 if(str->buffer[str->mark] == 0x0A)
1126                                                         str->mark++; /* skip past next newline */
1127                                                 buf[copycount++] = '\n';
1128                                                 break;
1129                                         }
1130                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1131                                 }
1132                                 buf[copycount] = '\0';
1133                                 return copycount;
1134                         }
1135                         /* for text streams, fall through to the memory case */
1136                 }
1137                 case STREAM_TYPE_MEMORY:
1138                 {
1139                         int copycount = 0;
1140                         if(str->unicode)
1141                         {
1142                                 /* Do it character-by-character */
1143                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
1144                                 {
1145                                         glui32 ch = str->ubuffer[str->mark++];
1146                                         /* Check for Unicode newline; slightly different than
1147                                         in file streams */
1148                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1149                                         {
1150                                                 buf[copycount++] = '\n';
1151                                                 break;
1152                                         }
1153                                         if(ch == 0x0D)
1154                                         {
1155                                                 if(str->ubuffer[str->mark] == 0x0A)
1156                                                         str->mark++; /* skip past next newline */
1157                                                 buf[copycount++] = '\n';
1158                                                 break;
1159                                         }
1160                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1161                                 }
1162                                 buf[copycount] = '\0';
1163                         }
1164                         else
1165                         {
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;
1173                         }
1174                         
1175                         str->read_count += copycount;
1176                         return copycount;
1177                 }       
1178                 case STREAM_TYPE_FILE:
1179                         if(str->binary) 
1180                         {
1181                                 if(str->unicode) /* Binary file with 4-byte characters */
1182                                 {
1183                                         /* Do it character-by-character */
1184                                         int copycount;
1185                                         for(copycount = 0; copycount < len - 1; copycount++)
1186                                         {
1187                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1188                                                 if(ch == -1) 
1189                                                 {
1190                                                         buf[copycount] = '\0';
1191                                                         return copycount;
1192                                                 }
1193                                                 str->read_count++;
1194                                                 if(is_unicode_newline(ch, str, FALSE))
1195                                                 {
1196                                                         buf[copycount++] = '\n';
1197                                                         buf[copycount] = '\0';
1198                                                         return copycount;
1199                                                 }
1200                                                 buf[copycount] = (ch > 0xFF)? '?' : (char)ch;
1201                                         }
1202                                         buf[len] = '\0';
1203                                         return copycount;
1204                                 }
1205                                 else /* Regular binary file */
1206                                 {
1207                                         ensure_file_operation(str, filemode_Read);
1208                                         if( !fgets(buf, len, str->file_pointer) ) {
1209                                                 *buf = 0;
1210                                                 return 0;
1211                                         }
1212
1213                                         int nread = strlen(buf);
1214                                         str->read_count += nread;
1215                                         return nread;
1216                                 }
1217                         }
1218                         else /* Text mode is the same for Unicode and regular files */
1219                         {
1220                                 /* Do it character-by-character */
1221                                 int foo;
1222                                 for(foo = 0; foo < len - 1; foo++)
1223                                 {
1224                                         glsi32 ch = read_utf8_char_from_file(str);
1225                                         if(ch == -1)
1226                                         {
1227                                                 buf[foo] = '\0';
1228                                                 return foo - 1;
1229                                         }
1230                                         str->read_count++;
1231                                         if(is_unicode_newline(ch, str, TRUE))
1232                                         {
1233                                                 buf[foo] = '\n';
1234                                                 buf[foo + 1] = '\0';
1235                                                 return foo;
1236                                         }
1237                                         buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
1238                                 }
1239                                 buf[len] = '\0';
1240                                 return foo;
1241                         }
1242                 default:
1243                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1244                         return 0;
1245         }
1246 }
1247
1248 /**
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.
1253  *
1254  * Reads Unicode characters from @str, until either 
1255  * <inlineequation>
1256  *   <alt>@len - 1</alt>
1257  *   <mathphrase>@len - 1</mathphrase>
1258  * </inlineequation> 
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.
1261  *
1262  * Returns: The number of characters actually read, including the newline (if
1263  * there is one) but not including the terminal null.
1264  */
1265 glui32
1266 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len)
1267 {
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);
1271
1272         switch(str->type)
1273         {
1274                 case STREAM_TYPE_RESOURCE:
1275                 {
1276                         int copycount = 0;
1277                         if(str->unicode)
1278                         {
1279                                 /* Do it character-by-character */
1280                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen) 
1281                                 {
1282                                         glsi32 ch;
1283                                         if(str->binary)
1284                                                 ch = read_ucs4be_char_from_buffer(str);
1285                                         else
1286                                                 ch = read_utf8_char_from_buffer(str);
1287                                         /* Check for Unicode newline; slightly different than
1288                                         in file streams */
1289                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1290                                         {
1291                                                 buf[copycount++] = '\n';
1292                                                 break;
1293                                         }
1294                                         if(ch == 0x0D)
1295                                         {
1296                                                 if(str->ubuffer[str->mark] == 0x0A)
1297                                                         str->mark++; /* skip past next newline */
1298                                                 buf[copycount++] = '\n';
1299                                                 break;
1300                                         }
1301                                         buf[copycount++] = ch;
1302                                 }
1303                                 buf[copycount] = '\0';
1304                                 return copycount;
1305                         }
1306                         /* for text streams, fall through to the memory case */
1307                 }
1308                 case STREAM_TYPE_MEMORY:
1309                 {
1310                         int copycount = 0;
1311                         if(str->unicode)
1312                         {
1313                                 /* Do it character-by-character */
1314                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
1315                                 {
1316                                         glui32 ch = str->ubuffer[str->mark++];
1317                                         /* Check for Unicode newline; slightly different than
1318                                         in file streams */
1319                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1320                                         {
1321                                                 buf[copycount++] = '\n';
1322                                                 break;
1323                                         }
1324                                         if(ch == 0x0D)
1325                                         {
1326                                                 if(str->ubuffer[str->mark] == 0x0A)
1327                                                         str->mark++; /* skip past next newline */
1328                                                 buf[copycount++] = '\n';
1329                                                 break;
1330                                         }
1331                                         buf[copycount++] = ch;
1332                                 }
1333                                 buf[copycount] = '\0';
1334                         }
1335                         else
1336                         {
1337                                 /* No recourse to memccpy(), so do it character-by-character */
1338                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1339                                 {
1340                                         gchar ch = str->buffer[str->mark++];
1341                                         /* Check for newline */
1342                                         if(ch == '\n') /* Also check for \r and \r\n? */
1343                                         {
1344                                                 buf[copycount++] = '\n';
1345                                                 break;
1346                                         }
1347                                         buf[copycount++] = (unsigned char)ch;
1348                                 }
1349                                 buf[copycount] = 0;
1350                         }
1351                         
1352                         str->read_count += copycount;
1353                         return copycount;
1354                 }       
1355                 case STREAM_TYPE_FILE:
1356                         if(str->binary) 
1357                         {
1358                                 if(str->unicode) /* Binary file with 4-byte characters */
1359                                 {
1360                                         /* Do it character-by-character */
1361                                         int copycount;
1362                                         for(copycount = 0; copycount < len - 1; copycount++)
1363                                         {
1364                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1365                                                 if(ch == -1) 
1366                                                 {
1367                                                         buf[copycount] = 0;
1368                                                         return copycount;
1369                                                 }
1370                                                 str->read_count++;
1371                                                 if(is_unicode_newline(ch, str, FALSE))
1372                                                 {
1373                                                         buf[copycount++] = ch; /* Preserve newline types??? */
1374                                                         buf[copycount] = 0;
1375                                                         return copycount;
1376                                                 }
1377                                                 buf[copycount] = ch;
1378                                         }
1379                                         buf[len] = 0;
1380                                         return copycount;
1381                                 }
1382                                 else /* Regular binary file */
1383                                 {
1384                                         gchar *readbuffer = g_new0(gchar, len);
1385                                         ensure_file_operation(str, filemode_Read);
1386                                         if( !fgets(readbuffer, len, str->file_pointer) ) {
1387                                                 *buf = 0;
1388                                                 return 0;
1389                                         }
1390
1391                                         glui32 count = strlen(readbuffer);
1392                                         int foo;
1393                                         for(foo = 0; foo < count + 1; foo++) /* Copy terminator */
1394                                                 buf[foo] = (unsigned char)(readbuffer[foo]);
1395                                         str->read_count += count;
1396                                         return count;
1397                                 }
1398                         }
1399                         else /* Text mode is the same for Unicode and regular files */
1400                         {
1401                                 /* Do it character-by-character */
1402                                 int foo;
1403                                 for(foo = 0; foo < len - 1; foo++)
1404                                 {
1405                                         glsi32 ch = read_utf8_char_from_file(str);
1406                                         if(ch == -1)
1407                                         {
1408                                                 buf[foo] = 0;
1409                                                 return foo - 1;
1410                                         }
1411                                         str->read_count++;
1412                                         if(is_unicode_newline(ch, str, TRUE))
1413                                         {
1414                                                 buf[foo] = ch; /* Preserve newline types??? */
1415                                                 buf[foo + 1] = 0;
1416                                                 return foo;
1417                                         }
1418                                         buf[foo] = ch;
1419                                 }
1420                                 buf[len] = 0;
1421                                 return foo;
1422                         }
1423                 default:
1424                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1425                         return 0;
1426         }
1427 }
1428
1429 /*
1430  *
1431  **************** SEEKING FUNCTIONS ********************************************
1432  *
1433  */
1434
1435 /**
1436  * glk_stream_get_position:
1437  * @str: A file or memory stream.
1438  *
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.
1446  *
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
1451  * bytes.
1452  *
1453  * <note><para>
1454  *   If this bothers you, don't use binary Unicode files. I don't think they're
1455  *   good for much anyhow.
1456  * </para></note>
1457  *
1458  * glk_stream_get_position() on a window stream will always return zero.
1459  *
1460  * <note><para>
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.
1464  * </para></note>
1465  *
1466  * Returns: position of the read/write mark in @str.
1467  */
1468 glui32
1469 glk_stream_get_position(strid_t str)
1470 {
1471         VALID_STREAM(str, return 0);
1472         
1473         switch(str->type)
1474         {
1475                 case STREAM_TYPE_MEMORY:
1476                 case STREAM_TYPE_RESOURCE:
1477                         return str->mark;
1478                 case STREAM_TYPE_FILE:
1479                         return ftell(str->file_pointer);
1480                 case STREAM_TYPE_WINDOW:
1481                         return 0;
1482                 default:
1483                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1484                         return 0;
1485         }
1486 }
1487
1488 /**
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.
1493  *
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.
1497  *
1498  * It is illegal to specify a position before the beginning or after the end of
1499  * the file.
1500  *
1501  * In binary files, the mark position is exact &mdash; 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 &mdash; 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().
1510  *
1511  * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
1512  * characters are 32-bit words, or four bytes each.
1513  *
1514  * A window stream doesn't have a movable mark, so calling
1515  * glk_stream_set_position() has no effect.
1516  */
1517 void
1518 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
1519 {
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));
1523         
1524         switch(str->type)
1525         {
1526                 case STREAM_TYPE_RESOURCE:
1527                 case STREAM_TYPE_MEMORY:
1528                         switch(seekmode)
1529                         {
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;
1533                                 default:
1534                                         g_return_if_reached();
1535                                         return;
1536                         }
1537                         break;
1538                 case STREAM_TYPE_FILE:
1539                 {
1540                         int whence;
1541                         switch(seekmode)
1542                         {
1543                                 case seekmode_Start:   whence = SEEK_SET; break;
1544                                 case seekmode_Current: whence = SEEK_CUR; break;
1545                                 case seekmode_End:     whence = SEEK_END; break;
1546                                 default:
1547                                         g_return_if_reached();
1548                                         return;
1549                         }
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() */
1553                         break;
1554                 }
1555                 case STREAM_TYPE_WINDOW:
1556                         break; /* Quietly do nothing */
1557                 default:
1558                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1559                         return;
1560         }
1561 }
1562