1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013 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"
51 #include "gl/minmax.h"
52 #include "gl/xalloc.h"
55 #define _(msgid) gettext (msgid)
57 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
61 #define N_BOX (RENDER_N_LINES * RENDER_N_LINES \
62 * RENDER_N_LINES * RENDER_N_LINES)
64 static const ucs4_t ascii_box_chars[N_BOX] =
95 static const ucs4_t unicode_box_chars[N_BOX] =
97 0x0020, 0x2575, 0x2551,
98 0x2574, 0x256f, 0x255c,
99 0x2550, 0x255b, 0x255d,
100 0x2577, 0x2502, 0x2551,
101 0x256e, 0x2524, 0x2562,
102 0x2555, 0x2561, 0x2563,
103 0x2551, 0x2551, 0x2551,
104 0x2556, 0x2562, 0x2562,
105 0x2557, 0x2563, 0x2563,
106 0x2576, 0x2570, 0x2559,
107 0x2500, 0x2534, 0x2568,
108 0x2550, 0x2567, 0x2569,
109 0x256d, 0x251c, 0x255f,
110 0x252c, 0x253c, 0x256a,
111 0x2564, 0x256a, 0x256c,
112 0x2553, 0x255f, 0x255f,
113 0x2565, 0x256b, 0x256b,
114 0x2566, 0x256c, 0x256c,
115 0x2550, 0x2558, 0x255a,
116 0x2550, 0x2567, 0x2569,
117 0x2550, 0x2567, 0x2569,
118 0x2552, 0x255e, 0x2560,
119 0x2564, 0x256a, 0x256c,
120 0x2564, 0x256a, 0x256c,
121 0x2554, 0x2560, 0x2560,
122 0x2560, 0x256c, 0x256c,
123 0x2566, 0x256c, 0x256c,
127 make_box_index (int left, int right, int top, int bottom)
129 return ((right * RENDER_N_LINES + bottom) * RENDER_N_LINES + left) * RENDER_N_LINES + 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 /* Colours for charts */
158 int width; /* Page width. */
159 int length; /* Page length minus margins and header. */
160 bool auto_width; /* Use viewwidth as page width? */
161 bool auto_length; /* Use viewlength as page width? */
163 int top_margin; /* Top margin in lines. */
164 int bottom_margin; /* Bottom margin in lines. */
166 const ucs4_t *box; /* Line & box drawing characters. */
168 /* Internal state. */
172 char *file_name; /* Output file name. */
173 FILE *file; /* Output file. */
174 bool error; /* Output error? */
175 int page_number; /* Current page number. */
176 struct u8_line *lines; /* Page content. */
177 int allocated_lines; /* Number of lines allocated. */
178 int chart_cnt; /* Number of charts so far. */
182 static const struct output_driver_class ascii_driver_class;
184 static void ascii_submit (struct output_driver *, const struct output_item *);
186 static int vertical_margins (const struct ascii_driver *);
188 static bool update_page_size (struct ascii_driver *, bool issue_error);
189 static int parse_page_size (struct driver_option *);
191 static void ascii_close_page (struct ascii_driver *);
192 static bool ascii_open_page (struct ascii_driver *);
194 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
195 enum render_line_style styles[TABLE_N_AXES][2]);
196 static void ascii_measure_cell_width (void *, const struct table_cell *,
198 static int ascii_measure_cell_height (void *, const struct table_cell *,
200 static void ascii_draw_cell (void *, const struct table_cell *,
201 int bb[TABLE_N_AXES][2],
202 int clip[TABLE_N_AXES][2]);
205 reallocate_lines (struct ascii_driver *a)
207 if (a->length > a->allocated_lines)
210 a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
211 for (i = a->allocated_lines; i < a->length; i++)
212 u8_line_init (&a->lines[i]);
213 a->allocated_lines = a->length;
218 static struct ascii_driver *
219 ascii_driver_cast (struct output_driver *driver)
221 assert (driver->class == &ascii_driver_class);
222 return UP_CAST (driver, struct ascii_driver, driver);
225 static struct driver_option *
226 opt (struct output_driver *d, struct string_map *options, const char *key,
227 const char *default_value)
229 return driver_option_get (d, options, key, default_value);
232 static struct output_driver *
233 ascii_create (const char *file_name, enum settings_output_devices device_type,
234 struct string_map *o)
236 enum { BOX_ASCII, BOX_UNICODE } box;
237 struct output_driver *d;
238 struct ascii_driver *a;
241 a = xzalloc (sizeof *a);
243 output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
244 a->append = parse_boolean (opt (d, o, "append", "false"));
245 a->headers = parse_boolean (opt (d, o, "headers", "false"));
246 a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
247 a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
248 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
250 "underline", EMPH_UNDERLINE,
254 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
256 a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
257 a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
259 a->width = parse_page_size (opt (d, o, "width", "79"));
260 paper_length = parse_page_size (opt (d, o, "length", "66"));
261 a->auto_width = a->width < 0;
262 a->auto_length = paper_length < 0;
263 a->length = paper_length - vertical_margins (a);
265 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
266 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
268 box = parse_enum (opt (d, o, "box", "ascii"),
270 "unicode", BOX_UNICODE,
272 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
274 a->command_name = NULL;
275 a->title = xstrdup ("");
276 a->subtitle = xstrdup ("");
277 a->file_name = xstrdup (file_name);
282 a->allocated_lines = 0;
285 if (!update_page_size (a, true))
291 output_driver_destroy (d);
296 parse_page_size (struct driver_option *option)
298 int dim = atol (option->default_value);
300 if (option->value != NULL)
302 if (!strcmp (option->value, "auto"))
310 value = strtol (option->value, &tail, 0);
311 if (dim >= 1 && errno != ERANGE && *tail == '\0')
314 msg (MW, _("%s: %s must be positive integer or `auto'"),
315 option->driver_name, option->name);
319 driver_option_destroy (option);
325 vertical_margins (const struct ascii_driver *a)
327 return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
330 /* Re-calculates the page width and length based on settings,
331 margins, and, if "auto" is set, the size of the user's
332 terminal window or GUI output window. */
334 update_page_size (struct ascii_driver *a, bool issue_error)
336 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
339 a->width = settings_get_viewwidth ();
341 a->length = settings_get_viewlength () - vertical_margins (a);
343 if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
347 _("ascii: page excluding margins and headers "
348 "must be at least %d characters wide by %d lines long, but "
349 "as configured is only %d characters by %d lines"),
350 MIN_WIDTH, MIN_LENGTH,
351 a->width, a->length);
352 if (a->width < MIN_WIDTH)
353 a->width = MIN_WIDTH;
354 if (a->length < MIN_LENGTH)
355 a->length = MIN_LENGTH;
359 reallocate_lines (a);
365 ascii_destroy (struct output_driver *driver)
367 struct ascii_driver *a = ascii_driver_cast (driver);
371 ascii_close_page (a);
374 fn_close (a->file_name, a->file);
375 free (a->command_name);
379 free (a->chart_file_name);
380 for (i = 0; i < a->allocated_lines; i++)
381 u8_line_destroy (&a->lines[i]);
387 ascii_flush (struct output_driver *driver)
389 struct ascii_driver *a = ascii_driver_cast (driver);
392 ascii_close_page (a);
394 if (fn_close (a->file_name, a->file) != 0)
395 msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
401 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
403 cell->contents = caption;
404 cell->options = TAB_LEFT;
405 cell->destructor = NULL;
409 ascii_output_table_item (struct ascii_driver *a,
410 const struct table_item *table_item)
412 const char *caption = table_item_get_caption (table_item);
413 struct render_params params;
414 struct render_page *page;
415 struct render_break x_break;
419 update_page_size (a, false);
423 /* XXX doesn't do well with very large captions */
424 struct table_cell cell;
425 ascii_init_caption_cell (caption, &cell);
426 caption_height = ascii_measure_cell_height (a, &cell, a->width);
431 params.draw_line = ascii_draw_line;
432 params.measure_cell_width = ascii_measure_cell_width;
433 params.measure_cell_height = ascii_measure_cell_height;
434 params.draw_cell = ascii_draw_cell,
436 params.size[H] = a->width;
437 params.size[V] = a->length - caption_height;
438 params.font_size[H] = 1;
439 params.font_size[V] = 1;
440 for (i = 0; i < RENDER_N_LINES; i++)
442 int width = i == RENDER_LINE_NONE ? 0 : 1;
443 params.line_widths[H][i] = width;
444 params.line_widths[V][i] = width;
447 if (a->file == NULL && !ascii_open_page (a))
450 page = render_page_create (¶ms, table_item_get_table (table_item));
451 for (render_break_init (&x_break, page, H);
452 render_break_has_next (&x_break); )
454 struct render_page *x_slice;
455 struct render_break y_break;
457 x_slice = render_break_next (&x_break, a->width);
458 for (render_break_init (&y_break, x_slice, V);
459 render_break_has_next (&y_break); )
461 struct render_page *y_slice;
467 space = a->length - a->y - caption_height;
468 if (render_break_next_size (&y_break) > space)
471 ascii_close_page (a);
472 if (!ascii_open_page (a))
477 y_slice = render_break_next (&y_break, space);
480 struct table_cell cell;
481 int bb[TABLE_N_AXES][2];
483 ascii_init_caption_cell (caption, &cell);
487 bb[V][1] = caption_height;
488 ascii_draw_cell (a, &cell, bb, bb);
489 a->y += caption_height;
492 render_page_draw (y_slice);
493 a->y += render_page_get_size (y_slice, V);
494 render_page_unref (y_slice);
496 render_break_destroy (&y_break);
498 render_break_destroy (&x_break);
502 ascii_output_text (struct ascii_driver *a, const char *text)
504 struct table_item *table_item;
506 table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
507 ascii_output_table_item (a, table_item);
508 table_item_unref (table_item);
512 ascii_submit (struct output_driver *driver,
513 const struct output_item *output_item)
515 struct ascii_driver *a = ascii_driver_cast (driver);
517 output_driver_track_current_command (output_item, &a->command_name);
522 if (is_table_item (output_item))
523 ascii_output_table_item (a, to_table_item (output_item));
525 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
527 struct chart_item *chart_item = to_chart_item (output_item);
530 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
534 if (file_name != NULL)
536 struct text_item *text_item;
538 text_item = text_item_create_format (
539 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
541 ascii_submit (driver, &text_item->output_item);
542 text_item_unref (text_item);
546 #endif /* HAVE_CAIRO */
547 else if (is_text_item (output_item))
549 const struct text_item *text_item = to_text_item (output_item);
550 enum text_item_type type = text_item_get_type (text_item);
551 const char *text = text_item_get_text (text_item);
555 case TEXT_ITEM_TITLE:
557 a->title = xstrdup (text);
560 case TEXT_ITEM_SUBTITLE:
562 a->subtitle = xstrdup (text);
565 case TEXT_ITEM_COMMAND_OPEN:
566 case TEXT_ITEM_COMMAND_CLOSE:
569 case TEXT_ITEM_BLANK_LINE:
574 case TEXT_ITEM_EJECT_PAGE:
576 ascii_close_page (a);
580 ascii_output_text (a, text);
584 else if (is_message_item (output_item))
586 const struct message_item *message_item = to_message_item (output_item);
587 const struct msg *msg = message_item_get_msg (message_item);
588 char *s = msg_to_string (msg, a->command_name);
589 ascii_output_text (a, s);
594 const struct output_driver_factory txt_driver_factory =
595 { "txt", "-", ascii_create };
596 const struct output_driver_factory list_driver_factory =
597 { "list", "-", ascii_create };
599 static const struct output_driver_class ascii_driver_class =
607 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
609 static void ascii_layout_cell (struct ascii_driver *,
610 const struct table_cell *,
611 int bb[TABLE_N_AXES][2],
612 int clip[TABLE_N_AXES][2],
613 int *width, int *height);
616 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
617 enum render_line_style styles[TABLE_N_AXES][2])
619 struct ascii_driver *a = a_;
626 /* Clip to the page. */
627 if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
630 x1 = MIN (bb[H][1], a->width);
631 y1 = MIN (bb[V][1] + a->y, a->length);
634 uc = a->box[make_box_index (styles[V][0], styles[V][1],
635 styles[H][0], styles[H][1])];
636 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
637 for (y = bb[V][0] + a->y; y < y1; y++)
639 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
640 for (x = x0; x < x1; x++)
642 memcpy (p, mbchar, mblen);
649 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
650 int *min_width, int *max_width)
652 struct ascii_driver *a = a_;
653 int bb[TABLE_N_AXES][2];
654 int clip[TABLE_N_AXES][2];
661 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
662 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
664 if (strchr (cell->contents, ' '))
667 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
670 *min_width = *max_width;
674 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
676 struct ascii_driver *a = a_;
677 int bb[TABLE_N_AXES][2];
678 int clip[TABLE_N_AXES][2];
685 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
686 ascii_layout_cell (a, cell, bb, clip, &w, &h);
691 ascii_draw_cell (void *a_, const struct table_cell *cell,
692 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
694 struct ascii_driver *a = a_;
697 ascii_layout_cell (a, cell, bb, clip, &w, &h);
701 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
703 assert (y < a->allocated_lines);
704 return u8_line_reserve (&a->lines[y], x0, x1, n);
708 text_draw (struct ascii_driver *a, unsigned int options,
709 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
710 int y, const uint8_t *string, int n, size_t width)
712 int x0 = MAX (0, clip[H][0]);
713 int y0 = MAX (0, clip[V][0] + a->y);
715 int y1 = MIN (a->length, clip[V][1] + a->y);
719 if (y < y0 || y >= y1)
722 switch (options & TAB_ALIGNMENT)
728 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
731 x = bb[H][1] - width;
747 mblen = u8_mbtouc (&uc, string, n);
752 w = uc_width (uc, "UTF-8");
767 for (ofs = 0; ofs < n; )
773 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
775 w = uc_width (uc, "UTF-8");
778 if (width + w > x1 - x)
789 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
790 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
798 /* First figure out how many bytes need to be inserted. */
800 for (ofs = 0; ofs < n; ofs += mblen)
805 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
806 w = uc_width (uc, "UTF-8");
809 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
812 /* Then insert them. */
813 out = ascii_reserve (a, y, x, x + width, n_out);
814 for (ofs = 0; ofs < n; ofs += mblen)
819 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
820 w = uc_width (uc, "UTF-8");
824 if (a->emphasis == EMPH_UNDERLINE)
827 out = mempcpy (out, string + ofs, mblen);
830 out = mempcpy (out, string + ofs, mblen);
836 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
837 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
838 int *widthp, int *heightp)
840 const char *text = cell->contents;
841 size_t length = strlen (text);
852 breaks = xmalloc (length + 1);
853 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
855 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
856 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
859 bb_width = bb[H][1] - bb[H][0];
860 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
862 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
863 const char *b = breaks + pos;
864 size_t n = length - pos;
866 size_t last_break_ofs = 0;
867 int last_break_width = 0;
872 for (ofs = 0; ofs < n; )
878 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
879 if (b[ofs] == UC_BREAK_MANDATORY)
881 else if (b[ofs] == UC_BREAK_POSSIBLE)
883 last_break_ofs = ofs;
884 last_break_width = width;
887 w = uc_width (uc, "UTF-8");
890 if (width + w > bb_width)
892 if (isspace (line[ofs]))
894 else if (last_break_ofs != 0)
896 ofs = last_break_ofs;
897 width = last_break_width;
906 /* Trim any trailing spaces off the end of the text to be drawn. */
907 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
908 if (!isspace (line[graph_ofs - 1]))
910 width -= ofs - graph_ofs;
913 text_draw (a, cell->options, bb, clip, y, line, graph_ofs, width);
915 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
916 past any spaces past the end of the line (but not past a new-line). */
917 if (b[ofs] == UC_BREAK_MANDATORY)
920 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
927 *heightp = y - bb[V][0];
933 ascii_test_write (struct output_driver *driver,
934 const char *s, int x, int y, unsigned int options)
936 struct ascii_driver *a = ascii_driver_cast (driver);
937 struct table_cell cell;
938 int bb[TABLE_N_AXES][2];
941 if (a->file == NULL && !ascii_open_page (a))
945 memset (&cell, 0, sizeof cell);
947 cell.options = options | TAB_LEFT;
949 bb[TABLE_HORZ][0] = x;
950 bb[TABLE_HORZ][1] = a->width;
951 bb[TABLE_VERT][0] = y;
952 bb[TABLE_VERT][1] = a->length;
954 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
960 ascii_test_set_length (struct output_driver *driver, int y, int length)
962 struct ascii_driver *a = ascii_driver_cast (driver);
964 if (a->file == NULL && !ascii_open_page (a))
967 if (y < 0 || y >= a->length)
969 u8_line_set_length (&a->lines[y], length);
972 /* ascii_close_page () and support routines. */
974 #if HAVE_DECL_SIGWINCH
975 static struct ascii_driver *the_driver;
978 winch_handler (int signum UNUSED)
980 update_page_size (the_driver, false);
985 ascii_open_page (struct ascii_driver *a)
994 a->file = fn_open (a->file_name, a->append ? "a" : "w");
997 if ( isatty (fileno (a->file)))
999 #if HAVE_DECL_SIGWINCH
1000 struct sigaction action;
1001 sigemptyset (&action.sa_mask);
1002 action.sa_flags = 0;
1003 action.sa_handler = winch_handler;
1005 sigaction (SIGWINCH, &action, NULL);
1007 a->auto_width = true;
1008 a->auto_length = true;
1013 msg_error (errno, _("ascii: opening output file `%s'"),
1022 reallocate_lines (a);
1024 for (i = 0; i < a->length; i++)
1025 u8_line_clear (&a->lines[i]);
1031 output_title_line (FILE *out, int width, const char *left, const char *right)
1033 struct string s = DS_EMPTY_INITIALIZER;
1034 ds_put_byte_multiple (&s, ' ', width);
1037 size_t length = MIN (strlen (left), width);
1038 memcpy (ds_end (&s) - width, left, length);
1042 size_t length = MIN (strlen (right), width);
1043 memcpy (ds_end (&s) - length, right, length);
1045 ds_put_byte (&s, '\n');
1046 fputs (ds_cstr (&s), out);
1051 ascii_close_page (struct ascii_driver *a)
1057 if (a->file == NULL)
1060 if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1061 && !a->paginate && a->page_number > 1)
1062 putc ('\n', a->file);
1064 for (i = 0; i < a->top_margin; i++)
1065 putc ('\n', a->file);
1070 r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1071 r2 = xasprintf ("%s - %s" , version, host_system);
1073 output_title_line (a->file, a->width, a->title, r1);
1074 output_title_line (a->file, a->width, a->subtitle, r2);
1075 putc ('\n', a->file);
1082 for (y = 0; y < a->allocated_lines; y++)
1084 struct u8_line *line = &a->lines[y];
1086 if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1092 putc ('\n', a->file);
1096 while (ds_chomp_byte (&line->s, ' '))
1098 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1099 putc ('\n', a->file);
1102 if (!a->squeeze_blank_lines)
1103 for (y = a->allocated_lines; y < a->length; y++)
1104 putc ('\n', a->file);
1106 for (i = 0; i < a->bottom_margin; i++)
1107 putc ('\n', a->file);
1109 putc ('\f', a->file);