Quit program when browser window is closed
[projects/chimara/chimara.git] / libchimara / strio.c
1 #include "charset.h"
2 #include "magic.h"
3 #include "stream.h"
4 #include <errno.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <glib.h>
8 #include <glib/gstdio.h>
9
10 /* Internal function: ensure that an fseek() is called on a file pointer in
11  between reading and writing operations, and vice versa. This will only come up
12  for ReadWrite or WriteAppend files. */
13 static void
14 ensure_file_operation(strid_t str, glui32 op)
15 {
16         if(str->lastop != 0 && str->lastop != op)
17         {
18                 long pos = ftell(str->file_pointer);
19                 if(pos == -1)
20                         WARNING_S("ftell() failed", g_strerror(errno));
21                 if(fseek(str->file_pointer, pos, SEEK_SET) != 0)
22                         WARNING_S("fseek() failed", g_strerror(errno));
23         }
24         str->lastop = op; /* Not 0, because we are about to do the operation anyway */
25 }
26
27 /*
28  *
29  **************** WRITING FUNCTIONS ********************************************
30  *
31  */
32
33 /* Internal function: write a UTF-8 string to a text buffer window's text buffer. */
34 static void
35 write_utf8_to_window_buffer(winid_t win, gchar *s)
36 {
37         if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
38         {
39                 ILLEGAL("Tried to print to a text buffer window with line input pending.");
40                 return;
41         }
42
43         // Write to the buffer  
44         g_string_append(win->buffer, s);
45 }
46         
47 /* Internal function: flush a window's text buffer to the screen. */
48 void
49 flush_window_buffer(winid_t win)
50 {
51 #ifdef DEBUG_STYLES
52         g_printf("%s\n", win->buffer->str);
53 #endif
54         if(win->type != wintype_TextBuffer && win->type != wintype_TextGrid)
55                 return;
56
57         if(win->buffer->len == 0)
58                 return;
59
60         gdk_threads_enter();
61
62         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
63
64         switch(win->type) {
65         case wintype_TextBuffer:
66         {
67                 GtkTextIter start, end;
68                 gtk_text_buffer_get_end_iter(buffer, &end);
69                 gint start_offset;
70
71                 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
72
73                 GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default");
74                 GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style);
75                 GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->glk_style);
76
77                 start_offset = gtk_text_iter_get_offset(&end);
78                 gtk_text_buffer_insert(buffer, &end, win->buffer->str, -1);
79                 gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
80
81                 // Player's style overrides
82                 gtk_text_buffer_apply_tag(buffer, style_tag, &start, &end);
83
84                 // GLK Program's style overrides
85                 gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &end);
86
87                 // Default style
88                 gtk_text_buffer_apply_tag(buffer, default_tag, &start, &end);
89
90                 // Link style overrides
91                 if(win->window_stream->hyperlink_mode) {
92                         GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink");
93                         GtkTextTag *link_tag = win->current_hyperlink->tag;
94                         gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &end);
95                         gtk_text_buffer_apply_tag(buffer, link_tag, &start, &end);
96                 }
97
98                 // GLK Program's style overrides using garglk_set_zcolors()
99                 if(win->zcolor != NULL) {
100                         gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &end);
101                 }
102
103                 // GLK Program's style overrides using garglk_set_reversevideo()
104                 if(win->zcolor_reversed != NULL) {
105                         gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &end);
106                 }
107
108                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
109                 g_assert(glk);
110                 g_signal_emit_by_name(glk, "text-buffer-output", win->rock, win->buffer->str);
111         }
112                 break;
113
114         case wintype_TextGrid:
115         {
116                 /* Number of characters to insert */
117                 glong length = win->buffer->len;
118                 glong chars_left = length;
119                 
120                 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
121                 
122                 /* Get cursor position */
123                 GtkTextIter start, insert;
124                 gint start_offset;
125
126                 gtk_text_buffer_get_iter_at_mark(buffer, &insert, cursor);
127                 /* Spaces available on this line */
128                 gint available_space = win->width - gtk_text_iter_get_line_offset(&insert);
129                 
130                 GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
131
132                 GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default");
133                 GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style);
134                 GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->glk_style);
135                 GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink");
136
137                 while(chars_left > available_space && !gtk_text_iter_is_end(&insert))
138                 {
139                         GtkTextIter end = insert;
140                         gtk_text_iter_forward_to_line_end(&end);
141                         gtk_text_buffer_delete(buffer, &insert, &end);
142
143                         start_offset = gtk_text_iter_get_offset(&insert);
144                         gtk_text_buffer_insert(buffer, &insert, win->buffer->str + (length - chars_left), available_space);
145                         gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
146
147                         // Default style
148                         gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
149
150                         // Player's style overrides
151                         gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
152
153                         // GLK Program's style overrides
154                         gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
155
156                         // Link style overrides
157                         if(win->window_stream->hyperlink_mode) {
158                                 GtkTextTag *link_tag = win->current_hyperlink->tag;
159                                 gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &insert);
160                                 gtk_text_buffer_apply_tag(buffer, link_tag, &start, &insert);
161                         }
162
163                         // GLK Program's style overrides using garglk_set_zcolors()
164                         if(win->zcolor != NULL)
165                                 gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &insert);
166
167                         // GLK Program's style overrides using garglk_set_reversevideo()
168                         if(win->zcolor_reversed != NULL) {
169                                 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &insert);
170                         }
171
172                         chars_left -= available_space;
173                         gtk_text_iter_forward_line(&insert);
174                         available_space = win->width;
175                 }
176                 if(!gtk_text_iter_is_end(&insert))
177                 {
178                         GtkTextIter end = insert;
179                         gtk_text_iter_forward_chars(&end, chars_left);
180                         gtk_text_buffer_delete(buffer, &insert, &end);
181
182                         start_offset = gtk_text_iter_get_offset(&insert);
183                         gtk_text_buffer_insert(buffer, &insert, win->buffer->str + (length - chars_left), -1);
184                         gtk_text_buffer_get_iter_at_offset(buffer, &start, start_offset);
185
186                         // Default style
187                         gtk_text_buffer_apply_tag(buffer, default_tag, &start, &insert);
188
189                         // Player's style overrides
190                         gtk_text_buffer_apply_tag(buffer, style_tag, &start, &insert);
191
192                         // GLK Program's style overrides
193                         gtk_text_buffer_apply_tag(buffer, glk_style_tag, &start, &insert);
194
195                         // Link style overrides
196                         if(win->window_stream->hyperlink_mode) {
197                                 GtkTextTag *link_tag = win->current_hyperlink->tag;
198                                 gtk_text_buffer_apply_tag(buffer, link_style_tag, &start, &insert);
199                                 gtk_text_buffer_apply_tag(buffer, link_tag, &start, &insert);
200                         }
201
202                         // GLK Program's style overrides using garglk_set_zcolors()
203                         if(win->zcolor != NULL)
204                                 gtk_text_buffer_apply_tag(buffer, win->zcolor, &start, &insert);
205
206                         // GLK Program's style overrides using garglk_set_reversevideo()
207                         if(win->zcolor_reversed != NULL) {
208                                 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, &start, &insert);
209                         }
210                 }
211                 
212                 gtk_text_buffer_move_mark(buffer, cursor, &start);
213         }
214                 break;
215         }
216
217         gdk_threads_leave();
218
219         g_string_truncate(win->buffer, 0);
220 }
221
222 /* Internal function: write a Latin-1 buffer with length to a stream. */
223 static void
224 write_buffer_to_stream(strid_t str, gchar *buf, glui32 len)
225 {
226         switch(str->type)
227         {
228                 case STREAM_TYPE_WINDOW:
229                         /* Each window type has a different way of printing to it */
230                         switch(str->window->type)
231                         {
232                                 /* Printing to these windows' streams does nothing */
233                                 case wintype_Blank:
234                                 case wintype_Pair:
235                                 case wintype_Graphics:
236                                         str->write_count += len;
237                                         break;
238                                         
239                             /* Text grid/buffer windows */
240                             case wintype_TextGrid:
241                                 {
242                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
243                                 if(utf8 != NULL) {
244                                                 /* Deal with newlines */
245                                                 int i;
246                                                 gchar *line = utf8;
247                                                 for(i=0; i<len; i++) {
248                                                         if(utf8[i] == '\n') {
249                                                                 utf8[i] = '\0';
250                                                                 write_utf8_to_window_buffer(str->window, line);
251                                                                 flush_window_buffer(str->window);
252
253                                                                 /* Move cursor position forward to the next line */
254                                                                 gdk_threads_enter();
255                                                                 GtkTextIter cursor_pos;
256                                                                 GtkTextView *textview = GTK_TEXT_VIEW(str->window->widget);
257                                                                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
258                                                                 GtkTextMark *cursor_mark = gtk_text_buffer_get_mark(buffer, "cursor_position");
259
260                                                             gtk_text_buffer_get_iter_at_mark( buffer, &cursor_pos, cursor_mark);
261                                                                 gtk_text_view_forward_display_line(textview, &cursor_pos);
262                                                                 gtk_text_view_backward_display_line_start(textview, &cursor_pos);
263                                                                 gtk_text_buffer_move_mark(buffer, cursor_mark, &cursor_pos);
264                                                                 gdk_threads_leave();
265
266                                                                 line = utf8 + (i < len-1 ? (i+1):(len-1));
267                                                         }
268                                                 }
269                                                                 
270                                                 /* No more newlines left. */
271                                                 write_utf8_to_window_buffer(str->window, line);
272                                                 g_free(utf8);
273                                         }
274
275                                         str->write_count += len;
276                                 }
277                                         break;
278
279                                 case wintype_TextBuffer:
280                             {
281                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
282                                 if(utf8 != NULL) {
283                                                 write_utf8_to_window_buffer(str->window, utf8);
284                                                 g_free(utf8);
285                                         }
286                                 }       
287                                         str->write_count += len;
288                                         break;
289                                 default:
290                                         ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
291                         }
292                         
293                         /* Now write the same buffer to the window's echo stream */
294                         if(str->window->echo_stream != NULL)
295                                 write_buffer_to_stream(str->window->echo_stream, buf, len);
296                         
297                         break;
298                         
299                 case STREAM_TYPE_MEMORY:
300                         if(str->unicode && str->ubuffer)
301                         {
302                                 int foo = 0;
303                                 while(str->mark < str->buflen && foo < len)
304                                         str->ubuffer[str->mark++] = (unsigned char)buf[foo++];
305                         }
306                         if(!str->unicode && str->buffer)
307                         {
308                                 int copycount = MIN(len, str->buflen - str->mark);
309                                 memmove(str->buffer + str->mark, buf, copycount);
310                                 str->mark += copycount;
311                         }
312
313                         /* Move the EOF marker if we wrote past it */
314                         if(str->mark > str->endmark)
315                                 str->endmark = str->mark;
316
317                         str->write_count += len;
318                         break;
319                         
320                 case STREAM_TYPE_FILE:
321                         if(str->binary) 
322                         {
323                                 if(str->unicode) 
324                                 {
325                                         gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len);
326                                         ensure_file_operation(str, filemode_Write);
327                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
328                                         g_free(writebuffer);
329                                 } 
330                                 else /* Regular file */
331                                 {
332                                         ensure_file_operation(str, filemode_Write);
333                                         fwrite(buf, sizeof(gchar), len, str->file_pointer);
334                                 }
335                         }
336                         else /* Text mode is the same for Unicode and regular files */
337                         {
338                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
339                                 if(utf8 != NULL)
340                                 {
341                                         ensure_file_operation(str, filemode_Write);
342                                         g_fprintf(str->file_pointer, "%s", utf8);
343                                         g_free(utf8);
344                                 }
345                         }
346                         
347                         str->write_count += len;
348                         break;
349                 default:
350                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
351         }
352 }
353
354 /* Internal function: write a Unicode buffer with length to a stream. */
355 static void
356 write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len)
357 {
358         switch(str->type)
359         {
360                 case STREAM_TYPE_WINDOW:
361                         /* Each window type has a different way of printing to it */
362                         switch(str->window->type)
363                         {
364                                 /* Printing to these windows' streams does nothing */
365                                 case wintype_Blank:
366                                 case wintype_Pair:
367                                 case wintype_Graphics:
368                                         str->write_count += len;
369                                         break;
370                                         
371                             /* Text grid/buffer windows */
372                             case wintype_TextGrid:
373                             case wintype_TextBuffer:
374                             {
375                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
376                                 if(utf8 != NULL) {
377                                                 write_utf8_to_window_buffer(str->window, utf8);
378                                                 g_free(utf8);
379                                         }
380                                 }       
381                                         str->write_count += len;
382                                         break;
383                                 default:
384                                         ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
385                         }
386                         
387                         /* Now write the same buffer to the window's echo stream */
388                         if(str->window->echo_stream != NULL)
389                                 write_buffer_to_stream_uni(str->window->echo_stream, buf, len);
390                         
391                         break;
392                         
393                 case STREAM_TYPE_MEMORY:
394                         if(str->unicode && str->ubuffer)
395                         {
396                                 int copycount = MIN(len, str->buflen - str->mark);
397                                 memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32));
398                                 str->mark += copycount;
399                         }
400                         if(!str->unicode && str->buffer)
401                         {
402                                 gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
403                                 int copycount = MIN(len, str->buflen - str->mark);
404                                 memmove(str->buffer + str->mark, latin1, copycount);
405                                 g_free(latin1);
406                                 str->mark += copycount;
407                         }
408
409                         /* Move the EOF marker if we wrote past it */
410                         if(str->mark > str->endmark)
411                                 str->endmark = str->mark;
412
413                         str->write_count += len;
414                         break;
415                         
416                 case STREAM_TYPE_FILE:
417                         if(str->binary) 
418                         {
419                                 if(str->unicode) 
420                                 {
421                                         gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len);
422                                         ensure_file_operation(str, filemode_Write);
423                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
424                                         g_free(writebuffer);
425                                 } 
426                                 else /* Regular file */
427                                 {
428                                         gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
429                                         ensure_file_operation(str, filemode_Write);
430                                         fwrite(latin1, sizeof(gchar), len, str->file_pointer);
431                                         g_free(latin1);
432                                 }
433                         }
434                         else /* Text mode is the same for Unicode and regular files */
435                         {
436                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
437                                 if(utf8 != NULL) 
438                                 {
439                                         ensure_file_operation(str, filemode_Write);
440                                         g_fprintf(str->file_pointer, "%s", utf8);
441                                         g_free(utf8);
442                                 }
443                         }
444                         
445                         str->write_count += len;
446                         break;
447                 default:
448                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
449         }
450 }
451
452 /**
453  * glk_put_char_stream:
454  * @str: An output stream.
455  * @ch: A character in Latin-1 encoding.
456  *
457  * The same as glk_put_char(), except that you specify a stream @str to print 
458  * to, instead of using the current stream. It is illegal for @str to be %NULL,
459  * or an input-only stream.
460  */
461 void
462 glk_put_char_stream(strid_t str, unsigned char ch)
463 {
464         VALID_STREAM(str, return);
465         g_return_if_fail(str->file_mode != filemode_Read);
466         
467         write_buffer_to_stream(str, (gchar *)&ch, 1);
468 }
469
470 /**
471  * glk_put_char_stream_uni:
472  * @str: An output stream.
473  * @ch: A Unicode code point.
474  *
475  * The same as glk_put_char_uni(), except that you specify a stream @str to
476  * print to, instead of using the current stream. It is illegal for @str to be 
477  * %NULL, or an input-only stream.
478  */
479 void
480 glk_put_char_stream_uni(strid_t str, glui32 ch)
481 {
482         VALID_STREAM(str, return);
483         g_return_if_fail(str->file_mode != filemode_Read);
484         
485         write_buffer_to_stream_uni(str, &ch, 1);
486 }
487
488 /**
489  * glk_put_string_stream:
490  * @str: An output stream.
491  * @s: A null-terminated string in Latin-1 encoding.
492  *
493  * The same as glk_put_string(), except that you specify a stream @str to print 
494  * to, instead of using the current stream. It is illegal for @str to be %NULL,
495  * or an input-only stream.
496  */
497 void
498 glk_put_string_stream(strid_t str, char *s)
499 {
500         VALID_STREAM(str, return);
501         if(*s == 0)
502                 return;
503
504         g_return_if_fail(str->file_mode != filemode_Read);
505
506         write_buffer_to_stream(str, s, strlen(s));
507 }
508
509 /**
510  * glk_put_string_stream_uni:
511  * @str: An output stream.
512  * @s: A null-terminated array of Unicode code points.
513  *
514  * The same as glk_put_string_uni(), except that you specify a stream @str to
515  * print to, instead of using the current stream. It is illegal for @str to be 
516  * %NULL, or an input-only stream.
517  */
518 void
519 glk_put_string_stream_uni(strid_t str, glui32 *s)
520 {
521         VALID_STREAM(str, return);
522         if(*s == 0)
523                 return;
524
525         g_return_if_fail(str->file_mode != filemode_Read);
526         
527         /* An impromptu strlen() for glui32 arrays */
528         glong len = 0;
529         glui32 *ptr = s;
530         while(*ptr++)
531                 len++;
532         write_buffer_to_stream_uni(str, s, len);
533 }
534
535 /**
536  * glk_put_buffer_stream:
537  * @str: An output stream.
538  * @buf: An array of characters in Latin-1 encoding.
539  * @len: Length of @buf.
540  *
541  * The same as glk_put_buffer(), except that you specify a stream @str to print 
542  * to, instead of using the current stream. It is illegal for @str to be %NULL,
543  * or an input-only stream.
544  */
545 void
546 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
547 {
548         VALID_STREAM(str, return);
549         if(len == 0)
550                 return;
551
552         g_return_if_fail(str->file_mode != filemode_Read);
553         
554         write_buffer_to_stream(str, buf, len);
555 }
556
557 /**
558  * glk_put_buffer_stream_uni:
559  * @str: An output stream.
560  * @buf: An array of Unicode code points.
561  * @len: Length of @buf.
562  *
563  * The same as glk_put_buffer_uni(), except that you specify a stream @str to
564  * print to, instead of using the current stream. It is illegal for @str to be 
565  * %NULL, or an input-only stream.
566  */
567 void
568 glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
569 {
570         VALID_STREAM(str, return);
571         if(len == 0)
572                 return;
573
574         g_return_if_fail(str->file_mode != filemode_Read);
575         
576         write_buffer_to_stream_uni(str, buf, len);
577 }
578
579 /*
580  *
581  **************** READING FUNCTIONS ********************************************
582  *
583  */
584
585 /* Internal function: Read one big-endian four-byte character from file fp and
586 return it as a Unicode code point, or -1 on EOF */
587 static glsi32
588 read_ucs4be_char_from_file(strid_t str)
589 {
590         unsigned char readbuffer[4];
591         ensure_file_operation(str, filemode_Read);
592         if(fread(readbuffer, sizeof(unsigned char), 4, str->file_pointer) < 4)
593                 return -1; /* EOF */
594         return
595                 readbuffer[0] << 24 | 
596                 readbuffer[1] << 16 | 
597                 readbuffer[2] << 8  | 
598                 readbuffer[3];
599 }
600
601 /* Internal function: Read one UTF-8 character, which may be more than one byte,
602 from file fp and return it as a Unicode code point, or -1 on EOF */
603 static glsi32
604 read_utf8_char_from_file(strid_t str)
605 {
606         gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
607         int foo;
608         gunichar charresult = (gunichar)-2;
609         ensure_file_operation(str, filemode_Read);
610         for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) 
611         {
612                 int ch = fgetc(str->file_pointer);
613                 if(ch == EOF)
614                         return -1;
615                 readbuffer[foo] = (gchar)ch;
616                 charresult = g_utf8_get_char_validated(readbuffer, foo + 1);
617                 /* charresult is -1 if invalid, -2 if incomplete, and the unicode code
618                 point otherwise */
619         }
620         /* Silently return unknown characters as 0xFFFD, Replacement Character */
621         if(charresult == (gunichar)-1 || charresult == (gunichar)-2) 
622                 return 0xFFFD;
623         return charresult;
624 }
625
626 /* Internal function: Tell whether this code point is a Unicode newline. The
627 file pointer and eight-bit flag are included in case the newline is a CR 
628 (U+000D). If the next character is LF (U+000A) then it also belongs to the
629 newline. */
630 static gboolean
631 is_unicode_newline(glsi32 ch, strid_t str, gboolean utf8)
632 {
633         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
634                 return TRUE;
635         if(ch == 0x0D) {
636                 glsi32 ch2 = utf8? read_utf8_char_from_file(str) :
637                         read_ucs4be_char_from_file(str);
638                 if(ch2 != 0x0A) {
639                         if(fseek(str->file_pointer, utf8? -1 : -4, SEEK_CUR) == -1);
640                                 WARNING_S("Seek failed on stream", g_strerror(errno) );
641                         str->lastop = 0; /* can read or write after a seek */
642                 }
643                 return TRUE;
644         }
645         return FALSE;
646 }
647
648 /* Internal function: Read one character from a stream. Returns a value which
649  can be returned unchanged by glk_get_char_stream_uni(), but 
650  glk_get_char_stream() must replace high values by the placeholder character. */
651 static glsi32
652 get_char_stream_common(strid_t str)
653 {
654         switch(str->type)
655         {
656                 case STREAM_TYPE_MEMORY:
657                         if(str->unicode)
658                         {
659                                 if(!str->ubuffer || str->mark >= str->buflen)
660                                         return -1;
661                                 glui32 ch = str->ubuffer[str->mark++];
662                                 str->read_count++;
663                                 return ch;
664                         }
665                         else
666                         {
667                                 if(!str->buffer || str->mark >= str->buflen)
668                                         return -1;
669                                 unsigned char ch = str->buffer[str->mark++];
670                                 str->read_count++;
671                                 return ch;
672                         }
673                         break;
674                         
675                 case STREAM_TYPE_FILE:
676                         if(str->binary) 
677                         {
678                                 if(str->unicode) 
679                                 {
680                                         glsi32 ch = read_ucs4be_char_from_file(str);
681                                         if(ch == -1)
682                                                 return -1;
683                                         str->read_count++;
684                                         return ch;
685                                 }
686                                 else /* Regular file */
687                                 {
688                                         ensure_file_operation(str, filemode_Read);
689                                         int ch = fgetc(str->file_pointer);
690                                         if(ch == EOF)
691                                                 return -1;
692                                         
693                                         str->read_count++;
694                                         return ch;
695                                 }
696                         }
697                         else /* Text mode is the same for Unicode and regular files */
698                         {
699                                 glsi32 ch = read_utf8_char_from_file(str);
700                                 if(ch == -1)
701                                         return -1;
702                                         
703                                 str->read_count++;
704                                 return ch;
705                         }
706                 default:
707                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
708                         return -1;
709         }
710 }
711
712 /**
713  * glk_get_char_stream:
714  * @str: An input stream.
715  *
716  * Reads one character from the stream @str. (There is no notion of a
717  * <quote>current input stream.</quote>) It is illegal for @str to be %NULL, or
718  * an output-only stream.
719  *
720  * The result will be between 0 and 255. As with all basic text functions, Glk
721  * assumes the Latin-1 encoding. See <link 
722  * linkend="chimara-Character-Encoding">Character Encoding</link>. If the end
723  * of the stream has been reached, the result will be -1. 
724  *
725  * <note><para>
726  *   Note that high-bit characters (128..255) are <emphasis>not</emphasis>
727  *   returned as negative numbers.
728  * </para></note>
729  *
730  * If the stream contains Unicode data &mdash; for example, if it was created
731  * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() &mdash; then
732  * characters beyond 255 will be returned as 0x3F (<code>"?"</code>).
733  *
734  * It is usually more efficient to read several characters at once with
735  * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling
736  * glk_get_char_stream() several times.
737  *
738  * Returns: A character value between 0 and 255, or -1 on end of stream.
739  */
740 glsi32
741 glk_get_char_stream(strid_t str)
742 {
743         VALID_STREAM(str, return -1);
744         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
745         
746         glsi32 ch = get_char_stream_common(str);
747         return (ch > 0xFF)? PLACEHOLDER : ch;
748 }
749
750 /**
751  * glk_get_char_stream_uni:
752  * @str: An input stream.
753  *
754  * Reads one character from the stream @str. If the end of the stream has been
755  * reached, the result will be -1.
756  *
757  * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream.
758  */
759 glsi32
760 glk_get_char_stream_uni(strid_t str)
761 {
762         VALID_STREAM(str, return -1);
763         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
764         
765         return get_char_stream_common(str);
766 }
767
768 /**
769  * glk_get_buffer_stream:
770  * @str: An input stream.
771  * @buf: A buffer with space for at least @len characters.
772  * @len: The number of characters to read.
773  *
774  * Reads @len characters from @str, unless the end of stream is reached first.
775  * No terminal null is placed in the buffer.
776  *
777  * Returns: The number of characters actually read.
778  */
779 glui32
780 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
781 {
782         VALID_STREAM(str, return 0);
783         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
784         g_return_val_if_fail(buf != NULL, 0);
785         
786         switch(str->type)
787         {
788                 case STREAM_TYPE_MEMORY:
789                 {
790                         int copycount = 0;
791                         if(str->unicode)
792                         {
793                                 while(copycount < len && str->ubuffer && str->mark < str->buflen) 
794                                 {
795                                         glui32 ch = str->ubuffer[str->mark++];
796                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
797                                 }
798                         }
799                         else
800                         {
801                                 if(str->buffer) /* if not, copycount stays 0 */
802                                         copycount = MIN(len, str->buflen - str->mark);
803                                 memmove(buf, str->buffer + str->mark, copycount);
804                                 str->mark += copycount;
805                         }
806
807                         str->read_count += copycount;           
808                         return copycount;
809                 }       
810                 case STREAM_TYPE_FILE:
811                         if(str->binary) 
812                         {
813                                 if(str->unicode) /* Binary file with 4-byte characters */
814                                 {
815                                         /* Read len characters of 4 bytes each */
816                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
817                                         ensure_file_operation(str, filemode_Read);
818                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
819                                         /* If there was an incomplete character */
820                                         if(count % 4 != 0) 
821                                         {
822                                                 count -= count % 4;
823                                                 WARNING("Incomplete character in binary Unicode file");
824                                         }
825                                         
826                                         int foo;
827                                         for(foo = 0; foo < count; foo += 4)
828                                         {
829                                                 glsi32 ch = readbuffer[foo] << 24
830                                                         | readbuffer[foo + 1] << 16
831                                                         | readbuffer[foo + 2] << 8
832                                                         | readbuffer[foo + 3];
833                                                 buf[foo / 4] = (ch > 255)? 0x3F : (char)ch;
834                                         }
835                                         g_free(readbuffer);
836                                         str->read_count += count / 4;
837                                         return count / 4;
838                                 }
839                                 else /* Regular binary file */
840                                 {
841                                         ensure_file_operation(str, filemode_Read);
842                                         size_t count = fread(buf, sizeof(char), len, str->file_pointer);
843                                         str->read_count += count;
844                                         return count;
845                                 }
846                         }
847                         else /* Text mode is the same for Unicode and regular files */
848                         {
849                                 /* Do it character-by-character */
850                                 int foo;
851                                 for(foo = 0; foo < len; foo++)
852                                 {
853                                         glsi32 ch = read_utf8_char_from_file(str);
854                                         if(ch == -1)
855                                                 break;
856                                         str->read_count++;
857                                         buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
858                                 }
859                                 return foo;
860                         }
861                 default:
862                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
863                         return 0;
864         }
865 }
866
867 /**
868  * glk_get_buffer_stream_uni:
869  * @str: An input stream.
870  * @buf: A buffer with space for at least @len Unicode code points.
871  * @len: The number of characters to read.
872  *
873  * Reads @len Unicode characters from @str, unless the end of stream is reached 
874  * first. No terminal null is placed in the buffer.
875  *
876  * Returns: The number of Unicode characters actually read.
877  */
878 glui32
879 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
880 {
881         VALID_STREAM(str, return 0);
882         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
883         g_return_val_if_fail(buf != NULL, 0);
884         
885         switch(str->type)
886         {
887                 case STREAM_TYPE_MEMORY:
888                 {
889                         int copycount = 0;
890                         if(str->unicode)
891                         {
892                                 if(str->ubuffer) /* if not, copycount stays 0 */
893                                         copycount = MIN(len, str->buflen - str->mark);
894                                 memmove(buf, str->ubuffer + str->mark, copycount * 4);
895                                 str->mark += copycount;
896                         }
897                         else
898                         {
899                                 while(copycount < len && str->buffer && str->mark < str->buflen)
900                                 {
901                                         unsigned char ch = str->buffer[str->mark++];
902                                         buf[copycount++] = ch;
903                                 }
904                         }
905
906                         str->read_count += copycount;           
907                         return copycount;
908                 }       
909                 case STREAM_TYPE_FILE:
910                         if(str->binary) 
911                         {
912                                 if(str->unicode) /* Binary file with 4-byte characters */
913                                 {
914                                         /* Read len characters of 4 bytes each */
915                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
916                                         ensure_file_operation(str, filemode_Read);
917                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
918                                         /* If there was an incomplete character */
919                                         if(count % 4 != 0) 
920                                         {
921                                                 count -= count % 4;
922                                                 WARNING("Incomplete character in binary Unicode file");
923                                         }
924                                         
925                                         int foo;
926                                         for(foo = 0; foo < count; foo += 4)
927                                                 buf[foo / 4] = readbuffer[foo] << 24
928                                                         | readbuffer[foo + 1] << 16
929                                                         | readbuffer[foo + 2] << 8
930                                                         | readbuffer[foo + 3];
931                                         g_free(readbuffer);
932                                         str->read_count += count / 4;
933                                         return count / 4;
934                                 }
935                                 else /* Regular binary file */
936                                 {
937                                         unsigned char *readbuffer = g_new0(unsigned char, len);
938                                         ensure_file_operation(str, filemode_Read);
939                                         size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer);
940                                         int foo;
941                                         for(foo = 0; foo < count; foo++)
942                                                 buf[foo] = readbuffer[foo];
943                                         g_free(readbuffer);
944                                         str->read_count += count;
945                                         return count;
946                                 }
947                         }
948                         else /* Text mode is the same for Unicode and regular files */
949                         {
950                                 /* Do it character-by-character */
951                                 int foo;
952                                 for(foo = 0; foo < len; foo++)
953                                 {
954                                         glsi32 ch = read_utf8_char_from_file(str);
955                                         if(ch == -1)
956                                                 break;
957                                         str->read_count++;
958                                         buf[foo] = ch;
959                                 }
960                                 return foo;
961                         }
962                 default:
963                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
964                         return 0;
965         }
966 }
967
968 /**
969  * glk_get_line_stream:
970  * @str: An input stream.
971  * @buf: A buffer with space for at least @len characters.
972  * @len: The number of characters to read, plus one.
973  *
974  * Reads characters from @str, until either 
975  * <inlineequation>
976  *   <alt>@len - 1</alt>
977  *   <mathphrase>@len - 1</mathphrase>
978  * </inlineequation>
979  * characters have been read or a newline has been read. It then puts a
980  * terminal null (<code>'\0'</code>) character on
981  * the end. It returns the number of characters actually read, including the
982  * newline (if there is one) but not including the terminal null.
983  *
984  * Returns: The number of characters actually read.
985  */
986 glui32
987 glk_get_line_stream(strid_t str, char *buf, glui32 len)
988 {
989         VALID_STREAM(str, return 0);
990         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
991         g_return_val_if_fail(buf != NULL, 0);
992
993         switch(str->type)
994         {
995                 case STREAM_TYPE_MEMORY:
996                 {
997                         int copycount = 0;
998                         if(str->unicode)
999                         {
1000                                 /* Do it character-by-character */
1001                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
1002                                 {
1003                                         glui32 ch = str->ubuffer[str->mark++];
1004                                         /* Check for Unicode newline; slightly different than
1005                                         in file streams */
1006                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1007                                         {
1008                                                 buf[copycount++] = '\n';
1009                                                 break;
1010                                         }
1011                                         if(ch == 0x0D)
1012                                         {
1013                                                 if(str->ubuffer[str->mark] == 0x0A)
1014                                                         str->mark++; /* skip past next newline */
1015                                                 buf[copycount++] = '\n';
1016                                                 break;
1017                                         }
1018                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1019                                 }
1020                                 buf[copycount] = '\0';
1021                         }
1022                         else
1023                         {
1024                                 if(str->buffer) /* if not, copycount stays 0 */
1025                                         copycount = MIN(len - 1, str->buflen - str->mark);
1026                                 char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount);
1027                                 if(endptr) /* newline was found */
1028                                         copycount = endptr - buf; /* Real copy count */
1029                                 buf[copycount] = '\0';
1030                                 str->mark += copycount;
1031                         }
1032                         
1033                         str->read_count += copycount;
1034                         return copycount;
1035                 }       
1036                 case STREAM_TYPE_FILE:
1037                         if(str->binary) 
1038                         {
1039                                 if(str->unicode) /* Binary file with 4-byte characters */
1040                                 {
1041                                         /* Do it character-by-character */
1042                                         int copycount;
1043                                         for(copycount = 0; copycount < len - 1; copycount++)
1044                                         {
1045                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1046                                                 if(ch == -1) 
1047                                                 {
1048                                                         buf[copycount] = '\0';
1049                                                         return copycount;
1050                                                 }
1051                                                 str->read_count++;
1052                                                 if(is_unicode_newline(ch, str, FALSE))
1053                                                 {
1054                                                         buf[copycount++] = '\n';
1055                                                         buf[copycount] = '\0';
1056                                                         return copycount;
1057                                                 }
1058                                                 buf[copycount] = (ch > 0xFF)? '?' : (char)ch;
1059                                         }
1060                                         buf[len] = '\0';
1061                                         return copycount;
1062                                 }
1063                                 else /* Regular binary file */
1064                                 {
1065                                         ensure_file_operation(str, filemode_Read);
1066                                         if( !fgets(buf, len, str->file_pointer) ) {
1067                                                 *buf = 0;
1068                                                 return 0;
1069                                         }
1070
1071                                         int nread = strlen(buf);
1072                                         str->read_count += nread;
1073                                         return nread;
1074                                 }
1075                         }
1076                         else /* Text mode is the same for Unicode and regular files */
1077                         {
1078                                 /* Do it character-by-character */
1079                                 int foo;
1080                                 for(foo = 0; foo < len - 1; foo++)
1081                                 {
1082                                         glsi32 ch = read_utf8_char_from_file(str);
1083                                         if(ch == -1)
1084                                         {
1085                                                 buf[foo] = '\0';
1086                                                 return foo - 1;
1087                                         }
1088                                         str->read_count++;
1089                                         if(is_unicode_newline(ch, str, TRUE))
1090                                         {
1091                                                 buf[foo] = '\n';
1092                                                 buf[foo + 1] = '\0';
1093                                                 return foo;
1094                                         }
1095                                         buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
1096                                 }
1097                                 buf[len] = '\0';
1098                                 return foo;
1099                         }
1100                 default:
1101                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1102                         return 0;
1103         }
1104 }
1105
1106 /**
1107  * glk_get_line_stream_uni:
1108  * @str: An input stream.
1109  * @buf: A buffer with space for at least @len Unicode code points.
1110  * @len: The number of characters to read, plus one.
1111  *
1112  * Reads Unicode characters from @str, until either 
1113  * <inlineequation>
1114  *   <alt>@len - 1</alt>
1115  *   <mathphrase>@len - 1</mathphrase>
1116  * </inlineequation> 
1117  * Unicode characters have been read or a newline has been read. It then puts a
1118  * terminal null (a zero value) on the end.
1119  *
1120  * Returns: The number of characters actually read, including the newline (if
1121  * there is one) but not including the terminal null.
1122  */
1123 glui32
1124 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len)
1125 {
1126         VALID_STREAM(str, return 0);
1127         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
1128         g_return_val_if_fail(buf != NULL, 0);
1129
1130         switch(str->type)
1131         {
1132                 case STREAM_TYPE_MEMORY:
1133                 {
1134                         int copycount = 0;
1135                         if(str->unicode)
1136                         {
1137                                 /* Do it character-by-character */
1138                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
1139                                 {
1140                                         glui32 ch = str->ubuffer[str->mark++];
1141                                         /* Check for Unicode newline; slightly different than
1142                                         in file streams */
1143                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1144                                         {
1145                                                 buf[copycount++] = '\n';
1146                                                 break;
1147                                         }
1148                                         if(ch == 0x0D)
1149                                         {
1150                                                 if(str->ubuffer[str->mark] == 0x0A)
1151                                                         str->mark++; /* skip past next newline */
1152                                                 buf[copycount++] = '\n';
1153                                                 break;
1154                                         }
1155                                         buf[copycount++] = ch;
1156                                 }
1157                                 buf[copycount] = '\0';
1158                         }
1159                         else
1160                         {
1161                                 /* No recourse to memccpy(), so do it character-by-character */
1162                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1163                                 {
1164                                         gchar ch = str->buffer[str->mark++];
1165                                         /* Check for newline */
1166                                         if(ch == '\n') /* Also check for \r and \r\n? */
1167                                         {
1168                                                 buf[copycount++] = '\n';
1169                                                 break;
1170                                         }
1171                                         buf[copycount++] = (unsigned char)ch;
1172                                 }
1173                                 buf[copycount] = 0;
1174                         }
1175                         
1176                         str->read_count += copycount;
1177                         return copycount;
1178                 }       
1179                 case STREAM_TYPE_FILE:
1180                         if(str->binary) 
1181                         {
1182                                 if(str->unicode) /* Binary file with 4-byte characters */
1183                                 {
1184                                         /* Do it character-by-character */
1185                                         int copycount;
1186                                         for(copycount = 0; copycount < len - 1; copycount++)
1187                                         {
1188                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1189                                                 if(ch == -1) 
1190                                                 {
1191                                                         buf[copycount] = 0;
1192                                                         return copycount;
1193                                                 }
1194                                                 str->read_count++;
1195                                                 if(is_unicode_newline(ch, str, FALSE))
1196                                                 {
1197                                                         buf[copycount++] = ch; /* Preserve newline types??? */
1198                                                         buf[copycount] = 0;
1199                                                         return copycount;
1200                                                 }
1201                                                 buf[copycount] = ch;
1202                                         }
1203                                         buf[len] = 0;
1204                                         return copycount;
1205                                 }
1206                                 else /* Regular binary file */
1207                                 {
1208                                         gchar *readbuffer = g_new0(gchar, len);
1209                                         ensure_file_operation(str, filemode_Read);
1210                                         if( !fgets(readbuffer, len, str->file_pointer) ) {
1211                                                 *buf = 0;
1212                                                 return 0;
1213                                         }
1214
1215                                         glui32 count = strlen(readbuffer);
1216                                         int foo;
1217                                         for(foo = 0; foo < count + 1; foo++) /* Copy terminator */
1218                                                 buf[foo] = (unsigned char)(readbuffer[foo]);
1219                                         str->read_count += count;
1220                                         return count;
1221                                 }
1222                         }
1223                         else /* Text mode is the same for Unicode and regular files */
1224                         {
1225                                 /* Do it character-by-character */
1226                                 int foo;
1227                                 for(foo = 0; foo < len - 1; foo++)
1228                                 {
1229                                         glsi32 ch = read_utf8_char_from_file(str);
1230                                         if(ch == -1)
1231                                         {
1232                                                 buf[foo] = 0;
1233                                                 return foo - 1;
1234                                         }
1235                                         str->read_count++;
1236                                         if(is_unicode_newline(ch, str, TRUE))
1237                                         {
1238                                                 buf[foo] = ch; /* Preserve newline types??? */
1239                                                 buf[foo + 1] = 0;
1240                                                 return foo;
1241                                         }
1242                                         buf[foo] = ch;
1243                                 }
1244                                 buf[len] = 0;
1245                                 return foo;
1246                         }
1247                 default:
1248                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1249                         return 0;
1250         }
1251 }
1252
1253 /*
1254  *
1255  **************** SEEKING FUNCTIONS ********************************************
1256  *
1257  */
1258
1259 /**
1260  * glk_stream_get_position:
1261  * @str: A file or memory stream.
1262  *
1263  * Returns the position of the read/write mark in @str. For memory streams and
1264  * binary file streams, this is exactly the number of characters read or written
1265  * from the beginning of the stream (unless you have moved the mark with
1266  * glk_stream_set_position().) For text file streams, matters are more 
1267  * ambiguous, since (for example) writing one byte to a text file may store more
1268  * than one character in the platform's native encoding. You can only be sure
1269  * that the position increases as you read or write to the file.
1270  *
1271  * Additional complication: for Latin-1 memory and file streams, a character is
1272  * a byte. For Unicode memory and file streams (those created by
1273  * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is
1274  * a 32-bit word. So in a binary Unicode file, positions are multiples of four
1275  * bytes.
1276  *
1277  * <note><para>
1278  *   If this bothers you, don't use binary Unicode files. I don't think they're
1279  *   good for much anyhow.
1280  * </para></note>
1281  *
1282  * glk_stream_get_position() on a window stream will always return zero.
1283  *
1284  * <note><para>
1285  *   It might make more sense to return the number of characters written to the
1286  *   window, but existing libraries do not support this and it's not really
1287  *   worth adding the feature.
1288  * </para></note>
1289  *
1290  * Returns: position of the read/write mark in @str.
1291  */
1292 glui32
1293 glk_stream_get_position(strid_t str)
1294 {
1295         VALID_STREAM(str, return 0);
1296         
1297         switch(str->type)
1298         {
1299                 case STREAM_TYPE_MEMORY:
1300                         return str->mark;
1301                 case STREAM_TYPE_FILE:
1302                         return ftell(str->file_pointer);
1303                 case STREAM_TYPE_WINDOW:
1304                         return 0;
1305                 default:
1306                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1307                         return 0;
1308         }
1309 }
1310
1311 /**
1312  * glk_stream_set_position:
1313  * @str: A file or memory stream.
1314  * @pos: The position to set the mark to, relative to @seekmode.
1315  * @seekmode: One of %seekmode_Start, %seekmode_Current, or %seekmode_End.
1316  *
1317  * Sets the position of the read/write mark in @str. The position is controlled
1318  * by @pos, and the meaning of @pos is controlled by @seekmode. See the
1319  * <code>seekmode_</code> constants below.
1320  *
1321  * It is illegal to specify a position before the beginning or after the end of
1322  * the file.
1323  *
1324  * In binary files, the mark position is exact &mdash; it corresponds with the
1325  * number of characters you have read or written. In text files, this mapping 
1326  * can vary, because of linefeed conventions or other character-set 
1327  * approximations. See <link linkend="chimara-Streams">Streams</link>.
1328  * glk_stream_set_position() and glk_stream_get_position() measure positions in
1329  * the platform's native encoding &mdash; after character cookery. Therefore,
1330  * in a text stream, it is safest to use glk_stream_set_position() only to move
1331  * to the beginning or end of a file, or to a position determined by
1332  * glk_stream_get_position().
1333  *
1334  * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
1335  * characters are 32-bit words, or four bytes each.
1336  *
1337  * A window stream doesn't have a movable mark, so calling
1338  * glk_stream_set_position() has no effect.
1339  */
1340 void
1341 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
1342 {
1343         VALID_STREAM(str, return);
1344         g_return_if_fail(!(seekmode == seekmode_Start && pos < 0));
1345         g_return_if_fail(!(seekmode == seekmode_End && pos > 0));
1346         
1347         switch(str->type)
1348         {
1349                 case STREAM_TYPE_MEMORY:
1350                         switch(seekmode)
1351                         {
1352                                 case seekmode_Start:   str->mark = pos;  break;
1353                                 case seekmode_Current: str->mark += pos; break;
1354                                 case seekmode_End:     str->mark = str->endmark + pos; break;
1355                                 default:
1356                                         g_return_if_reached();
1357                                         return;
1358                         }
1359                         break;
1360                 case STREAM_TYPE_FILE:
1361                 {
1362                         int whence;
1363                         switch(seekmode)
1364                         {
1365                                 case seekmode_Start:   whence = SEEK_SET; break;
1366                                 case seekmode_Current: whence = SEEK_CUR; break;
1367                                 case seekmode_End:     whence = SEEK_END; break;
1368                                 default:
1369                                         g_return_if_reached();
1370                                         return;
1371                         }
1372                         if(fseek(str->file_pointer, pos, whence) == -1)
1373                                 WARNING("Seek failed on file stream");
1374                         str->lastop = 0; /* Either reading or writing is legal after fseek() */
1375                         break;
1376                 }
1377                 case STREAM_TYPE_WINDOW:
1378                         break; /* Quietly do nothing */
1379                 default:
1380                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1381                         return;
1382         }
1383 }
1384