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