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