Added text grid windows. Output, cursor placement, and character input work. Line...
[rodin/chimara.git] / src / strio.c
1 #include "stream.h"
2 #include <stdio.h>
3 #include <string.h>
4 #include <glib.h>
5 #include <glib/gstdio.h>
6
7 /*
8  *
9  **************** WRITING FUNCTIONS ********************************************
10  *
11  */
12
13 /* Internal function: change illegal (control) characters in a string to a
14 placeholder character. Must free returned string afterwards. */
15 static gchar *
16 remove_latin1_control_characters(unsigned char *s, gsize len)
17 {
18         /* If len == 0, then return an empty string, not NULL */
19         if(len == 0)
20                 return g_strdup("");
21                         
22         gchar *retval = g_new0(gchar, len);
23         int i;
24         for(i = 0; i < len; i++)
25                 if( (s[i] < 32 && s[i] != 10) || (s[i] >= 127 && s[i] <= 159) )
26                         retval[i] = '?';
27                         /* Our placeholder character is '?'; other options are possible,
28                         like printing "0x7F" or something */
29                 else
30                         retval[i] = s[i];
31         return retval;
32 }
33
34 /* Internal function: convert a Latin-1 string to a UTF-8 string, replacing
35 Latin-1 control characters by a placeholder first. The UTF-8 string must be
36 freed afterwards. Returns NULL on error. */
37 static gchar *
38 convert_latin1_to_utf8(gchar *s, gsize len)
39 {
40         GError *error = NULL;
41         gchar *utf8;
42         gchar *canonical = remove_latin1_control_characters( (unsigned char *)s,
43                 len);
44         utf8 = g_convert(canonical, len, "UTF-8", "ISO-8859-1", NULL, NULL, &error);
45         g_free(canonical);
46         
47         if(utf8 == NULL)
48         {
49                 g_warning("Error during latin1->utf8 conversion: %s", error->message);
50                 return NULL;
51         }
52         
53         return utf8;
54 }
55
56 /* Internal function: write a UTF-8 string to a text grid window's text buffer. */
57 static void
58 write_utf8_to_grid(winid_t win, gchar *s)
59 {
60     /* Number of characters to insert */
61     glong length = g_utf8_strlen(s, -1);
62     glong chars_left = length;
63     
64     gdk_threads_enter();
65     
66     GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
67     GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
68     
69     /* Get cursor position */
70     GtkTextIter start;
71     gtk_text_buffer_get_iter_at_mark(buffer, &start, cursor);
72     /* Spaces available on this line */
73     gint available_space = win->width - gtk_text_iter_get_line_offset(&start);
74     
75     while(chars_left > available_space && !gtk_text_iter_is_end(&start))
76     {
77         GtkTextIter end = start;
78         gtk_text_iter_forward_to_line_end(&end);
79         gtk_text_buffer_delete(buffer, &start, &end);
80         gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), available_space);
81         chars_left -= available_space;
82         gtk_text_iter_forward_line(&start);
83         available_space = win->width;
84     }
85     if(!gtk_text_iter_is_end(&start))
86     {
87         GtkTextIter end = start;
88         gtk_text_iter_forward_chars(&end, chars_left);
89         gtk_text_buffer_delete(buffer, &start, &end);
90         gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), -1);
91     }
92     
93     gtk_text_buffer_move_mark(buffer, cursor, &start);
94     
95     gdk_threads_leave();
96 }
97
98 /* Internal function: write a UTF-8 string to a text buffer window's text buffer. */
99 static void
100 write_utf8_to_window(winid_t win, gchar *s)
101 {
102         gdk_threads_enter();
103
104         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
105
106         GtkTextIter iter;
107         gtk_text_buffer_get_end_iter(buffer, &iter);
108         gtk_text_buffer_insert(buffer, &iter, s, -1);
109
110         gdk_threads_leave();
111 }
112
113 /* Internal function: write a Latin-1 buffer with length to a stream. */
114 static void
115 write_buffer_to_stream(strid_t str, gchar *buf, glui32 len)
116 {
117         switch(str->type)
118         {
119                 case STREAM_TYPE_WINDOW:
120                         /* Each window type has a different way of printing to it */
121                         switch(str->window->type)
122                         {
123                                 /* Printing to these windows' streams does nothing */
124                                 case wintype_Blank:
125                                 case wintype_Pair:
126                                 case wintype_Graphics:
127                                         str->write_count += len;
128                                         break;
129                                         
130                             /* Text grid window */
131                             case wintype_TextGrid:
132                             {
133                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
134                                 if(utf8)
135                                 {
136                                     /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */
137                                     write_utf8_to_grid(str->window, utf8);
138                                     g_free(utf8);
139                                 }
140                             }
141                                 str->write_count += len;
142                                 break;
143                                         
144                                 /* Text buffer window */        
145                                 case wintype_TextBuffer:
146                                 {
147                                         gchar *utf8 = convert_latin1_to_utf8(buf, len);
148                                         if(utf8)
149                                         {
150                                                 write_utf8_to_window(str->window, utf8);
151                                                 g_free(utf8);
152                                         }
153                                 }       
154                                         str->write_count += len;
155                                         break;
156                                 default:
157                                         g_warning("%s: Writing to this kind of window unsupported.", __func__);
158                         }
159                         
160                         /* Now write the same buffer to the window's echo stream */
161                         if(str->window->echo_stream != NULL)
162                                 write_buffer_to_stream(str->window->echo_stream, buf, len);
163                         
164                         break;
165                         
166                 case STREAM_TYPE_MEMORY:
167                         if(str->unicode && str->ubuffer)
168                         {
169                                 int foo = 0;
170                                 while(str->mark < str->buflen && foo < len)
171                                         str->ubuffer[str->mark++] = (unsigned char)buf[foo++];
172                         }
173                         if(!str->unicode && str->buffer)
174                         {
175                                 int copycount = MIN(len, str->buflen - str->mark);
176                                 memmove(str->buffer + str->mark, buf, copycount);
177                                 str->mark += copycount;
178                         }
179
180                         str->write_count += len;
181                         break;
182                         
183                 case STREAM_TYPE_FILE:
184                         if(str->binary) 
185                         {
186                                 if(str->unicode) 
187                                 {
188                                         /* Convert to four-byte big-endian */
189                                         gchar *writebuffer = g_new0(gchar, len * 4);
190                                         int i;
191                                         for(i = 0; i < len; i++)
192                                                 writebuffer[i * 4 + 3] = buf[i];
193                                         fwrite(writebuffer, sizeof(gchar), len * 4, 
194                                                 str->file_pointer);
195                                 } 
196                                 else /* Regular file */
197                                 {
198                                         fwrite(buf, sizeof(gchar), len, str->file_pointer);
199                                 }
200                         }
201                         else /* Text mode is the same for Unicode and regular files */
202                         {
203                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
204                                 g_fprintf(str->file_pointer, "%s", utf8);
205                                 g_free(utf8);
206                         }
207                         
208                         str->write_count += len;
209                         break;
210                 default:
211                         g_warning("%s: Writing to this kind of stream unsupported.", __func__);
212         }
213 }
214
215 /**
216  * glk_put_char_stream:
217  * @str: An output stream.
218  * @ch: A character in Latin-1 encoding.
219  *
220  * Prints one character @ch to the stream @str. It is illegal for @str to be
221  * %NULL, or an input-only stream.
222  */
223 void
224 glk_put_char_stream(strid_t str, unsigned char ch)
225 {
226         g_return_if_fail(str != NULL);
227         g_return_if_fail(str->file_mode != filemode_Read);
228         
229         write_buffer_to_stream(str, (gchar *)&ch, 1);
230 }
231
232 /**
233  * glk_put_string_stream:
234  * @str: An output stream.
235  * @s: A null-terminated string in Latin-1 encoding.
236  *
237  * Prints @s to the stream @str. It is illegal for @str to be %NULL, or an
238  * input-only stream.
239  */
240 void
241 glk_put_string_stream(strid_t str, char *s)
242 {
243         g_return_if_fail(str != NULL);
244         g_return_if_fail(str->file_mode != filemode_Read);
245
246         write_buffer_to_stream(str, (gchar *)s, strlen(s));
247 }
248
249 /**
250  * glk_put_buffer_stream:
251  * @str: An output stream.
252  * @buf: An array of characters in Latin-1 encoding.
253  * @len: Length of @buf.
254  *
255  * Prints @buf to the stream @str. It is illegal for @str to be %NULL, or an
256  * input-only stream.
257  */
258 void
259 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
260 {
261         g_return_if_fail(str != NULL);
262         g_return_if_fail(str->file_mode != filemode_Read);
263         
264         write_buffer_to_stream(str, (gchar *)buf, len);
265 }
266
267 /*
268  *
269  **************** READING FUNCTIONS ********************************************
270  *
271  */
272
273 /* Internal function: Read one big-endian four-byte character from file fp and
274 return it as a Unicode code point, or -1 on EOF */
275 static glsi32
276 read_ucs4be_char_from_file(FILE *fp)
277 {
278         unsigned char readbuffer[4];
279         if(fread(readbuffer, sizeof(unsigned char), 4, fp) < 4)
280                 return -1; /* EOF */
281         return
282                 readbuffer[0] << 24 | 
283                 readbuffer[1] << 16 | 
284                 readbuffer[2] << 8  | 
285                 readbuffer[3];
286 }
287
288 /* Internal function: Read one UTF-8 character, which may be more than one byte,
289 from file fp and return it as a Unicode code point, or -1 on EOF */
290 static glsi32
291 read_utf8_char_from_file(FILE *fp)
292 {
293         gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
294         int foo;
295         gunichar charresult = (gunichar)-2;
296         for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) 
297         {
298                 int ch = fgetc(fp);
299                 if(ch == EOF)
300                         return -1;
301                 readbuffer[foo] = (gchar)ch;
302                 charresult = g_utf8_get_char_validated(readbuffer, foo + 1);
303                 /* charresult is -1 if invalid, -2 if incomplete, and the unicode code
304                 point otherwise */
305         }
306         /* Silently return unknown characters as 0xFFFD, Replacement Character */
307         if(charresult == (gunichar)-1 || charresult == (gunichar)-2) 
308                 return 0xFFFD;
309         return charresult;
310 }
311
312 /* Internal function: Tell whether this code point is a Unicode newline. The
313 file pointer and eight-bit flag are included in case the newline is a CR 
314 (U+000D). If the next character is LF (U+000A) then it also belongs to the
315 newline. */
316 static gboolean
317 is_unicode_newline(glsi32 ch, FILE *fp, gboolean utf8)
318 {
319         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
320                 return TRUE;
321         if(ch == 0x0D) {
322                 glsi32 ch2 = utf8? read_utf8_char_from_file(fp) : 
323                         read_ucs4be_char_from_file(fp);
324                 if(ch2 != 0x0A)
325                         fseek(fp, utf8? -1 : -4, SEEK_CUR);
326                 return TRUE;
327         }
328         return FALSE;
329 }
330
331 /**
332  * glk_get_char_stream:
333  * @str: An input stream.
334  *
335  * Reads one character from the stream @str. (There is no notion of a ``current
336  * input stream.'') It is illegal for @str to be %NULL, or an output-only
337  * stream.
338  *
339  * The result will be between 0 and 255. As with all basic text functions, Glk
340  * assumes the Latin-1 encoding. If the end of the stream has been reached, the
341  * result will be -1. Note that high-bit characters (128..255) are
342  * <emphasis>not</emphasis> returned as negative numbers.
343  *
344  * If the stream contains Unicode data --- for example, if it was created with
345  * glk_stream_open_file_uni() or glk_stream_open_memory_uni() --- then
346  * characters beyond 255 will be returned as 0x3F ("?").
347  *
348  * Returns: A character value between 0 and 255, or -1 on end of stream.
349  */
350 glsi32
351 glk_get_char_stream(strid_t str)
352 {
353         g_return_val_if_fail(str != NULL, -1);
354         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
355         
356         switch(str->type)
357         {
358                 case STREAM_TYPE_MEMORY:
359                         if(str->unicode)
360                         {
361                                 if(!str->ubuffer || str->mark >= str->buflen)
362                                         return -1;
363                                 glui32 ch = str->ubuffer[str->mark++];
364                                 str->read_count++;
365                                 return (ch > 0xFF)? 0x3F : ch;
366                         }
367                         else
368                         {
369                                 if(!str->buffer || str->mark >= str->buflen)
370                                         return -1;
371                                 char ch = str->buffer[str->mark++];
372                                 str->read_count++;
373                                 return ch;
374                         }
375                         break;
376                         
377                 case STREAM_TYPE_FILE:
378                         if(str->binary) 
379                         {
380                                 if(str->unicode) 
381                                 {
382                                         glsi32 ch = read_ucs4be_char_from_file(str->file_pointer);
383                                         if(ch == -1)
384                                                 return -1;
385                                         str->read_count++;
386                                         return (ch > 0xFF)? 0x3F : ch;
387                                 }
388                                 else /* Regular file */
389                                 {
390                                         int ch = fgetc(str->file_pointer);
391                                         if(ch == EOF)
392                                                 return -1;
393                                         
394                                         str->read_count++;
395                                         return ch;
396                                 }
397                         }
398                         else /* Text mode is the same for Unicode and regular files */
399                         {
400                                 glsi32 ch = read_utf8_char_from_file(str->file_pointer);
401                                 if(ch == -1)
402                                         return -1;
403                                         
404                                 str->read_count++;
405                                 return (ch > 0xFF)? 0x3F : ch;
406                         }
407                 default:
408                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
409                         return -1;
410         }
411 }
412
413 /**
414  * glk_get_buffer_stream:
415  * @str: An input stream.
416  * @buf: A buffer with space for at least @len characters.
417  * @len: The number of characters to read.
418  *
419  * Reads @len characters from @str, unless the end of stream is reached first.
420  * No terminal null is placed in the buffer.
421  *
422  * Returns: The number of characters actually read.
423  */
424 glui32
425 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
426 {
427         g_return_val_if_fail(str != NULL, 0);
428         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
429         g_return_val_if_fail(buf != NULL, 0);
430         
431         switch(str->type)
432         {
433                 case STREAM_TYPE_MEMORY:
434                 {
435                         int copycount = 0;
436                         if(str->unicode)
437                         {
438                                 while(copycount < len && str->ubuffer && str->mark < str->buflen) 
439                                 {
440                                         glui32 ch = str->ubuffer[str->mark++];
441                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
442                                 }
443                         }
444                         else
445                         {
446                                 if(str->buffer) /* if not, copycount stays 0 */
447                                         copycount = MIN(len, str->buflen - str->mark);
448                                 memmove(buf, str->buffer + str->mark, copycount);
449                                 str->mark += copycount;
450                         }
451
452                         str->read_count += copycount;           
453                         return copycount;
454                 }       
455                 case STREAM_TYPE_FILE:
456                         if(str->binary) 
457                         {
458                                 if(str->unicode) /* Binary file with 4-byte characters */
459                                 {
460                                         /* Read len characters of 4 bytes each */
461                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
462                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
463                                         /* If there was an incomplete character */
464                                         if(count % 4 != 0) 
465                                         {
466                                                 count -= count % 4;
467                                                 g_warning("%s: Incomplete character in binary Unicode file.", __func__);
468                                         }
469                                         
470                                         str->read_count += count / 4;
471                                         int foo;
472                                         for(foo = 0; foo < count; foo += 4)
473                                         {
474                                                 glsi32 ch = readbuffer[foo] << 24
475                                                         | readbuffer[foo + 1] << 16
476                                                         | readbuffer[foo + 2] << 8
477                                                         | readbuffer[foo + 3];
478                                                 buf[foo / 4] = (ch > 255)? 0x3F : (char)ch;
479                                         }
480                                         g_free(readbuffer);
481                                         return count / 4;
482                                 }
483                                 else /* Regular binary file */
484                                 {
485                                         size_t count = fread(buf, sizeof(char), len, str->file_pointer);
486                                         str->read_count += count;
487                                         return count;
488                                 }
489                         }
490                         else /* Text mode is the same for Unicode and regular files */
491                         {
492                                 /* Do it character-by-character */
493                                 int foo;
494                                 for(foo = 0; foo < len; foo++)
495                                 {
496                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
497                                         if(ch == -1)
498                                                 break;
499                                         str->read_count++;
500                                         buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
501                                 }
502                                 return foo;
503                         }
504                 default:
505                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
506                         return 0;
507         }
508 }
509
510 /**
511  * glk_get_line_stream:
512  * @str: An input stream.
513  * @buf: A buffer with space for at least @len characters.
514  * @len: The number of characters to read, plus one.
515  *
516  * Reads characters from @str, until either @len - 1 characters have been read
517  * or a newline has been read. It then puts a terminal null ('\0') aracter on
518  * the end. It returns the number of characters actually read, including the
519  * newline (if there is one) but not including the terminal null.
520  *
521  * It is usually more efficient to read several characters at once with
522  * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling
523  * glk_get_char_stream() several times.
524  *
525  * Returns: The number of characters actually read.
526  */
527 glui32
528 glk_get_line_stream(strid_t str, char *buf, glui32 len)
529 {
530         g_return_val_if_fail(str != NULL, 0);
531         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
532         g_return_val_if_fail(buf != NULL, 0);
533
534         switch(str->type)
535         {
536                 case STREAM_TYPE_MEMORY:
537                 {
538                         int copycount = 0;
539                         if(str->unicode)
540                         {
541                                 /* Do it character-by-character */
542                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
543                                 {
544                                         glui32 ch = str->ubuffer[str->mark++];
545                                         /* Check for Unicode newline; slightly different than
546                                         in file streams */
547                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
548                                         {
549                                                 buf[copycount++] = '\n';
550                                                 break;
551                                         }
552                                         if(ch == 0x0D)
553                                         {
554                                                 if(str->ubuffer[str->mark] == 0x0A)
555                                                         str->mark++; /* skip past next newline */
556                                                 buf[copycount++] = '\n';
557                                                 break;
558                                         }
559                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
560                                 }
561                                 buf[copycount] = '\0';
562                         }
563                         else
564                         {
565                                 if(str->buffer) /* if not, copycount stays 0 */
566                                         copycount = MIN(len - 1, str->buflen - str->mark);
567                                 char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount);
568                                 if(endptr) /* newline was found */
569                                         copycount = endptr - buf; /* Real copy count */
570                                 buf[copycount] = '\0';
571                                 str->mark += copycount;
572                         }
573                         
574                         str->read_count += copycount;
575                         return copycount;
576                 }       
577                 case STREAM_TYPE_FILE:
578                         if(str->binary) 
579                         {
580                                 if(str->unicode) /* Binary file with 4-byte characters */
581                                 {
582                                         /* Do it character-by-character */
583                                         int foo;
584                                         for(foo = 0; foo < len - 1; foo++)
585                                         {
586                                                 glsi32 ch = read_ucs4be_char_from_file(str->file_pointer);
587                                                 if(ch == -1) 
588                                                 {
589                                                         buf[foo] = '\0';
590                                                         return foo - 1;
591                                                 }
592                                                 str->read_count++;
593                                                 if(is_unicode_newline(ch, str->file_pointer, FALSE))
594                                                 {
595                                                         buf[foo] = '\n';
596                                                         buf[foo + 1] = '\0';
597                                                         return foo;
598                                                 }
599                                                 buf[foo] = (ch > 0xFF)? '?' : (char)ch;
600                                         }
601                                         buf[len] = '\0';
602                                         return foo;
603                                 }
604                                 else /* Regular binary file */
605                                 {
606                                         fgets(buf, len, str->file_pointer);
607                                         str->read_count += strlen(buf);
608                                         return strlen(buf);
609                                 }
610                         }
611                         else /* Text mode is the same for Unicode and regular files */
612                         {
613                                 /* Do it character-by-character */
614                                 int foo;
615                                 for(foo = 0; foo < len - 1; foo++)
616                                 {
617                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
618                                         if(ch == -1)
619                                         {
620                                                 buf[foo] = '\0';
621                                                 return foo - 1;
622                                         }
623                                         str->read_count++;
624                                         if(is_unicode_newline(ch, str->file_pointer, TRUE))
625                                         {
626                                                 buf[foo] = '\n';
627                                                 buf[foo + 1] = '\0';
628                                                 return foo;
629                                         }
630                                         buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
631                                 }
632                                 buf[len] = '\0';
633                                 return foo;
634                         }
635                 default:
636                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
637                         return 0;
638         }
639 }
640
641 /*
642  *
643  **************** SEEKING FUNCTIONS ********************************************
644  *
645  */
646
647 /**
648  * glk_stream_get_position:
649  * @str: A file or memory stream.
650  *
651  * Returns the position of the read/write mark in @str. For memory streams and
652  * binary file streams, this is exactly the number of characters read or written
653  * from the beginning of the stream (unless you have moved the mark with
654  * glk_stream_set_position().) For text file streams, matters are more 
655  * ambiguous, since (for example) writing one byte to a text file may store more
656  * than one character in the platform's native encoding. You can only be sure
657  * that the position increases as you read or write to the file.
658  *
659  * Additional complication: for Latin-1 memory and file streams, a character is
660  * a byte. For Unicode memory and file streams (those created by
661  * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is
662  * a 32-bit word. So in a binary Unicode file, positions are multiples of four
663  * bytes.
664  *
665  * Returns: position of the read/write mark in @str.
666  */
667 glui32
668 glk_stream_get_position(strid_t str)
669 {
670         g_return_val_if_fail(str != NULL, 0);
671         
672         switch(str->type)
673         {
674                 case STREAM_TYPE_MEMORY:
675                         return str->mark;
676                 case STREAM_TYPE_FILE:
677                         return ftell(str->file_pointer);
678                 default:
679                         g_warning("%s: Seeking not supported on this type of stream.",
680                                 __func__);
681                         return 0;
682         }
683 }
684
685 /**
686  * glk_stream_set_position:
687  * @str: A file or memory stream.
688  * @pos: The position to set the mark to, relative to @seekmode.
689  * @seekmode: One of #seekmode_Start, #seekmode_Current, or #seekmode_End.
690  *
691  * Sets the position of the read/write mark in @str. The position is controlled
692  * by @pos, and the meaning of @pos is controlled by @seekmode:
693  * <itemizedlist>
694  *  <listitem>#seekmode_Start: @pos characters after the beginning of the file.
695  *  </listitem>
696  *  <listitem>#seekmode_Current: @pos characters after the current position
697  *  (moving backwards if @pos is negative.)</listitem>
698  *  <listitem>#seekmode_End: @pos characters after the end of the file. (@pos
699  *  should always be zero or negative, so that this will move backwards to a
700  *  position within the file.</listitem>
701  * </itemizedlist>
702  * It is illegal to specify a position before the beginning or after the end of
703  * the file.
704  *
705  * In binary files, the mark position is exact --- it corresponds with the
706  * number of characters you have read or written. In text files, this mapping 
707  * can vary, because of linefeed conventions or other character-set 
708  * approximations. glk_stream_set_position() and glk_stream_get_position()
709  * measure positions in the platform's native encoding --- after character
710  * cookery. Therefore, in a text stream, it is safest to use
711  * glk_stream_set_position() only to move to the beginning or end of a file, or
712  * to a position determined by glk_stream_get_position().
713  *
714  * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
715  * characters are 32-bit words, or four bytes each.
716  */
717 void
718 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
719 {
720         g_return_if_fail(str != NULL);
721         g_return_if_fail(!(seekmode == seekmode_Start && pos < 0));
722         g_return_if_fail(!(seekmode == seekmode_End || pos > 0));
723         
724         switch(str->type)
725         {
726                 case STREAM_TYPE_MEMORY:
727                         switch(seekmode)
728                         {
729                                 case seekmode_Start:   str->mark = pos;  break;
730                                 case seekmode_Current: str->mark += pos; break;
731                                 case seekmode_End:     str->mark = str->buflen + pos; break;
732                                 default:
733                                         g_assert_not_reached();
734                                         return;
735                         }
736                         break;
737                 case STREAM_TYPE_FILE:
738                 {
739                         int whence;
740                         switch(seekmode)
741                         {
742                                 case seekmode_Start:   whence = SEEK_SET; break;
743                                 case seekmode_Current: whence = SEEK_CUR; break;
744                                 case seekmode_End:     whence = SEEK_END; break;
745                                 default:
746                                         g_assert_not_reached();
747                                         return;
748                         }
749                         fseek(str->file_pointer, pos, whence);
750                         break;
751                 }
752                 default:
753                         g_warning("%s: Seeking not supported on this type of stream.", __func__);
754                         return;
755         }
756 }
757