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