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