1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
30 #include "data/file-name.h"
31 #include "data/settings.h"
32 #include "libpspp/assertion.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/compiler.h"
35 #include "libpspp/message.h"
36 #include "libpspp/start-date.h"
37 #include "libpspp/string-map.h"
38 #include "libpspp/u8-line.h"
39 #include "libpspp/version.h"
40 #include "output/ascii.h"
41 #include "output/cairo.h"
42 #include "output/chart-item-provider.h"
43 #include "output/driver-provider.h"
44 #include "output/message-item.h"
45 #include "output/options.h"
46 #include "output/render.h"
47 #include "output/tab.h"
48 #include "output/table-item.h"
49 #include "output/text-item.h"
52 #include "gl/minmax.h"
53 #include "gl/xalloc.h"
56 #define _(msgid) gettext (msgid)
58 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
62 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
63 * RENDER_N_LINES * RENDER_N_LINES)
65 static const ucs4_t ascii_box_chars[N_BOX] =
96 static const ucs4_t unicode_box_chars[N_BOX] =
98 0x0020, 0x2575, 0x2551,
99 0x2574, 0x256f, 0x255c,
100 0x2550, 0x255b, 0x255d,
101 0x2577, 0x2502, 0x2551,
102 0x256e, 0x2524, 0x2562,
103 0x2555, 0x2561, 0x2563,
104 0x2551, 0x2551, 0x2551,
105 0x2556, 0x2562, 0x2562,
106 0x2557, 0x2563, 0x2563,
107 0x2576, 0x2570, 0x2559,
108 0x2500, 0x2534, 0x2568,
109 0x2550, 0x2567, 0x2569,
110 0x256d, 0x251c, 0x255f,
111 0x252c, 0x253c, 0x256a,
112 0x2564, 0x256a, 0x256c,
113 0x2553, 0x255f, 0x255f,
114 0x2565, 0x256b, 0x256b,
115 0x2566, 0x256c, 0x256c,
116 0x2550, 0x2558, 0x255a,
117 0x2550, 0x2567, 0x2569,
118 0x2550, 0x2567, 0x2569,
119 0x2552, 0x255e, 0x2560,
120 0x2564, 0x256a, 0x256c,
121 0x2564, 0x256a, 0x256c,
122 0x2554, 0x2560, 0x2560,
123 0x2566, 0x256c, 0x256c,
127 make_box_index (int left, int right, int top, int bottom)
129 return ((right * 3 + bottom) * 3 + left) * 3 + top;
132 /* How to emphasize text. */
135 EMPH_BOLD, /* Overstrike for bold. */
136 EMPH_UNDERLINE, /* Overstrike for underlining. */
137 EMPH_NONE /* No emphasis. */
140 /* ASCII output driver. */
143 struct output_driver driver;
145 /* User parameters. */
146 bool append; /* Append if output file already exists? */
147 bool headers; /* Print headers at top of page? */
148 bool paginate; /* Insert formfeeds? */
149 bool squeeze_blank_lines; /* Squeeze multiple blank lines into one? */
150 enum emphasis_style emphasis; /* How to emphasize text. */
151 char *chart_file_name; /* Name of files used for charts. */
153 int width; /* Page width. */
154 int length; /* Page length minus margins and header. */
155 bool auto_width; /* Use viewwidth as page width? */
156 bool auto_length; /* Use viewlength as page width? */
158 int top_margin; /* Top margin in lines. */
159 int bottom_margin; /* Bottom margin in lines. */
161 const ucs4_t *box; /* Line & box drawing characters. */
163 /* Internal state. */
167 char *file_name; /* Output file name. */
168 FILE *file; /* Output file. */
169 bool error; /* Output error? */
170 int page_number; /* Current page number. */
171 struct u8_line *lines; /* Page content. */
172 int allocated_lines; /* Number of lines allocated. */
173 int chart_cnt; /* Number of charts so far. */
177 static const struct output_driver_class ascii_driver_class;
179 static void ascii_submit (struct output_driver *, const struct output_item *);
181 static int vertical_margins (const struct ascii_driver *);
183 static bool update_page_size (struct ascii_driver *, bool issue_error);
184 static int parse_page_size (struct driver_option *);
186 static void ascii_close_page (struct ascii_driver *);
187 static bool ascii_open_page (struct ascii_driver *);
189 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
190 enum render_line_style styles[TABLE_N_AXES][2]);
191 static void ascii_measure_cell_width (void *, const struct table_cell *,
193 static int ascii_measure_cell_height (void *, const struct table_cell *,
195 static void ascii_draw_cell (void *, const struct table_cell *,
196 int bb[TABLE_N_AXES][2],
197 int clip[TABLE_N_AXES][2]);
200 reallocate_lines (struct ascii_driver *a)
202 if (a->length > a->allocated_lines)
205 a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
206 for (i = a->allocated_lines; i < a->length; i++)
207 u8_line_init (&a->lines[i]);
208 a->allocated_lines = a->length;
213 static struct ascii_driver *
214 ascii_driver_cast (struct output_driver *driver)
216 assert (driver->class == &ascii_driver_class);
217 return UP_CAST (driver, struct ascii_driver, driver);
220 static struct driver_option *
221 opt (struct output_driver *d, struct string_map *options, const char *key,
222 const char *default_value)
224 return driver_option_get (d, options, key, default_value);
227 static struct output_driver *
228 ascii_create (const char *file_name, enum settings_output_devices device_type,
229 struct string_map *o)
231 enum { BOX_ASCII, BOX_UNICODE } box;
232 struct output_driver *d;
233 struct ascii_driver *a;
236 a = xzalloc (sizeof *a);
238 output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
239 a->append = parse_boolean (opt (d, o, "append", "false"));
240 a->headers = parse_boolean (opt (d, o, "headers", "false"));
241 a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
242 a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
243 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
245 "underline", EMPH_UNDERLINE,
249 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
251 a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
252 a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
254 a->width = parse_page_size (opt (d, o, "width", "79"));
255 paper_length = parse_page_size (opt (d, o, "length", "66"));
256 a->auto_width = a->width < 0;
257 a->auto_length = paper_length < 0;
258 a->length = paper_length - vertical_margins (a);
260 box = parse_enum (opt (d, o, "box", "ascii"),
262 "unicode", BOX_UNICODE,
264 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
266 a->command_name = NULL;
267 a->title = xstrdup ("");
268 a->subtitle = xstrdup ("");
269 a->file_name = xstrdup (file_name);
274 a->allocated_lines = 0;
277 if (!update_page_size (a, true))
283 output_driver_destroy (d);
288 parse_page_size (struct driver_option *option)
290 int dim = atol (option->default_value);
292 if (option->value != NULL)
294 if (!strcmp (option->value, "auto"))
302 value = strtol (option->value, &tail, 0);
303 if (dim >= 1 && errno != ERANGE && *tail == '\0')
306 error (0, 0, _("%s: %s must be positive integer or `auto'"),
307 option->driver_name, option->name);
311 driver_option_destroy (option);
317 vertical_margins (const struct ascii_driver *a)
319 return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
322 /* Re-calculates the page width and length based on settings,
323 margins, and, if "auto" is set, the size of the user's
324 terminal window or GUI output window. */
326 update_page_size (struct ascii_driver *a, bool issue_error)
328 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
331 a->width = settings_get_viewwidth ();
333 a->length = settings_get_viewlength () - vertical_margins (a);
335 if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
339 _("ascii: page excluding margins and headers "
340 "must be at least %d characters wide by %d lines long, but "
341 "as configured is only %d characters by %d lines"),
342 MIN_WIDTH, MIN_LENGTH,
343 a->width, a->length);
344 if (a->width < MIN_WIDTH)
345 a->width = MIN_WIDTH;
346 if (a->length < MIN_LENGTH)
347 a->length = MIN_LENGTH;
351 reallocate_lines (a);
357 ascii_destroy (struct output_driver *driver)
359 struct ascii_driver *a = ascii_driver_cast (driver);
363 ascii_close_page (a);
366 fn_close (a->file_name, a->file);
367 free (a->command_name);
371 free (a->chart_file_name);
372 for (i = 0; i < a->allocated_lines; i++)
373 u8_line_destroy (&a->lines[i]);
379 ascii_flush (struct output_driver *driver)
381 struct ascii_driver *a = ascii_driver_cast (driver);
384 ascii_close_page (a);
386 if (fn_close (a->file_name, a->file) != 0)
387 error (0, errno, _("ascii: closing output file `%s'"),
394 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
396 cell->contents = caption;
397 cell->options = TAB_LEFT;
398 cell->destructor = NULL;
402 ascii_output_table_item (struct ascii_driver *a,
403 const struct table_item *table_item)
405 const char *caption = table_item_get_caption (table_item);
406 struct render_params params;
407 struct render_page *page;
408 struct render_break x_break;
412 update_page_size (a, false);
416 /* XXX doesn't do well with very large captions */
417 struct table_cell cell;
418 ascii_init_caption_cell (caption, &cell);
419 caption_height = ascii_measure_cell_height (a, &cell, a->width);
424 params.draw_line = ascii_draw_line;
425 params.measure_cell_width = ascii_measure_cell_width;
426 params.measure_cell_height = ascii_measure_cell_height;
427 params.draw_cell = ascii_draw_cell,
429 params.size[H] = a->width;
430 params.size[V] = a->length - caption_height;
431 params.font_size[H] = 1;
432 params.font_size[V] = 1;
433 for (i = 0; i < RENDER_N_LINES; i++)
435 int width = i == RENDER_LINE_NONE ? 0 : 1;
436 params.line_widths[H][i] = width;
437 params.line_widths[V][i] = width;
440 if (a->file == NULL && !ascii_open_page (a))
443 page = render_page_create (¶ms, table_item_get_table (table_item));
444 for (render_break_init (&x_break, page, H);
445 render_break_has_next (&x_break); )
447 struct render_page *x_slice;
448 struct render_break y_break;
450 x_slice = render_break_next (&x_break, a->width);
451 for (render_break_init (&y_break, x_slice, V);
452 render_break_has_next (&y_break); )
454 struct render_page *y_slice;
460 space = a->length - a->y - caption_height;
461 if (render_break_next_size (&y_break) > space)
464 ascii_close_page (a);
465 if (!ascii_open_page (a))
470 y_slice = render_break_next (&y_break, space);
473 struct table_cell cell;
474 int bb[TABLE_N_AXES][2];
476 ascii_init_caption_cell (caption, &cell);
480 bb[V][1] = caption_height;
481 ascii_draw_cell (a, &cell, bb, bb);
482 a->y += caption_height;
485 render_page_draw (y_slice);
486 a->y += render_page_get_size (y_slice, V);
487 render_page_unref (y_slice);
489 render_break_destroy (&y_break);
491 render_break_destroy (&x_break);
495 ascii_output_text (struct ascii_driver *a, const char *text)
497 struct table_item *table_item;
499 table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
500 ascii_output_table_item (a, table_item);
501 table_item_unref (table_item);
505 ascii_submit (struct output_driver *driver,
506 const struct output_item *output_item)
508 struct ascii_driver *a = ascii_driver_cast (driver);
510 output_driver_track_current_command (output_item, &a->command_name);
515 if (is_table_item (output_item))
516 ascii_output_table_item (a, to_table_item (output_item));
518 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
520 struct chart_item *chart_item = to_chart_item (output_item);
523 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
525 if (file_name != NULL)
527 struct text_item *text_item;
529 text_item = text_item_create_format (
530 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
532 ascii_submit (driver, &text_item->output_item);
533 text_item_unref (text_item);
537 #endif /* HAVE_CAIRO */
538 else if (is_text_item (output_item))
540 const struct text_item *text_item = to_text_item (output_item);
541 enum text_item_type type = text_item_get_type (text_item);
542 const char *text = text_item_get_text (text_item);
546 case TEXT_ITEM_TITLE:
548 a->title = xstrdup (text);
551 case TEXT_ITEM_SUBTITLE:
553 a->subtitle = xstrdup (text);
556 case TEXT_ITEM_COMMAND_OPEN:
557 case TEXT_ITEM_COMMAND_CLOSE:
560 case TEXT_ITEM_BLANK_LINE:
565 case TEXT_ITEM_EJECT_PAGE:
567 ascii_close_page (a);
571 ascii_output_text (a, text);
575 else if (is_message_item (output_item))
577 const struct message_item *message_item = to_message_item (output_item);
578 const struct msg *msg = message_item_get_msg (message_item);
579 char *s = msg_to_string (msg, a->command_name);
580 ascii_output_text (a, s);
585 const struct output_driver_factory txt_driver_factory =
586 { "txt", "-", ascii_create };
587 const struct output_driver_factory list_driver_factory =
588 { "list", "-", ascii_create };
590 static const struct output_driver_class ascii_driver_class =
598 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
600 static void ascii_layout_cell (struct ascii_driver *,
601 const struct table_cell *,
602 int bb[TABLE_N_AXES][2],
603 int clip[TABLE_N_AXES][2],
604 int *width, int *height);
607 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
608 enum render_line_style styles[TABLE_N_AXES][2])
610 struct ascii_driver *a = a_;
617 /* Clip to the page. */
618 if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
621 x1 = MIN (bb[H][1], a->width);
622 y1 = MIN (bb[V][1] + a->y, a->length);
625 uc = a->box[make_box_index (styles[V][0], styles[V][1],
626 styles[H][0], styles[H][1])];
627 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
628 for (y = bb[V][0] + a->y; y < y1; y++)
630 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
631 for (x = x0; x < x1; x++)
633 memcpy (p, mbchar, mblen);
640 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
641 int *min_width, int *max_width)
643 struct ascii_driver *a = a_;
644 int bb[TABLE_N_AXES][2];
645 int clip[TABLE_N_AXES][2];
652 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
653 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
655 if (strchr (cell->contents, ' '))
658 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
661 *min_width = *max_width;
665 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
667 struct ascii_driver *a = a_;
668 int bb[TABLE_N_AXES][2];
669 int clip[TABLE_N_AXES][2];
676 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
677 ascii_layout_cell (a, cell, bb, clip, &w, &h);
682 ascii_draw_cell (void *a_, const struct table_cell *cell,
683 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
685 struct ascii_driver *a = a_;
688 ascii_layout_cell (a, cell, bb, clip, &w, &h);
692 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
694 assert (y < a->allocated_lines);
695 return u8_line_reserve (&a->lines[y], x0, x1, n);
699 text_draw (struct ascii_driver *a, unsigned int options,
700 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
701 int y, const uint8_t *string, int n, size_t width)
703 int x0 = MAX (0, clip[H][0]);
704 int y0 = MAX (0, clip[V][0] + a->y);
706 int y1 = MIN (a->length, clip[V][1] + a->y);
710 if (y < y0 || y >= y1)
713 switch (options & TAB_ALIGNMENT)
719 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
722 x = bb[H][1] - width;
738 mblen = u8_mbtouc (&uc, string, n);
743 w = uc_width (uc, "UTF-8");
758 for (ofs = 0; ofs < n; )
764 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
766 w = uc_width (uc, "UTF-8");
769 if (width + w > x1 - x)
780 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
781 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
789 /* First figure out how many bytes need to be inserted. */
791 for (ofs = 0; ofs < n; ofs += mblen)
796 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
797 w = uc_width (uc, "UTF-8");
800 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
803 /* Then insert them. */
804 out = ascii_reserve (a, y, x, x + width, n_out);
805 for (ofs = 0; ofs < n; ofs += mblen)
810 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
811 w = uc_width (uc, "UTF-8");
815 if (a->emphasis == EMPH_UNDERLINE)
818 out = mempcpy (out, string + ofs, mblen);
821 out = mempcpy (out, string + ofs, mblen);
827 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
828 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
829 int *widthp, int *heightp)
831 const char *text = cell->contents;
832 size_t length = strlen (text);
843 breaks = xmalloc (length + 1);
844 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
846 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
847 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
850 bb_width = bb[H][1] - bb[H][0];
851 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
853 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
854 const char *b = breaks + pos;
855 size_t n = length - pos;
857 size_t last_break_ofs = 0;
858 int last_break_width = 0;
862 for (ofs = 0; ofs < n; )
868 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
869 if (b[ofs] == UC_BREAK_MANDATORY)
871 else if (b[ofs] == UC_BREAK_POSSIBLE)
873 last_break_ofs = ofs;
874 last_break_width = width;
877 w = uc_width (uc, "UTF-8");
880 if (width + w > bb_width)
882 if (isspace (line[ofs]))
884 else if (last_break_ofs != 0)
886 ofs = last_break_ofs;
887 width = last_break_width;
895 if (b[ofs] != UC_BREAK_MANDATORY)
897 while (ofs > 0 && isspace (line[ofs - 1]))
907 text_draw (a, cell->options, bb, clip, y, line, ofs, width);
911 if (ofs < n && isspace (line[ofs]))
915 *heightp = y - bb[V][0];
921 ascii_test_write (struct output_driver *driver,
922 const char *s, int x, int y, unsigned int options)
924 struct ascii_driver *a = ascii_driver_cast (driver);
925 struct table_cell cell;
926 int bb[TABLE_N_AXES][2];
929 if (a->file == NULL && !ascii_open_page (a))
933 memset (&cell, 0, sizeof cell);
935 cell.options = options | TAB_LEFT;
937 bb[TABLE_HORZ][0] = x;
938 bb[TABLE_HORZ][1] = a->width;
939 bb[TABLE_VERT][0] = y;
940 bb[TABLE_VERT][1] = a->length;
942 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
948 ascii_test_set_length (struct output_driver *driver, int y, int length)
950 struct ascii_driver *a = ascii_driver_cast (driver);
952 if (a->file == NULL && !ascii_open_page (a))
955 if (y < 0 || y >= a->length)
957 u8_line_set_length (&a->lines[y], length);
960 /* ascii_close_page () and support routines. */
962 #if HAVE_DECL_SIGWINCH
963 static struct ascii_driver *the_driver;
966 winch_handler (int signum UNUSED)
968 update_page_size (the_driver, false);
973 ascii_open_page (struct ascii_driver *a)
982 a->file = fn_open (a->file_name, a->append ? "a" : "w");
985 if ( isatty (fileno (a->file)))
987 #if HAVE_DECL_SIGWINCH
988 struct sigaction action;
989 sigemptyset (&action.sa_mask);
991 action.sa_handler = winch_handler;
993 sigaction (SIGWINCH, &action, NULL);
995 a->auto_width = true;
996 a->auto_length = true;
1001 error (0, errno, _("ascii: opening output file `%s'"),
1010 reallocate_lines (a);
1012 for (i = 0; i < a->length; i++)
1013 u8_line_clear (&a->lines[i]);
1019 output_title_line (FILE *out, int width, const char *left, const char *right)
1021 struct string s = DS_EMPTY_INITIALIZER;
1022 ds_put_byte_multiple (&s, ' ', width);
1025 size_t length = MIN (strlen (left), width);
1026 memcpy (ds_end (&s) - width, left, length);
1030 size_t length = MIN (strlen (right), width);
1031 memcpy (ds_end (&s) - length, right, length);
1033 ds_put_byte (&s, '\n');
1034 fputs (ds_cstr (&s), out);
1039 ascii_close_page (struct ascii_driver *a)
1045 if (a->file == NULL)
1048 if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1049 && !a->paginate && a->page_number > 1)
1050 putc ('\n', a->file);
1052 for (i = 0; i < a->top_margin; i++)
1053 putc ('\n', a->file);
1058 r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1059 r2 = xasprintf ("%s - %s" , version, host_system);
1061 output_title_line (a->file, a->width, a->title, r1);
1062 output_title_line (a->file, a->width, a->subtitle, r2);
1063 putc ('\n', a->file);
1070 for (y = 0; y < a->allocated_lines; y++)
1072 struct u8_line *line = &a->lines[y];
1074 if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1080 putc ('\n', a->file);
1084 while (ds_chomp_byte (&line->s, ' '))
1086 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1087 putc ('\n', a->file);
1090 if (!a->squeeze_blank_lines)
1091 for (y = a->allocated_lines; y < a->length; y++)
1092 putc ('\n', a->file);
1094 for (i = 0; i < a->bottom_margin; i++)
1095 putc ('\n', a->file);
1097 putc ('\f', a->file);