039e189a5d8676b6c34506ebeb63a02add52ab9e
[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                         str->write_count += len;
314                         break;
315                         
316                 case STREAM_TYPE_FILE:
317                         if(str->binary) 
318                         {
319                                 if(str->unicode) 
320                                 {
321                                         gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len);
322                                         ensure_file_operation(str, filemode_Write);
323                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
324                                         g_free(writebuffer);
325                                 } 
326                                 else /* Regular file */
327                                 {
328                                         ensure_file_operation(str, filemode_Write);
329                                         fwrite(buf, sizeof(gchar), len, str->file_pointer);
330                                 }
331                         }
332                         else /* Text mode is the same for Unicode and regular files */
333                         {
334                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
335                                 if(utf8 != NULL)
336                                 {
337                                         ensure_file_operation(str, filemode_Write);
338                                         g_fprintf(str->file_pointer, "%s", utf8);
339                                         g_free(utf8);
340                                 }
341                         }
342                         
343                         str->write_count += len;
344                         break;
345                 default:
346                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
347         }
348 }
349
350 /* Internal function: write a Unicode buffer with length to a stream. */
351 static void
352 write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len)
353 {
354         switch(str->type)
355         {
356                 case STREAM_TYPE_WINDOW:
357                         /* Each window type has a different way of printing to it */
358                         switch(str->window->type)
359                         {
360                                 /* Printing to these windows' streams does nothing */
361                                 case wintype_Blank:
362                                 case wintype_Pair:
363                                 case wintype_Graphics:
364                                         str->write_count += len;
365                                         break;
366                                         
367                             /* Text grid/buffer windows */
368                             case wintype_TextGrid:
369                             case wintype_TextBuffer:
370                             {
371                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
372                                 if(utf8 != NULL) {
373                                                 write_utf8_to_window_buffer(str->window, utf8);
374                                                 g_free(utf8);
375                                         }
376                                 }       
377                                         str->write_count += len;
378                                         break;
379                                 default:
380                                         ILLEGAL_PARAM("Unknown window type: %u", str->window->type);
381                         }
382                         
383                         /* Now write the same buffer to the window's echo stream */
384                         if(str->window->echo_stream != NULL)
385                                 write_buffer_to_stream_uni(str->window->echo_stream, buf, len);
386                         
387                         break;
388                         
389                 case STREAM_TYPE_MEMORY:
390                         if(str->unicode && str->ubuffer)
391                         {
392                                 int copycount = MIN(len, str->buflen - str->mark);
393                                 memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32));
394                                 str->mark += copycount;
395                         }
396                         if(!str->unicode && str->buffer)
397                         {
398                                 gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
399                                 int copycount = MIN(len, str->buflen - str->mark);
400                                 memmove(str->buffer + str->mark, latin1, copycount);
401                                 g_free(latin1);
402                                 str->mark += copycount;
403                         }
404
405                         str->write_count += len;
406                         break;
407                         
408                 case STREAM_TYPE_FILE:
409                         if(str->binary) 
410                         {
411                                 if(str->unicode) 
412                                 {
413                                         gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len);
414                                         ensure_file_operation(str, filemode_Write);
415                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
416                                         g_free(writebuffer);
417                                 } 
418                                 else /* Regular file */
419                                 {
420                                         gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
421                                         ensure_file_operation(str, filemode_Write);
422                                         fwrite(latin1, sizeof(gchar), len, str->file_pointer);
423                                         g_free(latin1);
424                                 }
425                         }
426                         else /* Text mode is the same for Unicode and regular files */
427                         {
428                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
429                                 if(utf8 != NULL) 
430                                 {
431                                         ensure_file_operation(str, filemode_Write);
432                                         g_fprintf(str->file_pointer, "%s", utf8);
433                                         g_free(utf8);
434                                 }
435                         }
436                         
437                         str->write_count += len;
438                         break;
439                 default:
440                         ILLEGAL_PARAM("Unknown stream type: %u", str->type);
441         }
442 }
443
444 /**
445  * glk_put_char_stream:
446  * @str: An output stream.
447  * @ch: A character in Latin-1 encoding.
448  *
449  * The same as glk_put_char(), except that you specify a stream @str to print 
450  * to, instead of using the current stream. It is illegal for @str to be %NULL,
451  * or an input-only stream.
452  */
453 void
454 glk_put_char_stream(strid_t str, unsigned char ch)
455 {
456         VALID_STREAM(str, return);
457         g_return_if_fail(str->file_mode != filemode_Read);
458         
459         write_buffer_to_stream(str, (gchar *)&ch, 1);
460 }
461
462 /**
463  * glk_put_char_stream_uni:
464  * @str: An output stream.
465  * @ch: A Unicode code point.
466  *
467  * The same as glk_put_char_uni(), except that you specify a stream @str to
468  * print to, instead of using the current stream. It is illegal for @str to be 
469  * %NULL, or an input-only stream.
470  */
471 void
472 glk_put_char_stream_uni(strid_t str, glui32 ch)
473 {
474         VALID_STREAM(str, return);
475         g_return_if_fail(str->file_mode != filemode_Read);
476         
477         write_buffer_to_stream_uni(str, &ch, 1);
478 }
479
480 /**
481  * glk_put_string_stream:
482  * @str: An output stream.
483  * @s: A null-terminated string in Latin-1 encoding.
484  *
485  * The same as glk_put_string(), except that you specify a stream @str to print 
486  * to, instead of using the current stream. It is illegal for @str to be %NULL,
487  * or an input-only stream.
488  */
489 void
490 glk_put_string_stream(strid_t str, char *s)
491 {
492         VALID_STREAM(str, return);
493         if(*s == 0)
494                 return;
495
496         g_return_if_fail(str->file_mode != filemode_Read);
497
498         write_buffer_to_stream(str, s, strlen(s));
499 }
500
501 /**
502  * glk_put_string_stream_uni:
503  * @str: An output stream.
504  * @s: A null-terminated array of Unicode code points.
505  *
506  * The same as glk_put_string_uni(), except that you specify a stream @str to
507  * print to, instead of using the current stream. It is illegal for @str to be 
508  * %NULL, or an input-only stream.
509  */
510 void
511 glk_put_string_stream_uni(strid_t str, glui32 *s)
512 {
513         VALID_STREAM(str, return);
514         if(*s == 0)
515                 return;
516
517         g_return_if_fail(str->file_mode != filemode_Read);
518         
519         /* An impromptu strlen() for glui32 arrays */
520         glong len = 0;
521         glui32 *ptr = s;
522         while(*ptr++)
523                 len++;
524         write_buffer_to_stream_uni(str, s, len);
525 }
526
527 /**
528  * glk_put_buffer_stream:
529  * @str: An output stream.
530  * @buf: An array of characters in Latin-1 encoding.
531  * @len: Length of @buf.
532  *
533  * The same as glk_put_buffer(), except that you specify a stream @str to print 
534  * to, instead of using the current stream. It is illegal for @str to be %NULL,
535  * or an input-only stream.
536  */
537 void
538 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
539 {
540         VALID_STREAM(str, return);
541         if(len == 0)
542                 return;
543
544         g_return_if_fail(str->file_mode != filemode_Read);
545         
546         write_buffer_to_stream(str, buf, len);
547 }
548
549 /**
550  * glk_put_buffer_stream_uni:
551  * @str: An output stream.
552  * @buf: An array of Unicode code points.
553  * @len: Length of @buf.
554  *
555  * The same as glk_put_buffer_uni(), except that you specify a stream @str to
556  * print to, instead of using the current stream. It is illegal for @str to be 
557  * %NULL, or an input-only stream.
558  */
559 void
560 glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
561 {
562         VALID_STREAM(str, return);
563         if(len == 0)
564                 return;
565
566         g_return_if_fail(str->file_mode != filemode_Read);
567         
568         write_buffer_to_stream_uni(str, buf, len);
569 }
570
571 /*
572  *
573  **************** READING FUNCTIONS ********************************************
574  *
575  */
576
577 /* Internal function: Read one big-endian four-byte character from file fp and
578 return it as a Unicode code point, or -1 on EOF */
579 static glsi32
580 read_ucs4be_char_from_file(strid_t str)
581 {
582         unsigned char readbuffer[4];
583         ensure_file_operation(str, filemode_Read);
584         if(fread(readbuffer, sizeof(unsigned char), 4, str->file_pointer) < 4)
585                 return -1; /* EOF */
586         return
587                 readbuffer[0] << 24 | 
588                 readbuffer[1] << 16 | 
589                 readbuffer[2] << 8  | 
590                 readbuffer[3];
591 }
592
593 /* Internal function: Read one UTF-8 character, which may be more than one byte,
594 from file fp and return it as a Unicode code point, or -1 on EOF */
595 static glsi32
596 read_utf8_char_from_file(strid_t str)
597 {
598         gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
599         int foo;
600         gunichar charresult = (gunichar)-2;
601         ensure_file_operation(str, filemode_Read);
602         for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) 
603         {
604                 int ch = fgetc(str->file_pointer);
605                 if(ch == EOF)
606                         return -1;
607                 readbuffer[foo] = (gchar)ch;
608                 charresult = g_utf8_get_char_validated(readbuffer, foo + 1);
609                 /* charresult is -1 if invalid, -2 if incomplete, and the unicode code
610                 point otherwise */
611         }
612         /* Silently return unknown characters as 0xFFFD, Replacement Character */
613         if(charresult == (gunichar)-1 || charresult == (gunichar)-2) 
614                 return 0xFFFD;
615         return charresult;
616 }
617
618 /* Internal function: Tell whether this code point is a Unicode newline. The
619 file pointer and eight-bit flag are included in case the newline is a CR 
620 (U+000D). If the next character is LF (U+000A) then it also belongs to the
621 newline. */
622 static gboolean
623 is_unicode_newline(glsi32 ch, strid_t str, gboolean utf8)
624 {
625         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
626                 return TRUE;
627         if(ch == 0x0D) {
628                 glsi32 ch2 = utf8? read_utf8_char_from_file(str) :
629                         read_ucs4be_char_from_file(str);
630                 if(ch2 != 0x0A) {
631                         if(fseek(str->file_pointer, utf8? -1 : -4, SEEK_CUR) == -1);
632                                 WARNING_S("Seek failed on stream", g_strerror(errno) );
633                         str->lastop = 0; /* can read or write after a seek */
634                 }
635                 return TRUE;
636         }
637         return FALSE;
638 }
639
640 /* Internal function: Read one character from a stream. Returns a value which
641  can be returned unchanged by glk_get_char_stream_uni(), but 
642  glk_get_char_stream() must replace high values by the placeholder character. */
643 static glsi32
644 get_char_stream_common(strid_t str)
645 {
646         switch(str->type)
647         {
648                 case STREAM_TYPE_MEMORY:
649                         if(str->unicode)
650                         {
651                                 if(!str->ubuffer || str->mark >= str->buflen)
652                                         return -1;
653                                 glui32 ch = str->ubuffer[str->mark++];
654                                 str->read_count++;
655                                 return ch;
656                         }
657                         else
658                         {
659                                 if(!str->buffer || str->mark >= str->buflen)
660                                         return -1;
661                                 unsigned char ch = str->buffer[str->mark++];
662                                 str->read_count++;
663                                 return ch;
664                         }
665                         break;
666                         
667                 case STREAM_TYPE_FILE:
668                         if(str->binary) 
669                         {
670                                 if(str->unicode) 
671                                 {
672                                         glsi32 ch = read_ucs4be_char_from_file(str);
673                                         if(ch == -1)
674                                                 return -1;
675                                         str->read_count++;
676                                         return ch;
677                                 }
678                                 else /* Regular file */
679                                 {
680                                         ensure_file_operation(str, filemode_Read);
681                                         int ch = fgetc(str->file_pointer);
682                                         if(ch == EOF)
683                                                 return -1;
684                                         
685                                         str->read_count++;
686                                         return ch;
687                                 }
688                         }
689                         else /* Text mode is the same for Unicode and regular files */
690                         {
691                                 glsi32 ch = read_utf8_char_from_file(str);
692                                 if(ch == -1)
693                                         return -1;
694                                         
695                                 str->read_count++;
696                                 return ch;
697                         }
698                 default:
699                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
700                         return -1;
701         }
702 }
703
704 /**
705  * glk_get_char_stream:
706  * @str: An input stream.
707  *
708  * Reads one character from the stream @str. (There is no notion of a
709  * <quote>current input stream.</quote>) It is illegal for @str to be %NULL, or
710  * an output-only stream.
711  *
712  * The result will be between 0 and 255. As with all basic text functions, Glk
713  * assumes the Latin-1 encoding. See <link 
714  * linkend="chimara-Character-Encoding">Character Encoding</link>. If the end
715  * of the stream has been reached, the result will be -1. 
716  *
717  * <note><para>
718  *   Note that high-bit characters (128..255) are <emphasis>not</emphasis>
719  *   returned as negative numbers.
720  * </para></note>
721  *
722  * If the stream contains Unicode data &mdash; for example, if it was created
723  * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() &mdash; then
724  * characters beyond 255 will be returned as 0x3F (<code>"?"</code>).
725  *
726  * It is usually more efficient to read several characters at once with
727  * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling
728  * glk_get_char_stream() several times.
729  *
730  * Returns: A character value between 0 and 255, or -1 on end of stream.
731  */
732 glsi32
733 glk_get_char_stream(strid_t str)
734 {
735         VALID_STREAM(str, return -1);
736         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
737         
738         glsi32 ch = get_char_stream_common(str);
739         return (ch > 0xFF)? PLACEHOLDER : ch;
740 }
741
742 /**
743  * glk_get_char_stream_uni:
744  * @str: An input stream.
745  *
746  * Reads one character from the stream @str. If the end of the stream has been
747  * reached, the result will be -1.
748  *
749  * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream.
750  */
751 glsi32
752 glk_get_char_stream_uni(strid_t str)
753 {
754         VALID_STREAM(str, return -1);
755         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
756         
757         return get_char_stream_common(str);
758 }
759
760 /**
761  * glk_get_buffer_stream:
762  * @str: An input stream.
763  * @buf: A buffer with space for at least @len characters.
764  * @len: The number of characters to read.
765  *
766  * Reads @len characters from @str, unless the end of stream is reached first.
767  * No terminal null is placed in the buffer.
768  *
769  * Returns: The number of characters actually read.
770  */
771 glui32
772 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
773 {
774         VALID_STREAM(str, return 0);
775         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
776         g_return_val_if_fail(buf != NULL, 0);
777         
778         switch(str->type)
779         {
780                 case STREAM_TYPE_MEMORY:
781                 {
782                         int copycount = 0;
783                         if(str->unicode)
784                         {
785                                 while(copycount < len && str->ubuffer && str->mark < str->buflen) 
786                                 {
787                                         glui32 ch = str->ubuffer[str->mark++];
788                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
789                                 }
790                         }
791                         else
792                         {
793                                 if(str->buffer) /* if not, copycount stays 0 */
794                                         copycount = MIN(len, str->buflen - str->mark);
795                                 memmove(buf, str->buffer + str->mark, copycount);
796                                 str->mark += copycount;
797                         }
798
799                         str->read_count += copycount;           
800                         return copycount;
801                 }       
802                 case STREAM_TYPE_FILE:
803                         if(str->binary) 
804                         {
805                                 if(str->unicode) /* Binary file with 4-byte characters */
806                                 {
807                                         /* Read len characters of 4 bytes each */
808                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
809                                         ensure_file_operation(str, filemode_Read);
810                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
811                                         /* If there was an incomplete character */
812                                         if(count % 4 != 0) 
813                                         {
814                                                 count -= count % 4;
815                                                 WARNING("Incomplete character in binary Unicode file");
816                                         }
817                                         
818                                         int foo;
819                                         for(foo = 0; foo < count; foo += 4)
820                                         {
821                                                 glsi32 ch = readbuffer[foo] << 24
822                                                         | readbuffer[foo + 1] << 16
823                                                         | readbuffer[foo + 2] << 8
824                                                         | readbuffer[foo + 3];
825                                                 buf[foo / 4] = (ch > 255)? 0x3F : (char)ch;
826                                         }
827                                         g_free(readbuffer);
828                                         str->read_count += count / 4;
829                                         return count / 4;
830                                 }
831                                 else /* Regular binary file */
832                                 {
833                                         ensure_file_operation(str, filemode_Read);
834                                         size_t count = fread(buf, sizeof(char), len, str->file_pointer);
835                                         str->read_count += count;
836                                         return count;
837                                 }
838                         }
839                         else /* Text mode is the same for Unicode and regular files */
840                         {
841                                 /* Do it character-by-character */
842                                 int foo;
843                                 for(foo = 0; foo < len; foo++)
844                                 {
845                                         glsi32 ch = read_utf8_char_from_file(str);
846                                         if(ch == -1)
847                                                 break;
848                                         str->read_count++;
849                                         buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
850                                 }
851                                 return foo;
852                         }
853                 default:
854                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
855                         return 0;
856         }
857 }
858
859 /**
860  * glk_get_buffer_stream_uni:
861  * @str: An input stream.
862  * @buf: A buffer with space for at least @len Unicode code points.
863  * @len: The number of characters to read.
864  *
865  * Reads @len Unicode characters from @str, unless the end of stream is reached 
866  * first. No terminal null is placed in the buffer.
867  *
868  * Returns: The number of Unicode characters actually read.
869  */
870 glui32
871 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
872 {
873         VALID_STREAM(str, return 0);
874         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
875         g_return_val_if_fail(buf != NULL, 0);
876         
877         switch(str->type)
878         {
879                 case STREAM_TYPE_MEMORY:
880                 {
881                         int copycount = 0;
882                         if(str->unicode)
883                         {
884                                 if(str->ubuffer) /* if not, copycount stays 0 */
885                                         copycount = MIN(len, str->buflen - str->mark);
886                                 memmove(buf, str->ubuffer + str->mark, copycount * 4);
887                                 str->mark += copycount;
888                         }
889                         else
890                         {
891                                 while(copycount < len && str->buffer && str->mark < str->buflen)
892                                 {
893                                         unsigned char ch = str->buffer[str->mark++];
894                                         buf[copycount++] = ch;
895                                 }
896                         }
897
898                         str->read_count += copycount;           
899                         return copycount;
900                 }       
901                 case STREAM_TYPE_FILE:
902                         if(str->binary) 
903                         {
904                                 if(str->unicode) /* Binary file with 4-byte characters */
905                                 {
906                                         /* Read len characters of 4 bytes each */
907                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
908                                         ensure_file_operation(str, filemode_Read);
909                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
910                                         /* If there was an incomplete character */
911                                         if(count % 4 != 0) 
912                                         {
913                                                 count -= count % 4;
914                                                 WARNING("Incomplete character in binary Unicode file");
915                                         }
916                                         
917                                         int foo;
918                                         for(foo = 0; foo < count; foo += 4)
919                                                 buf[foo / 4] = readbuffer[foo] << 24
920                                                         | readbuffer[foo + 1] << 16
921                                                         | readbuffer[foo + 2] << 8
922                                                         | readbuffer[foo + 3];
923                                         g_free(readbuffer);
924                                         str->read_count += count / 4;
925                                         return count / 4;
926                                 }
927                                 else /* Regular binary file */
928                                 {
929                                         unsigned char *readbuffer = g_new0(unsigned char, len);
930                                         ensure_file_operation(str, filemode_Read);
931                                         size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer);
932                                         int foo;
933                                         for(foo = 0; foo < count; foo++)
934                                                 buf[foo] = readbuffer[foo];
935                                         g_free(readbuffer);
936                                         str->read_count += count;
937                                         return count;
938                                 }
939                         }
940                         else /* Text mode is the same for Unicode and regular files */
941                         {
942                                 /* Do it character-by-character */
943                                 int foo;
944                                 for(foo = 0; foo < len; foo++)
945                                 {
946                                         glsi32 ch = read_utf8_char_from_file(str);
947                                         if(ch == -1)
948                                                 break;
949                                         str->read_count++;
950                                         buf[foo] = ch;
951                                 }
952                                 return foo;
953                         }
954                 default:
955                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
956                         return 0;
957         }
958 }
959
960 /**
961  * glk_get_line_stream:
962  * @str: An input stream.
963  * @buf: A buffer with space for at least @len characters.
964  * @len: The number of characters to read, plus one.
965  *
966  * Reads characters from @str, until either 
967  * <inlineequation>
968  *   <alt>@len - 1</alt>
969  *   <mathphrase>@len - 1</mathphrase>
970  * </inlineequation>
971  * characters have been read or a newline has been read. It then puts a
972  * terminal null (<code>'\0'</code>) character on
973  * the end. It returns the number of characters actually read, including the
974  * newline (if there is one) but not including the terminal null.
975  *
976  * Returns: The number of characters actually read.
977  */
978 glui32
979 glk_get_line_stream(strid_t str, char *buf, glui32 len)
980 {
981         VALID_STREAM(str, return 0);
982         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
983         g_return_val_if_fail(buf != NULL, 0);
984
985         switch(str->type)
986         {
987                 case STREAM_TYPE_MEMORY:
988                 {
989                         int copycount = 0;
990                         if(str->unicode)
991                         {
992                                 /* Do it character-by-character */
993                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
994                                 {
995                                         glui32 ch = str->ubuffer[str->mark++];
996                                         /* Check for Unicode newline; slightly different than
997                                         in file streams */
998                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
999                                         {
1000                                                 buf[copycount++] = '\n';
1001                                                 break;
1002                                         }
1003                                         if(ch == 0x0D)
1004                                         {
1005                                                 if(str->ubuffer[str->mark] == 0x0A)
1006                                                         str->mark++; /* skip past next newline */
1007                                                 buf[copycount++] = '\n';
1008                                                 break;
1009                                         }
1010                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
1011                                 }
1012                                 buf[copycount] = '\0';
1013                         }
1014                         else
1015                         {
1016                                 if(str->buffer) /* if not, copycount stays 0 */
1017                                         copycount = MIN(len - 1, str->buflen - str->mark);
1018                                 char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount);
1019                                 if(endptr) /* newline was found */
1020                                         copycount = endptr - buf; /* Real copy count */
1021                                 buf[copycount] = '\0';
1022                                 str->mark += copycount;
1023                         }
1024                         
1025                         str->read_count += copycount;
1026                         return copycount;
1027                 }       
1028                 case STREAM_TYPE_FILE:
1029                         if(str->binary) 
1030                         {
1031                                 if(str->unicode) /* Binary file with 4-byte characters */
1032                                 {
1033                                         /* Do it character-by-character */
1034                                         int copycount;
1035                                         for(copycount = 0; copycount < len - 1; copycount++)
1036                                         {
1037                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1038                                                 if(ch == -1) 
1039                                                 {
1040                                                         buf[copycount] = '\0';
1041                                                         return copycount;
1042                                                 }
1043                                                 str->read_count++;
1044                                                 if(is_unicode_newline(ch, str, FALSE))
1045                                                 {
1046                                                         buf[copycount++] = '\n';
1047                                                         buf[copycount] = '\0';
1048                                                         return copycount;
1049                                                 }
1050                                                 buf[copycount] = (ch > 0xFF)? '?' : (char)ch;
1051                                         }
1052                                         buf[len] = '\0';
1053                                         return copycount;
1054                                 }
1055                                 else /* Regular binary file */
1056                                 {
1057                                         ensure_file_operation(str, filemode_Read);
1058                                         if( !fgets(buf, len, str->file_pointer) ) {
1059                                                 *buf = 0;
1060                                                 return 0;
1061                                         }
1062
1063                                         int nread = strlen(buf);
1064                                         str->read_count += nread;
1065                                         return nread;
1066                                 }
1067                         }
1068                         else /* Text mode is the same for Unicode and regular files */
1069                         {
1070                                 /* Do it character-by-character */
1071                                 int foo;
1072                                 for(foo = 0; foo < len - 1; foo++)
1073                                 {
1074                                         glsi32 ch = read_utf8_char_from_file(str);
1075                                         if(ch == -1)
1076                                         {
1077                                                 buf[foo] = '\0';
1078                                                 return foo - 1;
1079                                         }
1080                                         str->read_count++;
1081                                         if(is_unicode_newline(ch, str, TRUE))
1082                                         {
1083                                                 buf[foo] = '\n';
1084                                                 buf[foo + 1] = '\0';
1085                                                 return foo;
1086                                         }
1087                                         buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
1088                                 }
1089                                 buf[len] = '\0';
1090                                 return foo;
1091                         }
1092                 default:
1093                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1094                         return 0;
1095         }
1096 }
1097
1098 /**
1099  * glk_get_line_stream_uni:
1100  * @str: An input stream.
1101  * @buf: A buffer with space for at least @len Unicode code points.
1102  * @len: The number of characters to read, plus one.
1103  *
1104  * Reads Unicode characters from @str, until either 
1105  * <inlineequation>
1106  *   <alt>@len - 1</alt>
1107  *   <mathphrase>@len - 1</mathphrase>
1108  * </inlineequation> 
1109  * Unicode characters have been read or a newline has been read. It then puts a
1110  * terminal null (a zero value) on the end.
1111  *
1112  * Returns: The number of characters actually read, including the newline (if
1113  * there is one) but not including the terminal null.
1114  */
1115 glui32
1116 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len)
1117 {
1118         VALID_STREAM(str, return 0);
1119         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
1120         g_return_val_if_fail(buf != NULL, 0);
1121
1122         switch(str->type)
1123         {
1124                 case STREAM_TYPE_MEMORY:
1125                 {
1126                         int copycount = 0;
1127                         if(str->unicode)
1128                         {
1129                                 /* Do it character-by-character */
1130                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
1131                                 {
1132                                         glui32 ch = str->ubuffer[str->mark++];
1133                                         /* Check for Unicode newline; slightly different than
1134                                         in file streams */
1135                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
1136                                         {
1137                                                 buf[copycount++] = '\n';
1138                                                 break;
1139                                         }
1140                                         if(ch == 0x0D)
1141                                         {
1142                                                 if(str->ubuffer[str->mark] == 0x0A)
1143                                                         str->mark++; /* skip past next newline */
1144                                                 buf[copycount++] = '\n';
1145                                                 break;
1146                                         }
1147                                         buf[copycount++] = ch;
1148                                 }
1149                                 buf[copycount] = '\0';
1150                         }
1151                         else
1152                         {
1153                                 /* No recourse to memccpy(), so do it character-by-character */
1154                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
1155                                 {
1156                                         gchar ch = str->buffer[str->mark++];
1157                                         /* Check for newline */
1158                                         if(ch == '\n') /* Also check for \r and \r\n? */
1159                                         {
1160                                                 buf[copycount++] = '\n';
1161                                                 break;
1162                                         }
1163                                         buf[copycount++] = (unsigned char)ch;
1164                                 }
1165                                 buf[copycount] = 0;
1166                         }
1167                         
1168                         str->read_count += copycount;
1169                         return copycount;
1170                 }       
1171                 case STREAM_TYPE_FILE:
1172                         if(str->binary) 
1173                         {
1174                                 if(str->unicode) /* Binary file with 4-byte characters */
1175                                 {
1176                                         /* Do it character-by-character */
1177                                         int copycount;
1178                                         for(copycount = 0; copycount < len - 1; copycount++)
1179                                         {
1180                                                 glsi32 ch = read_ucs4be_char_from_file(str);
1181                                                 if(ch == -1) 
1182                                                 {
1183                                                         buf[copycount] = 0;
1184                                                         return copycount;
1185                                                 }
1186                                                 str->read_count++;
1187                                                 if(is_unicode_newline(ch, str, FALSE))
1188                                                 {
1189                                                         buf[copycount++] = ch; /* Preserve newline types??? */
1190                                                         buf[copycount] = 0;
1191                                                         return copycount;
1192                                                 }
1193                                                 buf[copycount] = ch;
1194                                         }
1195                                         buf[len] = 0;
1196                                         return copycount;
1197                                 }
1198                                 else /* Regular binary file */
1199                                 {
1200                                         gchar *readbuffer = g_new0(gchar, len);
1201                                         ensure_file_operation(str, filemode_Read);
1202                                         if( !fgets(readbuffer, len, str->file_pointer) ) {
1203                                                 *buf = 0;
1204                                                 return 0;
1205                                         }
1206
1207                                         glui32 count = strlen(readbuffer);
1208                                         int foo;
1209                                         for(foo = 0; foo < count + 1; foo++) /* Copy terminator */
1210                                                 buf[foo] = (unsigned char)(readbuffer[foo]);
1211                                         str->read_count += count;
1212                                         return count;
1213                                 }
1214                         }
1215                         else /* Text mode is the same for Unicode and regular files */
1216                         {
1217                                 /* Do it character-by-character */
1218                                 int foo;
1219                                 for(foo = 0; foo < len - 1; foo++)
1220                                 {
1221                                         glsi32 ch = read_utf8_char_from_file(str);
1222                                         if(ch == -1)
1223                                         {
1224                                                 buf[foo] = 0;
1225                                                 return foo - 1;
1226                                         }
1227                                         str->read_count++;
1228                                         if(is_unicode_newline(ch, str, TRUE))
1229                                         {
1230                                                 buf[foo] = ch; /* Preserve newline types??? */
1231                                                 buf[foo + 1] = 0;
1232                                                 return foo;
1233                                         }
1234                                         buf[foo] = ch;
1235                                 }
1236                                 buf[len] = 0;
1237                                 return foo;
1238                         }
1239                 default:
1240                         ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type);
1241                         return 0;
1242         }
1243 }
1244
1245 /*
1246  *
1247  **************** SEEKING FUNCTIONS ********************************************
1248  *
1249  */
1250
1251 /**
1252  * glk_stream_get_position:
1253  * @str: A file or memory stream.
1254  *
1255  * Returns the position of the read/write mark in @str. For memory streams and
1256  * binary file streams, this is exactly the number of characters read or written
1257  * from the beginning of the stream (unless you have moved the mark with
1258  * glk_stream_set_position().) For text file streams, matters are more 
1259  * ambiguous, since (for example) writing one byte to a text file may store more
1260  * than one character in the platform's native encoding. You can only be sure
1261  * that the position increases as you read or write to the file.
1262  *
1263  * Additional complication: for Latin-1 memory and file streams, a character is
1264  * a byte. For Unicode memory and file streams (those created by
1265  * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is
1266  * a 32-bit word. So in a binary Unicode file, positions are multiples of four
1267  * bytes.
1268  *
1269  * <note><para>
1270  *   If this bothers you, don't use binary Unicode files. I don't think they're
1271  *   good for much anyhow.
1272  * </para></note>
1273  *
1274  * glk_stream_get_position() on a window stream will always return zero.
1275  *
1276  * <note><para>
1277  *   It might make more sense to return the number of characters written to the
1278  *   window, but existing libraries do not support this and it's not really
1279  *   worth adding the feature.
1280  * </para></note>
1281  *
1282  * Returns: position of the read/write mark in @str.
1283  */
1284 glui32
1285 glk_stream_get_position(strid_t str)
1286 {
1287         VALID_STREAM(str, return 0);
1288         
1289         switch(str->type)
1290         {
1291                 case STREAM_TYPE_MEMORY:
1292                         return str->mark;
1293                 case STREAM_TYPE_FILE:
1294                         return ftell(str->file_pointer);
1295                 case STREAM_TYPE_WINDOW:
1296                         return 0;
1297                 default:
1298                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1299                         return 0;
1300         }
1301 }
1302
1303 /**
1304  * glk_stream_set_position:
1305  * @str: A file or memory stream.
1306  * @pos: The position to set the mark to, relative to @seekmode.
1307  * @seekmode: One of %seekmode_Start, %seekmode_Current, or %seekmode_End.
1308  *
1309  * Sets the position of the read/write mark in @str. The position is controlled
1310  * by @pos, and the meaning of @pos is controlled by @seekmode. See the
1311  * <code>seekmode_</code> constants below.
1312  *
1313  * It is illegal to specify a position before the beginning or after the end of
1314  * the file.
1315  *
1316  * In binary files, the mark position is exact &mdash; it corresponds with the
1317  * number of characters you have read or written. In text files, this mapping 
1318  * can vary, because of linefeed conventions or other character-set 
1319  * approximations. See <link linkend="chimara-Streams">Streams</link>.
1320  * glk_stream_set_position() and glk_stream_get_position() measure positions in
1321  * the platform's native encoding &mdash; after character cookery. Therefore,
1322  * in a text stream, it is safest to use glk_stream_set_position() only to move
1323  * to the beginning or end of a file, or to a position determined by
1324  * glk_stream_get_position().
1325  *
1326  * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
1327  * characters are 32-bit words, or four bytes each.
1328  *
1329  * A window stream doesn't have a movable mark, so calling
1330  * glk_stream_set_position() has no effect.
1331  */
1332 void
1333 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
1334 {
1335         VALID_STREAM(str, return);
1336         g_return_if_fail(!(seekmode == seekmode_Start && pos < 0));
1337         g_return_if_fail(!(seekmode == seekmode_End && pos > 0));
1338         
1339         switch(str->type)
1340         {
1341                 case STREAM_TYPE_MEMORY:
1342                         switch(seekmode)
1343                         {
1344                                 case seekmode_Start:   str->mark = pos;  break;
1345                                 case seekmode_Current: str->mark += pos; break;
1346                                 case seekmode_End:     str->mark = str->buflen + pos; break;
1347                                 default:
1348                                         g_return_if_reached();
1349                                         return;
1350                         }
1351                         break;
1352                 case STREAM_TYPE_FILE:
1353                 {
1354                         int whence;
1355                         switch(seekmode)
1356                         {
1357                                 case seekmode_Start:   whence = SEEK_SET; break;
1358                                 case seekmode_Current: whence = SEEK_CUR; break;
1359                                 case seekmode_End:     whence = SEEK_END; break;
1360                                 default:
1361                                         g_return_if_reached();
1362                                         return;
1363                         }
1364                         if(fseek(str->file_pointer, pos, whence) == -1)
1365                                 WARNING("Seek failed on file stream");
1366                         str->lastop = 0; /* Either reading or writing is legal after fseek() */
1367                         break;
1368                 }
1369                 case STREAM_TYPE_WINDOW:
1370                         break; /* Quietly do nothing */
1371                 default:
1372                         ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type);
1373                         return;
1374         }
1375 }
1376