98140ada9fd1891656722a00d04719440b2332eb
[projects/chimara/chimara.git] / src / strio.c
1 #include "charset.h"
2 #include "stream.h"
3 #include <stdio.h>
4 #include <string.h>
5 #include <glib.h>
6 #include <glib/gstdio.h>
7
8 /*
9  *
10  **************** WRITING FUNCTIONS ********************************************
11  *
12  */
13
14 /* Internal function: write a UTF-8 string to a text grid window's text buffer. */
15 static void
16 write_utf8_to_grid(winid_t win, gchar *s)
17 {
18     /* Number of characters to insert */
19     glong length = g_utf8_strlen(s, -1);
20     glong chars_left = length;
21     
22     gdk_threads_enter();
23     
24     GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
25     GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
26     
27     /* Get cursor position */
28     GtkTextIter start;
29     gtk_text_buffer_get_iter_at_mark(buffer, &start, cursor);
30     /* Spaces available on this line */
31     gint available_space = win->width - gtk_text_iter_get_line_offset(&start);
32     
33     while(chars_left > available_space && !gtk_text_iter_is_end(&start))
34     {
35         GtkTextIter end = start;
36         gtk_text_iter_forward_to_line_end(&end);
37         gtk_text_buffer_delete(buffer, &start, &end);
38         gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), available_space);
39         chars_left -= available_space;
40         gtk_text_iter_forward_line(&start);
41         available_space = win->width;
42     }
43     if(!gtk_text_iter_is_end(&start))
44     {
45         GtkTextIter end = start;
46         gtk_text_iter_forward_chars(&end, chars_left);
47         gtk_text_buffer_delete(buffer, &start, &end);
48         gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), -1);
49     }
50     
51     gtk_text_buffer_move_mark(buffer, cursor, &start);
52     
53     gdk_threads_leave();
54 }
55
56 /* Internal function: write a UTF-8 string to a text buffer window's text buffer. */
57 static void
58 write_utf8_to_window(winid_t win, gchar *s)
59 {
60         gdk_threads_enter();
61
62         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
63
64         GtkTextIter iter;
65         gtk_text_buffer_get_end_iter(buffer, &iter);
66         gtk_text_buffer_insert(buffer, &iter, s, -1);
67
68         gdk_threads_leave();
69 }
70
71 /* Internal function: write a Latin-1 buffer with length to a stream. */
72 static void
73 write_buffer_to_stream(strid_t str, gchar *buf, glui32 len)
74 {
75         switch(str->type)
76         {
77                 case STREAM_TYPE_WINDOW:
78                         /* Each window type has a different way of printing to it */
79                         switch(str->window->type)
80                         {
81                                 /* Printing to these windows' streams does nothing */
82                                 case wintype_Blank:
83                                 case wintype_Pair:
84                                 case wintype_Graphics:
85                                         str->write_count += len;
86                                         break;
87                                         
88                             /* Text grid window */
89                             case wintype_TextGrid:
90                             {
91                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
92                                 if(utf8 != NULL)
93                                 {
94                                     /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */
95                                     write_utf8_to_grid(str->window, utf8);
96                                     g_free(utf8);
97                                 }
98                             }
99                                 str->write_count += len;
100                                 break;
101                                         
102                                 /* Text buffer window */        
103                                 case wintype_TextBuffer:
104                                 {
105                                         gchar *utf8 = convert_latin1_to_utf8(buf, len);
106                                         if(utf8 != NULL)
107                                         {
108                                                 write_utf8_to_window(str->window, utf8);
109                                                 g_free(utf8);
110                                         }
111                                 }       
112                                         str->write_count += len;
113                                         break;
114                                 default:
115                                         g_warning("%s: Writing to this kind of window unsupported.", __func__);
116                         }
117                         
118                         /* Now write the same buffer to the window's echo stream */
119                         if(str->window->echo_stream != NULL)
120                                 write_buffer_to_stream(str->window->echo_stream, buf, len);
121                         
122                         break;
123                         
124                 case STREAM_TYPE_MEMORY:
125                         if(str->unicode && str->ubuffer)
126                         {
127                                 int foo = 0;
128                                 while(str->mark < str->buflen && foo < len)
129                                         str->ubuffer[str->mark++] = (unsigned char)buf[foo++];
130                         }
131                         if(!str->unicode && str->buffer)
132                         {
133                                 int copycount = MIN(len, str->buflen - str->mark);
134                                 memmove(str->buffer + str->mark, buf, copycount);
135                                 str->mark += copycount;
136                         }
137
138                         str->write_count += len;
139                         break;
140                         
141                 case STREAM_TYPE_FILE:
142                         if(str->binary) 
143                         {
144                                 if(str->unicode) 
145                                 {
146                                         gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len);
147                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
148                                         g_free(writebuffer);
149                                 } 
150                                 else /* Regular file */
151                                 {
152                                         fwrite(buf, sizeof(gchar), len, str->file_pointer);
153                                 }
154                         }
155                         else /* Text mode is the same for Unicode and regular files */
156                         {
157                                 gchar *utf8 = convert_latin1_to_utf8(buf, len);
158                                 if(utf8 != NULL)
159                                 {
160                                         g_fprintf(str->file_pointer, "%s", utf8);
161                                         g_free(utf8);
162                                 }
163                         }
164                         
165                         str->write_count += len;
166                         break;
167                 default:
168                         g_warning("%s: Writing to this kind of stream unsupported.", __func__);
169         }
170 }
171
172 /* Internal function: write a Unicode buffer with length to a stream. */
173 static void
174 write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len)
175 {
176         switch(str->type)
177         {
178                 case STREAM_TYPE_WINDOW:
179                         /* Each window type has a different way of printing to it */
180                         switch(str->window->type)
181                         {
182                                 /* Printing to these windows' streams does nothing */
183                                 case wintype_Blank:
184                                 case wintype_Pair:
185                                 case wintype_Graphics:
186                                         str->write_count += len;
187                                         break;
188                                         
189                             /* Text grid window */
190                             case wintype_TextGrid:
191                             {
192                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
193                                 if(utf8 != NULL)
194                                 {
195                                     /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */
196                                     write_utf8_to_grid(str->window, utf8);
197                                     g_free(utf8);
198                                 }
199                             }
200                                 str->write_count += len;
201                                 break;
202                                         
203                                 /* Text buffer window */        
204                                 case wintype_TextBuffer:
205                                 {
206                                         gchar *utf8 = convert_ucs4_to_utf8(buf, len);
207                                         if(utf8 != NULL)
208                                         {
209                                                 write_utf8_to_window(str->window, utf8);
210                                                 g_free(utf8);
211                                         }
212                                 }       
213                                         str->write_count += len;
214                                         break;
215                                 default:
216                                         g_warning("%s: Writing to this kind of window unsupported.", __func__);
217                         }
218                         
219                         /* Now write the same buffer to the window's echo stream */
220                         if(str->window->echo_stream != NULL)
221                                 write_buffer_to_stream_uni(str->window->echo_stream, buf, len);
222                         
223                         break;
224                         
225                 case STREAM_TYPE_MEMORY:
226                         if(str->unicode && str->ubuffer)
227                         {
228                                 int copycount = MIN(len, str->buflen - str->mark);
229                                 memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32));
230                                 str->mark += copycount;
231                         }
232                         if(!str->unicode && str->buffer)
233                         {
234                                 gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
235                                 int copycount = MIN(len, str->buflen - str->mark);
236                                 memmove(str->buffer + str->mark, latin1, copycount);
237                                 g_free(latin1);
238                                 str->mark += copycount;
239                         }
240
241                         str->write_count += len;
242                         break;
243                         
244                 case STREAM_TYPE_FILE:
245                         if(str->binary) 
246                         {
247                                 if(str->unicode) 
248                                 {
249                                         gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len);
250                                         fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer);
251                                         g_free(writebuffer);
252                                 } 
253                                 else /* Regular file */
254                                 {
255                                         gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len);
256                                         fwrite(latin1, sizeof(gchar), len, str->file_pointer);
257                                         g_free(latin1);
258                                 }
259                         }
260                         else /* Text mode is the same for Unicode and regular files */
261                         {
262                                 gchar *utf8 = convert_ucs4_to_utf8(buf, len);
263                                 if(utf8 != NULL) 
264                                 {
265                                         g_fprintf(str->file_pointer, "%s", utf8);
266                                         g_free(utf8);
267                                 }
268                         }
269                         
270                         str->write_count += len;
271                         break;
272                 default:
273                         g_warning("%s: Writing to this kind of stream unsupported.", __func__);
274         }
275 }
276
277 /**
278  * glk_put_char_stream:
279  * @str: An output stream.
280  * @ch: A character in Latin-1 encoding.
281  *
282  * Prints one character @ch to the stream @str. It is illegal for @str to be
283  * %NULL, or an input-only stream.
284  */
285 void
286 glk_put_char_stream(strid_t str, unsigned char ch)
287 {
288         g_return_if_fail(str != NULL);
289         g_return_if_fail(str->file_mode != filemode_Read);
290         
291         write_buffer_to_stream(str, (gchar *)&ch, 1);
292 }
293
294 /**
295  * glk_put_char_stream_uni:
296  * @str: An output stream.
297  * @ch: A Unicode code point.
298  *
299  * Prints one character @ch to the stream @str. It is illegal for @str to be
300  * %NULL, or an input-only stream.
301  */
302 void
303 glk_put_char_stream_uni(strid_t str, glui32 ch)
304 {
305         g_return_if_fail(str != NULL);
306         g_return_if_fail(str->file_mode != filemode_Read);
307         
308         write_buffer_to_stream_uni(str, &ch, 1);
309 }
310
311 /**
312  * glk_put_string_stream:
313  * @str: An output stream.
314  * @s: A null-terminated string in Latin-1 encoding.
315  *
316  * Prints @s to the stream @str. It is illegal for @str to be %NULL, or an
317  * input-only stream.
318  */
319 void
320 glk_put_string_stream(strid_t str, char *s)
321 {
322         g_return_if_fail(str != NULL);
323         g_return_if_fail(str->file_mode != filemode_Read);
324
325         write_buffer_to_stream(str, s, strlen(s));
326 }
327
328 /**
329  * glk_put_string_stream_uni:
330  * @str: An output stream.
331  * @s: A null-terminated array of Unicode code points.
332  *
333  * Prints @s to the stream @str. It is illegal for @str to be %NULL, or an
334  * input-only stream.
335  */
336 void
337 glk_put_string_stream_uni(strid_t str, glui32 *s)
338 {
339         g_return_if_fail(str != NULL);
340         g_return_if_fail(str->file_mode != filemode_Read);
341         
342         /* An impromptu strlen() for glui32 arrays */
343         glong len = 0;
344         glui32 *ptr = s;
345         while(*ptr++)
346                 len++;
347         write_buffer_to_stream_uni(str, s, len);
348 }
349
350 /**
351  * glk_put_buffer_stream:
352  * @str: An output stream.
353  * @buf: An array of characters in Latin-1 encoding.
354  * @len: Length of @buf.
355  *
356  * Prints @buf to the stream @str. It is illegal for @str to be %NULL, or an
357  * input-only stream.
358  */
359 void
360 glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
361 {
362         g_return_if_fail(str != NULL);
363         g_return_if_fail(str->file_mode != filemode_Read);
364         
365         write_buffer_to_stream(str, buf, len);
366 }
367
368 /**
369  * glk_put_buffer_stream_uni:
370  * @str: An output stream.
371  * @buf: An array of Unicode code points.
372  * @len: Length of @buf.
373  *
374  * Prints @buf to the stream @str. It is illegal for @str to be %NULL, or an
375  * input-only stream.
376  */
377 void
378 glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
379 {
380         g_return_if_fail(str != NULL);
381         g_return_if_fail(str->file_mode != filemode_Read);
382         
383         write_buffer_to_stream_uni(str, buf, len);
384 }
385
386 /*
387  *
388  **************** READING FUNCTIONS ********************************************
389  *
390  */
391
392 /* Internal function: Read one big-endian four-byte character from file fp and
393 return it as a Unicode code point, or -1 on EOF */
394 static glsi32
395 read_ucs4be_char_from_file(FILE *fp)
396 {
397         unsigned char readbuffer[4];
398         if(fread(readbuffer, sizeof(unsigned char), 4, fp) < 4)
399                 return -1; /* EOF */
400         return
401                 readbuffer[0] << 24 | 
402                 readbuffer[1] << 16 | 
403                 readbuffer[2] << 8  | 
404                 readbuffer[3];
405 }
406
407 /* Internal function: Read one UTF-8 character, which may be more than one byte,
408 from file fp and return it as a Unicode code point, or -1 on EOF */
409 static glsi32
410 read_utf8_char_from_file(FILE *fp)
411 {
412         gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */
413         int foo;
414         gunichar charresult = (gunichar)-2;
415         for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) 
416         {
417                 int ch = fgetc(fp);
418                 if(ch == EOF)
419                         return -1;
420                 readbuffer[foo] = (gchar)ch;
421                 charresult = g_utf8_get_char_validated(readbuffer, foo + 1);
422                 /* charresult is -1 if invalid, -2 if incomplete, and the unicode code
423                 point otherwise */
424         }
425         /* Silently return unknown characters as 0xFFFD, Replacement Character */
426         if(charresult == (gunichar)-1 || charresult == (gunichar)-2) 
427                 return 0xFFFD;
428         return charresult;
429 }
430
431 /* Internal function: Tell whether this code point is a Unicode newline. The
432 file pointer and eight-bit flag are included in case the newline is a CR 
433 (U+000D). If the next character is LF (U+000A) then it also belongs to the
434 newline. */
435 static gboolean
436 is_unicode_newline(glsi32 ch, FILE *fp, gboolean utf8)
437 {
438         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
439                 return TRUE;
440         if(ch == 0x0D) {
441                 glsi32 ch2 = utf8? read_utf8_char_from_file(fp) : 
442                         read_ucs4be_char_from_file(fp);
443                 if(ch2 != 0x0A)
444                         fseek(fp, utf8? -1 : -4, SEEK_CUR);
445                 return TRUE;
446         }
447         return FALSE;
448 }
449
450 /* Internal function: Read one character from a stream. Returns a value which
451  can be returned unchanged by glk_get_char_stream_uni(), but 
452  glk_get_char_stream() must replace high values by the placeholder character. */
453 glsi32
454 get_char_stream_common(strid_t str)
455 {
456         switch(str->type)
457         {
458                 case STREAM_TYPE_MEMORY:
459                         if(str->unicode)
460                         {
461                                 if(!str->ubuffer || str->mark >= str->buflen)
462                                         return -1;
463                                 glui32 ch = str->ubuffer[str->mark++];
464                                 str->read_count++;
465                                 return ch;
466                         }
467                         else
468                         {
469                                 if(!str->buffer || str->mark >= str->buflen)
470                                         return -1;
471                                 unsigned char ch = str->buffer[str->mark++];
472                                 str->read_count++;
473                                 return ch;
474                         }
475                         break;
476                         
477                 case STREAM_TYPE_FILE:
478                         if(str->binary) 
479                         {
480                                 if(str->unicode) 
481                                 {
482                                         glsi32 ch = read_ucs4be_char_from_file(str->file_pointer);
483                                         if(ch == -1)
484                                                 return -1;
485                                         str->read_count++;
486                                         return ch;
487                                 }
488                                 else /* Regular file */
489                                 {
490                                         int ch = fgetc(str->file_pointer);
491                                         if(ch == EOF)
492                                                 return -1;
493                                         
494                                         str->read_count++;
495                                         return ch;
496                                 }
497                         }
498                         else /* Text mode is the same for Unicode and regular files */
499                         {
500                                 glsi32 ch = read_utf8_char_from_file(str->file_pointer);
501                                 if(ch == -1)
502                                         return -1;
503                                         
504                                 str->read_count++;
505                                 return ch;
506                         }
507                 default:
508                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
509                         return -1;
510         }
511 }
512
513 /**
514  * glk_get_char_stream:
515  * @str: An input stream.
516  *
517  * Reads one character from the stream @str. (There is no notion of a ``current
518  * input stream.'') It is illegal for @str to be %NULL, or an output-only
519  * stream.
520  *
521  * The result will be between 0 and 255. As with all basic text functions, Glk
522  * assumes the Latin-1 encoding. If the end of the stream has been reached, the
523  * result will be -1. Note that high-bit characters (128..255) are
524  * <emphasis>not</emphasis> returned as negative numbers.
525  *
526  * If the stream contains Unicode data --- for example, if it was created with
527  * glk_stream_open_file_uni() or glk_stream_open_memory_uni() --- then
528  * characters beyond 255 will be returned as 0x3F ("?").
529  *
530  * Returns: A character value between 0 and 255, or -1 on end of stream.
531  */
532 glsi32
533 glk_get_char_stream(strid_t str)
534 {
535         g_return_val_if_fail(str != NULL, -1);
536         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
537         
538         glsi32 ch = get_char_stream_common(str);
539         return (ch > 0xFF)? PLACEHOLDER : ch;
540 }
541
542 /**
543  * glk_get_char_stream_uni:
544  * @str: An input stream.
545  *
546  * Reads one character from the stream @str. The result will be between 0 and 
547  * 0x7FFFFFFF. If the end of the stream has been reached, the result will be -1.
548  *
549  * Returns: A character value between 0 and 255, or -1 on end of stream.
550  */
551 glsi32
552 glk_get_char_stream_uni(strid_t str)
553 {
554         g_return_val_if_fail(str != NULL, -1);
555         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1);
556         
557         return get_char_stream_common(str);
558 }
559
560 /**
561  * glk_get_buffer_stream:
562  * @str: An input stream.
563  * @buf: A buffer with space for at least @len characters.
564  * @len: The number of characters to read.
565  *
566  * Reads @len characters from @str, unless the end of stream is reached first.
567  * No terminal null is placed in the buffer.
568  *
569  * Returns: The number of characters actually read.
570  */
571 glui32
572 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
573 {
574         g_return_val_if_fail(str != NULL, 0);
575         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
576         g_return_val_if_fail(buf != NULL, 0);
577         
578         switch(str->type)
579         {
580                 case STREAM_TYPE_MEMORY:
581                 {
582                         int copycount = 0;
583                         if(str->unicode)
584                         {
585                                 while(copycount < len && str->ubuffer && str->mark < str->buflen) 
586                                 {
587                                         glui32 ch = str->ubuffer[str->mark++];
588                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
589                                 }
590                         }
591                         else
592                         {
593                                 if(str->buffer) /* if not, copycount stays 0 */
594                                         copycount = MIN(len, str->buflen - str->mark);
595                                 memmove(buf, str->buffer + str->mark, copycount);
596                                 str->mark += copycount;
597                         }
598
599                         str->read_count += copycount;           
600                         return copycount;
601                 }       
602                 case STREAM_TYPE_FILE:
603                         if(str->binary) 
604                         {
605                                 if(str->unicode) /* Binary file with 4-byte characters */
606                                 {
607                                         /* Read len characters of 4 bytes each */
608                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
609                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
610                                         /* If there was an incomplete character */
611                                         if(count % 4 != 0) 
612                                         {
613                                                 count -= count % 4;
614                                                 g_warning("%s: Incomplete character in binary Unicode file.", __func__);
615                                         }
616                                         
617                                         int foo;
618                                         for(foo = 0; foo < count; foo += 4)
619                                         {
620                                                 glsi32 ch = readbuffer[foo] << 24
621                                                         | readbuffer[foo + 1] << 16
622                                                         | readbuffer[foo + 2] << 8
623                                                         | readbuffer[foo + 3];
624                                                 buf[foo / 4] = (ch > 255)? 0x3F : (char)ch;
625                                         }
626                                         g_free(readbuffer);
627                                         str->read_count += count / 4;
628                                         return count / 4;
629                                 }
630                                 else /* Regular binary file */
631                                 {
632                                         size_t count = fread(buf, sizeof(char), len, str->file_pointer);
633                                         str->read_count += count;
634                                         return count;
635                                 }
636                         }
637                         else /* Text mode is the same for Unicode and regular files */
638                         {
639                                 /* Do it character-by-character */
640                                 int foo;
641                                 for(foo = 0; foo < len; foo++)
642                                 {
643                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
644                                         if(ch == -1)
645                                                 break;
646                                         str->read_count++;
647                                         buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch;
648                                 }
649                                 return foo;
650                         }
651                 default:
652                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
653                         return 0;
654         }
655 }
656
657 /**
658  * glk_get_buffer_stream_uni:
659  * @str: An input stream.
660  * @buf: A buffer with space for at least @len Unicode code points.
661  * @len: The number of characters to read.
662  *
663  * Reads @len Unicode characters from @str, unless the end of stream is reached 
664  * first. No terminal null is placed in the buffer.
665  *
666  * Returns: The number of Unicode characters actually read.
667  */
668 glui32
669 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len)
670 {
671         g_return_val_if_fail(str != NULL, 0);
672         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
673         g_return_val_if_fail(buf != NULL, 0);
674         
675         switch(str->type)
676         {
677                 case STREAM_TYPE_MEMORY:
678                 {
679                         int copycount = 0;
680                         if(str->unicode)
681                         {
682                                 if(str->ubuffer) /* if not, copycount stays 0 */
683                                         copycount = MIN(len, str->buflen - str->mark);
684                                 memmove(buf, str->ubuffer + str->mark, copycount * 4);
685                                 str->mark += copycount;
686                         }
687                         else
688                         {
689                                 while(copycount < len && str->buffer && str->mark < str->buflen)
690                                 {
691                                         unsigned char ch = str->buffer[str->mark++];
692                                         buf[copycount++] = ch;
693                                 }
694                         }
695
696                         str->read_count += copycount;           
697                         return copycount;
698                 }       
699                 case STREAM_TYPE_FILE:
700                         if(str->binary) 
701                         {
702                                 if(str->unicode) /* Binary file with 4-byte characters */
703                                 {
704                                         /* Read len characters of 4 bytes each */
705                                         unsigned char *readbuffer = g_new0(unsigned char, 4 * len);
706                                         size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer);
707                                         /* If there was an incomplete character */
708                                         if(count % 4 != 0) 
709                                         {
710                                                 count -= count % 4;
711                                                 g_warning("%s: Incomplete character in binary Unicode file.", __func__);
712                                         }
713                                         
714                                         int foo;
715                                         for(foo = 0; foo < count; foo += 4)
716                                                 buf[foo / 4] = readbuffer[foo] << 24
717                                                         | readbuffer[foo + 1] << 16
718                                                         | readbuffer[foo + 2] << 8
719                                                         | readbuffer[foo + 3];
720                                         g_free(readbuffer);
721                                         str->read_count += count / 4;
722                                         return count / 4;
723                                 }
724                                 else /* Regular binary file */
725                                 {
726                                         unsigned char *readbuffer = g_new0(unsigned char, len);
727                                         size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer);
728                                         int foo;
729                                         for(foo = 0; foo < count; foo++)
730                                                 buf[foo] = readbuffer[foo];
731                                         g_free(readbuffer);
732                                         str->read_count += count;
733                                         return count;
734                                 }
735                         }
736                         else /* Text mode is the same for Unicode and regular files */
737                         {
738                                 /* Do it character-by-character */
739                                 int foo;
740                                 for(foo = 0; foo < len; foo++)
741                                 {
742                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
743                                         if(ch == -1)
744                                                 break;
745                                         str->read_count++;
746                                         buf[foo] = ch;
747                                 }
748                                 return foo;
749                         }
750                 default:
751                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
752                         return 0;
753         }
754 }
755
756 /**
757  * glk_get_line_stream:
758  * @str: An input stream.
759  * @buf: A buffer with space for at least @len characters.
760  * @len: The number of characters to read, plus one.
761  *
762  * Reads characters from @str, until either @len - 1 characters have been read
763  * or a newline has been read. It then puts a terminal null ('\0') aracter on
764  * the end. It returns the number of characters actually read, including the
765  * newline (if there is one) but not including the terminal null.
766  *
767  * It is usually more efficient to read several characters at once with
768  * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling
769  * glk_get_char_stream() several times.
770  *
771  * Returns: The number of characters actually read.
772  */
773 glui32
774 glk_get_line_stream(strid_t str, char *buf, glui32 len)
775 {
776         g_return_val_if_fail(str != NULL, 0);
777         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
778         g_return_val_if_fail(buf != NULL, 0);
779
780         switch(str->type)
781         {
782                 case STREAM_TYPE_MEMORY:
783                 {
784                         int copycount = 0;
785                         if(str->unicode)
786                         {
787                                 /* Do it character-by-character */
788                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
789                                 {
790                                         glui32 ch = str->ubuffer[str->mark++];
791                                         /* Check for Unicode newline; slightly different than
792                                         in file streams */
793                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
794                                         {
795                                                 buf[copycount++] = '\n';
796                                                 break;
797                                         }
798                                         if(ch == 0x0D)
799                                         {
800                                                 if(str->ubuffer[str->mark] == 0x0A)
801                                                         str->mark++; /* skip past next newline */
802                                                 buf[copycount++] = '\n';
803                                                 break;
804                                         }
805                                         buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
806                                 }
807                                 buf[copycount] = '\0';
808                         }
809                         else
810                         {
811                                 if(str->buffer) /* if not, copycount stays 0 */
812                                         copycount = MIN(len - 1, str->buflen - str->mark);
813                                 char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount);
814                                 if(endptr) /* newline was found */
815                                         copycount = endptr - buf; /* Real copy count */
816                                 buf[copycount] = '\0';
817                                 str->mark += copycount;
818                         }
819                         
820                         str->read_count += copycount;
821                         return copycount;
822                 }       
823                 case STREAM_TYPE_FILE:
824                         if(str->binary) 
825                         {
826                                 if(str->unicode) /* Binary file with 4-byte characters */
827                                 {
828                                         /* Do it character-by-character */
829                                         int foo;
830                                         for(foo = 0; foo < len - 1; foo++)
831                                         {
832                                                 glsi32 ch = read_ucs4be_char_from_file(str->file_pointer);
833                                                 if(ch == -1) 
834                                                 {
835                                                         buf[foo] = '\0';
836                                                         return foo - 1;
837                                                 }
838                                                 str->read_count++;
839                                                 if(is_unicode_newline(ch, str->file_pointer, FALSE))
840                                                 {
841                                                         buf[foo] = '\n';
842                                                         buf[foo + 1] = '\0';
843                                                         return foo;
844                                                 }
845                                                 buf[foo] = (ch > 0xFF)? '?' : (char)ch;
846                                         }
847                                         buf[len] = '\0';
848                                         return foo;
849                                 }
850                                 else /* Regular binary file */
851                                 {
852                                         fgets(buf, len, str->file_pointer);
853                                         str->read_count += strlen(buf);
854                                         return strlen(buf);
855                                 }
856                         }
857                         else /* Text mode is the same for Unicode and regular files */
858                         {
859                                 /* Do it character-by-character */
860                                 int foo;
861                                 for(foo = 0; foo < len - 1; foo++)
862                                 {
863                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
864                                         if(ch == -1)
865                                         {
866                                                 buf[foo] = '\0';
867                                                 return foo - 1;
868                                         }
869                                         str->read_count++;
870                                         if(is_unicode_newline(ch, str->file_pointer, TRUE))
871                                         {
872                                                 buf[foo] = '\n';
873                                                 buf[foo + 1] = '\0';
874                                                 return foo;
875                                         }
876                                         buf[foo] = (ch > 0xFF)? 0x3F : (char)ch;
877                                 }
878                                 buf[len] = '\0';
879                                 return foo;
880                         }
881                 default:
882                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
883                         return 0;
884         }
885 }
886
887 /**
888  * glk_get_line_stream_uni:
889  * @str: An input stream.
890  * @buf: A buffer with space for at least @len Unicode code points.
891  * @len: The number of characters to read, plus one.
892  *
893  * Reads Unicode characters from @str, until either @len - 1 Unicode characters
894  * have been read or a newline has been read. It then puts a terminal null (a
895  * zero value) on the end.
896  *
897  * Returns: The number of characters actually read, including the newline (if
898  * there is one) but not including the terminal null.
899  */
900 glui32
901 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len)
902 {
903         g_return_val_if_fail(str != NULL, 0);
904         g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0);
905         g_return_val_if_fail(buf != NULL, 0);
906
907         switch(str->type)
908         {
909                 case STREAM_TYPE_MEMORY:
910                 {
911                         int copycount = 0;
912                         if(str->unicode)
913                         {
914                                 /* Do it character-by-character */
915                                 while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) 
916                                 {
917                                         glui32 ch = str->ubuffer[str->mark++];
918                                         /* Check for Unicode newline; slightly different than
919                                         in file streams */
920                                         if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
921                                         {
922                                                 buf[copycount++] = '\n';
923                                                 break;
924                                         }
925                                         if(ch == 0x0D)
926                                         {
927                                                 if(str->ubuffer[str->mark] == 0x0A)
928                                                         str->mark++; /* skip past next newline */
929                                                 buf[copycount++] = '\n';
930                                                 break;
931                                         }
932                                         buf[copycount++] = ch;
933                                 }
934                                 buf[copycount] = '\0';
935                         }
936                         else
937                         {
938                                 /* No recourse to memccpy(), so do it character-by-character */
939                                 while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
940                                 {
941                                         gchar ch = str->buffer[str->mark++];
942                                         /* Check for newline */
943                                         if(ch == '\n') /* Also check for \r and \r\n? */
944                                         {
945                                                 buf[copycount++] = '\n';
946                                                 break;
947                                         }
948                                         buf[copycount++] = (unsigned char)ch;
949                                 }
950                                 buf[copycount] = 0;
951                         }
952                         
953                         str->read_count += copycount;
954                         return copycount;
955                 }       
956                 case STREAM_TYPE_FILE:
957                         if(str->binary) 
958                         {
959                                 if(str->unicode) /* Binary file with 4-byte characters */
960                                 {
961                                         /* Do it character-by-character */
962                                         int foo;
963                                         for(foo = 0; foo < len - 1; foo++)
964                                         {
965                                                 glsi32 ch = read_ucs4be_char_from_file(str->file_pointer);
966                                                 if(ch == -1) 
967                                                 {
968                                                         buf[foo] = 0;
969                                                         return foo - 1;
970                                                 }
971                                                 str->read_count++;
972                                                 if(is_unicode_newline(ch, str->file_pointer, FALSE))
973                                                 {
974                                                         buf[foo] = ch; /* Preserve newline types??? */
975                                                         buf[foo + 1] = 0;
976                                                         return foo;
977                                                 }
978                                                 buf[foo] = ch;
979                                         }
980                                         buf[len] = 0;
981                                         return foo;
982                                 }
983                                 else /* Regular binary file */
984                                 {
985                                         gchar *readbuffer = g_new0(gchar, len);
986                                         fgets(readbuffer, len, str->file_pointer);
987                                         glui32 count = strlen(readbuffer) + 1; /* Copy terminator */
988                                         int foo;
989                                         for(foo = 0; foo < count; foo++)
990                                                 buf[foo] = (unsigned char)(readbuffer[foo]);
991                                         str->read_count += count;
992                                         return count;
993                                 }
994                         }
995                         else /* Text mode is the same for Unicode and regular files */
996                         {
997                                 /* Do it character-by-character */
998                                 int foo;
999                                 for(foo = 0; foo < len - 1; foo++)
1000                                 {
1001                                         glsi32 ch = read_utf8_char_from_file(str->file_pointer);
1002                                         if(ch == -1)
1003                                         {
1004                                                 buf[foo] = 0;
1005                                                 return foo - 1;
1006                                         }
1007                                         str->read_count++;
1008                                         if(is_unicode_newline(ch, str->file_pointer, TRUE))
1009                                         {
1010                                                 buf[foo] = ch; /* Preserve newline types??? */
1011                                                 buf[foo + 1] = 0;
1012                                                 return foo;
1013                                         }
1014                                         buf[foo] = ch;
1015                                 }
1016                                 buf[len] = 0;
1017                                 return foo;
1018                         }
1019                 default:
1020                         g_warning("%s: Reading from this kind of stream unsupported.", __func__);
1021                         return 0;
1022         }
1023 }
1024
1025 /*
1026  *
1027  **************** SEEKING FUNCTIONS ********************************************
1028  *
1029  */
1030
1031 /**
1032  * glk_stream_get_position:
1033  * @str: A file or memory stream.
1034  *
1035  * Returns the position of the read/write mark in @str. For memory streams and
1036  * binary file streams, this is exactly the number of characters read or written
1037  * from the beginning of the stream (unless you have moved the mark with
1038  * glk_stream_set_position().) For text file streams, matters are more 
1039  * ambiguous, since (for example) writing one byte to a text file may store more
1040  * than one character in the platform's native encoding. You can only be sure
1041  * that the position increases as you read or write to the file.
1042  *
1043  * Additional complication: for Latin-1 memory and file streams, a character is
1044  * a byte. For Unicode memory and file streams (those created by
1045  * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is
1046  * a 32-bit word. So in a binary Unicode file, positions are multiples of four
1047  * bytes.
1048  *
1049  * Returns: position of the read/write mark in @str.
1050  */
1051 glui32
1052 glk_stream_get_position(strid_t str)
1053 {
1054         g_return_val_if_fail(str != NULL, 0);
1055         
1056         switch(str->type)
1057         {
1058                 case STREAM_TYPE_MEMORY:
1059                         return str->mark;
1060                 case STREAM_TYPE_FILE:
1061                         return ftell(str->file_pointer);
1062                 default:
1063                         g_warning("%s: Seeking not supported on this type of stream.",
1064                                 __func__);
1065                         return 0;
1066         }
1067 }
1068
1069 /**
1070  * glk_stream_set_position:
1071  * @str: A file or memory stream.
1072  * @pos: The position to set the mark to, relative to @seekmode.
1073  * @seekmode: One of #seekmode_Start, #seekmode_Current, or #seekmode_End.
1074  *
1075  * Sets the position of the read/write mark in @str. The position is controlled
1076  * by @pos, and the meaning of @pos is controlled by @seekmode:
1077  * <itemizedlist>
1078  *  <listitem>#seekmode_Start: @pos characters after the beginning of the file.
1079  *  </listitem>
1080  *  <listitem>#seekmode_Current: @pos characters after the current position
1081  *  (moving backwards if @pos is negative.)</listitem>
1082  *  <listitem>#seekmode_End: @pos characters after the end of the file. (@pos
1083  *  should always be zero or negative, so that this will move backwards to a
1084  *  position within the file.</listitem>
1085  * </itemizedlist>
1086  * It is illegal to specify a position before the beginning or after the end of
1087  * the file.
1088  *
1089  * In binary files, the mark position is exact --- it corresponds with the
1090  * number of characters you have read or written. In text files, this mapping 
1091  * can vary, because of linefeed conventions or other character-set 
1092  * approximations. glk_stream_set_position() and glk_stream_get_position()
1093  * measure positions in the platform's native encoding --- after character
1094  * cookery. Therefore, in a text stream, it is safest to use
1095  * glk_stream_set_position() only to move to the beginning or end of a file, or
1096  * to a position determined by glk_stream_get_position().
1097  *
1098  * Again, in Latin-1 streams, characters are bytes. In Unicode streams,
1099  * characters are 32-bit words, or four bytes each.
1100  */
1101 void
1102 glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
1103 {
1104         g_return_if_fail(str != NULL);
1105         g_return_if_fail(!(seekmode == seekmode_Start && pos < 0));
1106         g_return_if_fail(!(seekmode == seekmode_End || pos > 0));
1107         
1108         switch(str->type)
1109         {
1110                 case STREAM_TYPE_MEMORY:
1111                         switch(seekmode)
1112                         {
1113                                 case seekmode_Start:   str->mark = pos;  break;
1114                                 case seekmode_Current: str->mark += pos; break;
1115                                 case seekmode_End:     str->mark = str->buflen + pos; break;
1116                                 default:
1117                                         g_assert_not_reached();
1118                                         return;
1119                         }
1120                         break;
1121                 case STREAM_TYPE_FILE:
1122                 {
1123                         int whence;
1124                         switch(seekmode)
1125                         {
1126                                 case seekmode_Start:   whence = SEEK_SET; break;
1127                                 case seekmode_Current: whence = SEEK_CUR; break;
1128                                 case seekmode_End:     whence = SEEK_END; break;
1129                                 default:
1130                                         g_assert_not_reached();
1131                                         return;
1132                         }
1133                         fseek(str->file_pointer, pos, whence);
1134                         break;
1135                 }
1136                 default:
1137                         g_warning("%s: Seeking not supported on this type of stream.", __func__);
1138                         return;
1139         }
1140 }
1141