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