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. */
154 /* Colours for charts */
159 int width; /* Page width. */
160 int length; /* Page length minus margins and header. */
161 bool auto_width; /* Use viewwidth as page width? */
162 bool auto_length; /* Use viewlength as page width? */
164 int top_margin; /* Top margin in lines. */
165 int bottom_margin; /* Bottom margin in lines. */
167 const ucs4_t *box; /* Line & box drawing characters. */
169 /* Internal state. */
173 char *file_name; /* Output file name. */
174 FILE *file; /* Output file. */
175 bool error; /* Output error? */
176 int page_number; /* Current page number. */
177 struct u8_line *lines; /* Page content. */
178 int allocated_lines; /* Number of lines allocated. */
179 int chart_cnt; /* Number of charts so far. */
183 static const struct output_driver_class ascii_driver_class;
185 static void ascii_submit (struct output_driver *, const struct output_item *);
187 static int vertical_margins (const struct ascii_driver *);
189 static bool update_page_size (struct ascii_driver *, bool issue_error);
190 static int parse_page_size (struct driver_option *);
192 static void ascii_close_page (struct ascii_driver *);
193 static bool ascii_open_page (struct ascii_driver *);
195 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
196 enum render_line_style styles[TABLE_N_AXES][2]);
197 static void ascii_measure_cell_width (void *, const struct table_cell *,
199 static int ascii_measure_cell_height (void *, const struct table_cell *,
201 static void ascii_draw_cell (void *, const struct table_cell *,
202 int bb[TABLE_N_AXES][2],
203 int clip[TABLE_N_AXES][2]);
206 reallocate_lines (struct ascii_driver *a)
208 if (a->length > a->allocated_lines)
211 a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
212 for (i = a->allocated_lines; i < a->length; i++)
213 u8_line_init (&a->lines[i]);
214 a->allocated_lines = a->length;
219 static struct ascii_driver *
220 ascii_driver_cast (struct output_driver *driver)
222 assert (driver->class == &ascii_driver_class);
223 return UP_CAST (driver, struct ascii_driver, driver);
226 static struct driver_option *
227 opt (struct output_driver *d, struct string_map *options, const char *key,
228 const char *default_value)
230 return driver_option_get (d, options, key, default_value);
233 static struct output_driver *
234 ascii_create (const char *file_name, enum settings_output_devices device_type,
235 struct string_map *o)
237 enum { BOX_ASCII, BOX_UNICODE } box;
238 struct output_driver *d;
239 struct ascii_driver *a;
242 a = xzalloc (sizeof *a);
244 output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
245 a->append = parse_boolean (opt (d, o, "append", "false"));
246 a->headers = parse_boolean (opt (d, o, "headers", "false"));
247 a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
248 a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
249 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
251 "underline", EMPH_UNDERLINE,
255 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
257 a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
258 a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
260 a->width = parse_page_size (opt (d, o, "width", "79"));
261 paper_length = parse_page_size (opt (d, o, "length", "66"));
262 a->auto_width = a->width < 0;
263 a->auto_length = paper_length < 0;
264 a->length = paper_length - vertical_margins (a);
266 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
267 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
269 box = parse_enum (opt (d, o, "box", "ascii"),
271 "unicode", BOX_UNICODE,
273 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
275 a->command_name = NULL;
276 a->title = xstrdup ("");
277 a->subtitle = xstrdup ("");
278 a->file_name = xstrdup (file_name);
283 a->allocated_lines = 0;
286 if (!update_page_size (a, true))
292 output_driver_destroy (d);
297 parse_page_size (struct driver_option *option)
299 int dim = atol (option->default_value);
301 if (option->value != NULL)
303 if (!strcmp (option->value, "auto"))
311 value = strtol (option->value, &tail, 0);
312 if (dim >= 1 && errno != ERANGE && *tail == '\0')
315 msg (MW, _("%s: %s must be positive integer or `auto'"),
316 option->driver_name, option->name);
320 driver_option_destroy (option);
326 vertical_margins (const struct ascii_driver *a)
328 return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
331 /* Re-calculates the page width and length based on settings,
332 margins, and, if "auto" is set, the size of the user's
333 terminal window or GUI output window. */
335 update_page_size (struct ascii_driver *a, bool issue_error)
337 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
340 a->width = settings_get_viewwidth ();
342 a->length = settings_get_viewlength () - vertical_margins (a);
344 if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
348 _("ascii: page excluding margins and headers "
349 "must be at least %d characters wide by %d lines long, but "
350 "as configured is only %d characters by %d lines"),
351 MIN_WIDTH, MIN_LENGTH,
352 a->width, a->length);
353 if (a->width < MIN_WIDTH)
354 a->width = MIN_WIDTH;
355 if (a->length < MIN_LENGTH)
356 a->length = MIN_LENGTH;
360 reallocate_lines (a);
366 ascii_destroy (struct output_driver *driver)
368 struct ascii_driver *a = ascii_driver_cast (driver);
372 ascii_close_page (a);
375 fn_close (a->file_name, a->file);
376 free (a->command_name);
380 free (a->chart_file_name);
381 for (i = 0; i < a->allocated_lines; i++)
382 u8_line_destroy (&a->lines[i]);
388 ascii_flush (struct output_driver *driver)
390 struct ascii_driver *a = ascii_driver_cast (driver);
393 ascii_close_page (a);
395 if (fn_close (a->file_name, a->file) != 0)
396 msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
402 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
404 cell->contents = caption;
405 cell->options = TAB_LEFT;
406 cell->destructor = NULL;
410 ascii_output_table_item (struct ascii_driver *a,
411 const struct table_item *table_item)
413 const char *caption = table_item_get_caption (table_item);
414 struct render_params params;
415 struct render_page *page;
416 struct render_break x_break;
420 update_page_size (a, false);
424 /* XXX doesn't do well with very large captions */
425 struct table_cell cell;
426 ascii_init_caption_cell (caption, &cell);
427 caption_height = ascii_measure_cell_height (a, &cell, a->width);
432 params.draw_line = ascii_draw_line;
433 params.measure_cell_width = ascii_measure_cell_width;
434 params.measure_cell_height = ascii_measure_cell_height;
435 params.draw_cell = ascii_draw_cell,
437 params.size[H] = a->width;
438 params.size[V] = a->length - caption_height;
439 params.font_size[H] = 1;
440 params.font_size[V] = 1;
441 for (i = 0; i < RENDER_N_LINES; i++)
443 int width = i == RENDER_LINE_NONE ? 0 : 1;
444 params.line_widths[H][i] = width;
445 params.line_widths[V][i] = width;
448 if (a->file == NULL && !ascii_open_page (a))
451 page = render_page_create (¶ms, table_item_get_table (table_item));
452 for (render_break_init (&x_break, page, H);
453 render_break_has_next (&x_break); )
455 struct render_page *x_slice;
456 struct render_break y_break;
458 x_slice = render_break_next (&x_break, a->width);
459 for (render_break_init (&y_break, x_slice, V);
460 render_break_has_next (&y_break); )
462 struct render_page *y_slice;
468 space = a->length - a->y - caption_height;
469 if (render_break_next_size (&y_break) > space)
472 ascii_close_page (a);
473 if (!ascii_open_page (a))
478 y_slice = render_break_next (&y_break, space);
481 struct table_cell cell;
482 int bb[TABLE_N_AXES][2];
484 ascii_init_caption_cell (caption, &cell);
488 bb[V][1] = caption_height;
489 ascii_draw_cell (a, &cell, bb, bb);
490 a->y += caption_height;
493 render_page_draw (y_slice);
494 a->y += render_page_get_size (y_slice, V);
495 render_page_unref (y_slice);
497 render_break_destroy (&y_break);
499 render_break_destroy (&x_break);
503 ascii_output_text (struct ascii_driver *a, const char *text)
505 struct table_item *table_item;
507 table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
508 ascii_output_table_item (a, table_item);
509 table_item_unref (table_item);
513 ascii_submit (struct output_driver *driver,
514 const struct output_item *output_item)
516 struct ascii_driver *a = ascii_driver_cast (driver);
518 output_driver_track_current_command (output_item, &a->command_name);
523 if (is_table_item (output_item))
524 ascii_output_table_item (a, to_table_item (output_item));
526 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
528 struct chart_item *chart_item = to_chart_item (output_item);
531 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
535 if (file_name != NULL)
537 struct text_item *text_item;
539 text_item = text_item_create_format (
540 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
542 ascii_submit (driver, &text_item->output_item);
543 text_item_unref (text_item);
547 #endif /* HAVE_CAIRO */
548 else if (is_text_item (output_item))
550 const struct text_item *text_item = to_text_item (output_item);
551 enum text_item_type type = text_item_get_type (text_item);
552 const char *text = text_item_get_text (text_item);
556 case TEXT_ITEM_TITLE:
558 a->title = xstrdup (text);
561 case TEXT_ITEM_SUBTITLE:
563 a->subtitle = xstrdup (text);
566 case TEXT_ITEM_COMMAND_OPEN:
567 case TEXT_ITEM_COMMAND_CLOSE:
570 case TEXT_ITEM_BLANK_LINE:
575 case TEXT_ITEM_EJECT_PAGE:
577 ascii_close_page (a);
581 ascii_output_text (a, text);
585 else if (is_message_item (output_item))
587 const struct message_item *message_item = to_message_item (output_item);
588 const struct msg *msg = message_item_get_msg (message_item);
589 char *s = msg_to_string (msg, a->command_name);
590 ascii_output_text (a, s);
595 const struct output_driver_factory txt_driver_factory =
596 { "txt", "-", ascii_create };
597 const struct output_driver_factory list_driver_factory =
598 { "list", "-", ascii_create };
600 static const struct output_driver_class ascii_driver_class =
608 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
610 static void ascii_layout_cell (struct ascii_driver *,
611 const struct table_cell *,
612 int bb[TABLE_N_AXES][2],
613 int clip[TABLE_N_AXES][2],
614 int *width, int *height);
617 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
618 enum render_line_style styles[TABLE_N_AXES][2])
620 struct ascii_driver *a = a_;
627 /* Clip to the page. */
628 if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
631 x1 = MIN (bb[H][1], a->width);
632 y1 = MIN (bb[V][1] + a->y, a->length);
635 uc = a->box[make_box_index (styles[V][0], styles[V][1],
636 styles[H][0], styles[H][1])];
637 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
638 for (y = bb[V][0] + a->y; y < y1; y++)
640 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
641 for (x = x0; x < x1; x++)
643 memcpy (p, mbchar, mblen);
650 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
651 int *min_width, int *max_width)
653 struct ascii_driver *a = a_;
654 int bb[TABLE_N_AXES][2];
655 int clip[TABLE_N_AXES][2];
662 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
663 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
665 if (strchr (cell->contents, ' '))
668 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
671 *min_width = *max_width;
675 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
677 struct ascii_driver *a = a_;
678 int bb[TABLE_N_AXES][2];
679 int clip[TABLE_N_AXES][2];
686 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
687 ascii_layout_cell (a, cell, bb, clip, &w, &h);
692 ascii_draw_cell (void *a_, const struct table_cell *cell,
693 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
695 struct ascii_driver *a = a_;
698 ascii_layout_cell (a, cell, bb, clip, &w, &h);
702 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
704 assert (y < a->allocated_lines);
705 return u8_line_reserve (&a->lines[y], x0, x1, n);
709 text_draw (struct ascii_driver *a, unsigned int options,
710 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
711 int y, const uint8_t *string, int n, size_t width)
713 int x0 = MAX (0, clip[H][0]);
714 int y0 = MAX (0, clip[V][0] + a->y);
716 int y1 = MIN (a->length, clip[V][1] + a->y);
720 if (y < y0 || y >= y1)
723 switch (options & TAB_ALIGNMENT)
729 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
732 x = bb[H][1] - width;
748 mblen = u8_mbtouc (&uc, string, n);
753 w = uc_width (uc, "UTF-8");
768 for (ofs = 0; ofs < n; )
774 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
776 w = uc_width (uc, "UTF-8");
779 if (width + w > x1 - x)
790 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
791 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
799 /* First figure out how many bytes need to be inserted. */
801 for (ofs = 0; ofs < n; ofs += mblen)
806 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
807 w = uc_width (uc, "UTF-8");
810 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
813 /* Then insert them. */
814 out = ascii_reserve (a, y, x, x + width, n_out);
815 for (ofs = 0; ofs < n; ofs += mblen)
820 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
821 w = uc_width (uc, "UTF-8");
825 if (a->emphasis == EMPH_UNDERLINE)
828 out = mempcpy (out, string + ofs, mblen);
831 out = mempcpy (out, string + ofs, mblen);
837 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
838 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
839 int *widthp, int *heightp)
841 const char *text = cell->contents;
842 size_t length = strlen (text);
853 breaks = xmalloc (length + 1);
854 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
856 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
857 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
860 bb_width = bb[H][1] - bb[H][0];
861 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
863 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
864 const char *b = breaks + pos;
865 size_t n = length - pos;
867 size_t last_break_ofs = 0;
868 int last_break_width = 0;
873 for (ofs = 0; ofs < n; )
879 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
880 if (b[ofs] == UC_BREAK_MANDATORY)
882 else if (b[ofs] == UC_BREAK_POSSIBLE)
884 last_break_ofs = ofs;
885 last_break_width = width;
888 w = uc_width (uc, "UTF-8");
891 if (width + w > bb_width)
893 if (isspace (line[ofs]))
895 else if (last_break_ofs != 0)
897 ofs = last_break_ofs;
898 width = last_break_width;
907 /* Trim any trailing spaces off the end of the text to be drawn. */
908 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
909 if (!isspace (line[graph_ofs - 1]))
911 width -= ofs - graph_ofs;
914 text_draw (a, cell->options, bb, clip, y, line, graph_ofs, width);
916 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
917 past any spaces past the end of the line (but not past a new-line). */
918 if (b[ofs] == UC_BREAK_MANDATORY)
921 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
928 *heightp = y - bb[V][0];
934 ascii_test_write (struct output_driver *driver,
935 const char *s, int x, int y, unsigned int options)
937 struct ascii_driver *a = ascii_driver_cast (driver);
938 struct table_cell cell;
939 int bb[TABLE_N_AXES][2];
942 if (a->file == NULL && !ascii_open_page (a))
946 memset (&cell, 0, sizeof cell);
948 cell.options = options | TAB_LEFT;
950 bb[TABLE_HORZ][0] = x;
951 bb[TABLE_HORZ][1] = a->width;
952 bb[TABLE_VERT][0] = y;
953 bb[TABLE_VERT][1] = a->length;
955 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
961 ascii_test_set_length (struct output_driver *driver, int y, int length)
963 struct ascii_driver *a = ascii_driver_cast (driver);
965 if (a->file == NULL && !ascii_open_page (a))
968 if (y < 0 || y >= a->length)
970 u8_line_set_length (&a->lines[y], length);
973 /* ascii_close_page () and support routines. */
975 #if HAVE_DECL_SIGWINCH
976 static struct ascii_driver *the_driver;
979 winch_handler (int signum UNUSED)
981 update_page_size (the_driver, false);
986 ascii_open_page (struct ascii_driver *a)
995 a->file = fn_open (a->file_name, a->append ? "a" : "w");
998 if ( isatty (fileno (a->file)))
1000 #if HAVE_DECL_SIGWINCH
1001 struct sigaction action;
1002 sigemptyset (&action.sa_mask);
1003 action.sa_flags = 0;
1004 action.sa_handler = winch_handler;
1006 sigaction (SIGWINCH, &action, NULL);
1008 a->auto_width = true;
1009 a->auto_length = true;
1014 msg_error (errno, _("ascii: opening output file `%s'"),
1023 reallocate_lines (a);
1025 for (i = 0; i < a->length; i++)
1026 u8_line_clear (&a->lines[i]);
1032 output_title_line (FILE *out, int width, const char *left, const char *right)
1034 struct string s = DS_EMPTY_INITIALIZER;
1035 ds_put_byte_multiple (&s, ' ', width);
1038 size_t length = MIN (strlen (left), width);
1039 memcpy (ds_end (&s) - width, left, length);
1043 size_t length = MIN (strlen (right), width);
1044 memcpy (ds_end (&s) - length, right, length);
1046 ds_put_byte (&s, '\n');
1047 fputs (ds_cstr (&s), out);
1052 ascii_close_page (struct ascii_driver *a)
1058 if (a->file == NULL)
1061 if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1062 && !a->paginate && a->page_number > 1)
1063 putc ('\n', a->file);
1065 for (i = 0; i < a->top_margin; i++)
1066 putc ('\n', a->file);
1071 r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1072 r2 = xasprintf ("%s - %s" , version, host_system);
1074 output_title_line (a->file, a->width, a->title, r1);
1075 output_title_line (a->file, a->width, a->subtitle, r2);
1076 putc ('\n', a->file);
1083 for (y = 0; y < a->allocated_lines; y++)
1085 struct u8_line *line = &a->lines[y];
1087 if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1093 putc ('\n', a->file);
1097 while (ds_chomp_byte (&line->s, ' '))
1099 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1100 putc ('\n', a->file);
1103 if (!a->squeeze_blank_lines)
1104 for (y = a->allocated_lines; y < a->length; y++)
1105 putc ('\n', a->file);
1107 for (i = 0; i < a->bottom_margin; i++)
1108 putc ('\n', a->file);
1110 putc ('\f', a->file);