1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2013, 2014 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 int min_break[TABLE_N_AXES]; /* Min cell size to break across pages. */
169 const ucs4_t *box; /* Line & box drawing characters. */
171 /* Internal state. */
175 char *file_name; /* Output file name. */
176 FILE *file; /* Output file. */
177 bool error; /* Output error? */
178 int page_number; /* Current page number. */
179 struct u8_line *lines; /* Page content. */
180 int allocated_lines; /* Number of lines allocated. */
181 int chart_cnt; /* Number of charts so far. */
185 static const struct output_driver_class ascii_driver_class;
187 static void ascii_submit (struct output_driver *, const struct output_item *);
189 static int vertical_margins (const struct ascii_driver *);
191 static bool update_page_size (struct ascii_driver *, bool issue_error);
192 static int parse_page_size (struct driver_option *);
194 static void ascii_close_page (struct ascii_driver *);
195 static bool ascii_open_page (struct ascii_driver *);
197 static void ascii_draw_line (void *, int bb[TABLE_N_AXES][2],
198 enum render_line_style styles[TABLE_N_AXES][2]);
199 static void ascii_measure_cell_width (void *, const struct table_cell *,
201 static int ascii_measure_cell_height (void *, const struct table_cell *,
203 static void ascii_draw_cell (void *, const struct table_cell *,
204 int bb[TABLE_N_AXES][2],
205 int clip[TABLE_N_AXES][2]);
208 reallocate_lines (struct ascii_driver *a)
210 if (a->length > a->allocated_lines)
213 a->lines = xnrealloc (a->lines, a->length, sizeof *a->lines);
214 for (i = a->allocated_lines; i < a->length; i++)
215 u8_line_init (&a->lines[i]);
216 a->allocated_lines = a->length;
221 static struct ascii_driver *
222 ascii_driver_cast (struct output_driver *driver)
224 assert (driver->class == &ascii_driver_class);
225 return UP_CAST (driver, struct ascii_driver, driver);
228 static struct driver_option *
229 opt (struct output_driver *d, struct string_map *options, const char *key,
230 const char *default_value)
232 return driver_option_get (d, options, key, default_value);
235 static struct output_driver *
236 ascii_create (const char *file_name, enum settings_output_devices device_type,
237 struct string_map *o)
239 enum { BOX_ASCII, BOX_UNICODE } box;
240 int min_break[TABLE_N_AXES];
241 struct output_driver *d;
242 struct ascii_driver *a;
245 a = xzalloc (sizeof *a);
247 output_driver_init (&a->driver, &ascii_driver_class, file_name, device_type);
248 a->append = parse_boolean (opt (d, o, "append", "false"));
249 a->headers = parse_boolean (opt (d, o, "headers", "false"));
250 a->paginate = parse_boolean (opt (d, o, "paginate", "false"));
251 a->squeeze_blank_lines = parse_boolean (opt (d, o, "squeeze", "true"));
252 a->emphasis = parse_enum (opt (d, o, "emphasis", "none"),
254 "underline", EMPH_UNDERLINE,
258 a->chart_file_name = parse_chart_file_name (opt (d, o, "charts", file_name));
260 a->top_margin = parse_int (opt (d, o, "top-margin", "0"), 0, INT_MAX);
261 a->bottom_margin = parse_int (opt (d, o, "bottom-margin", "0"), 0, INT_MAX);
263 min_break[H] = parse_int (opt (d, o, "min-hbreak", "-1"), -1, INT_MAX);
264 min_break[V] = parse_int (opt (d, o, "min-vbreak", "-1"), -1, INT_MAX);
266 a->width = parse_page_size (opt (d, o, "width", "79"));
267 paper_length = parse_page_size (opt (d, o, "length", "66"));
268 a->auto_width = a->width < 0;
269 a->auto_length = paper_length < 0;
270 a->length = paper_length - vertical_margins (a);
271 a->min_break[H] = min_break[H] >= 0 ? min_break[H] : a->width / 2;
272 a->min_break[V] = min_break[V] >= 0 ? min_break[V] : a->length / 2;
274 parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &a->bg);
275 parse_color (d, o, "foreground-color", "#000000000000", &a->fg);
277 box = parse_enum (opt (d, o, "box", "ascii"),
279 "unicode", BOX_UNICODE,
281 a->box = box == BOX_ASCII ? ascii_box_chars : unicode_box_chars;
283 a->command_name = NULL;
284 a->title = xstrdup ("");
285 a->subtitle = xstrdup ("");
286 a->file_name = xstrdup (file_name);
291 a->allocated_lines = 0;
294 if (!update_page_size (a, true))
300 output_driver_destroy (d);
305 parse_page_size (struct driver_option *option)
307 int dim = atol (option->default_value);
309 if (option->value != NULL)
311 if (!strcmp (option->value, "auto"))
319 value = strtol (option->value, &tail, 0);
320 if (dim >= 1 && errno != ERANGE && *tail == '\0')
323 msg (MW, _("%s: %s must be positive integer or `auto'"),
324 option->driver_name, option->name);
328 driver_option_destroy (option);
334 vertical_margins (const struct ascii_driver *a)
336 return a->top_margin + a->bottom_margin + (a->headers ? 3 : 0);
339 /* Re-calculates the page width and length based on settings,
340 margins, and, if "auto" is set, the size of the user's
341 terminal window or GUI output window. */
343 update_page_size (struct ascii_driver *a, bool issue_error)
345 enum { MIN_WIDTH = 6, MIN_LENGTH = 6 };
348 a->width = settings_get_viewwidth ();
350 a->length = settings_get_viewlength () - vertical_margins (a);
352 if (a->width < MIN_WIDTH || a->length < MIN_LENGTH)
356 _("ascii: page excluding margins and headers "
357 "must be at least %d characters wide by %d lines long, but "
358 "as configured is only %d characters by %d lines"),
359 MIN_WIDTH, MIN_LENGTH,
360 a->width, a->length);
361 if (a->width < MIN_WIDTH)
362 a->width = MIN_WIDTH;
363 if (a->length < MIN_LENGTH)
364 a->length = MIN_LENGTH;
368 reallocate_lines (a);
374 ascii_destroy (struct output_driver *driver)
376 struct ascii_driver *a = ascii_driver_cast (driver);
380 ascii_close_page (a);
383 fn_close (a->file_name, a->file);
384 free (a->command_name);
388 free (a->chart_file_name);
389 for (i = 0; i < a->allocated_lines; i++)
390 u8_line_destroy (&a->lines[i]);
396 ascii_flush (struct output_driver *driver)
398 struct ascii_driver *a = ascii_driver_cast (driver);
401 ascii_close_page (a);
403 if (fn_close (a->file_name, a->file) != 0)
404 msg_error (errno, _("ascii: closing output file `%s'"), a->file_name);
410 ascii_init_caption_cell (const char *caption, struct table_cell *cell)
412 cell->contents = caption;
413 cell->options = TAB_LEFT;
414 cell->destructor = NULL;
418 ascii_output_table_item (struct ascii_driver *a,
419 const struct table_item *table_item)
421 const char *caption = table_item_get_caption (table_item);
422 struct render_params params;
423 struct render_page *page;
424 struct render_break x_break;
428 update_page_size (a, false);
432 /* XXX doesn't do well with very large captions */
433 struct table_cell cell;
434 ascii_init_caption_cell (caption, &cell);
435 caption_height = ascii_measure_cell_height (a, &cell, a->width);
440 params.draw_line = ascii_draw_line;
441 params.measure_cell_width = ascii_measure_cell_width;
442 params.measure_cell_height = ascii_measure_cell_height;
443 params.draw_cell = ascii_draw_cell,
445 params.size[H] = a->width;
446 params.size[V] = a->length - caption_height;
447 params.font_size[H] = 1;
448 params.font_size[V] = 1;
449 for (i = 0; i < RENDER_N_LINES; i++)
451 int width = i == RENDER_LINE_NONE ? 0 : 1;
452 params.line_widths[H][i] = width;
453 params.line_widths[V][i] = width;
455 for (i = 0; i < TABLE_N_AXES; i++)
456 params.min_break[i] = a->min_break[i];
458 if (a->file == NULL && !ascii_open_page (a))
461 page = render_page_create (¶ms, table_item_get_table (table_item));
462 for (render_break_init (&x_break, page, H);
463 render_break_has_next (&x_break); )
465 struct render_page *x_slice;
466 struct render_break y_break;
468 x_slice = render_break_next (&x_break, a->width);
469 for (render_break_init (&y_break, x_slice, V);
470 render_break_has_next (&y_break); )
472 struct render_page *y_slice;
478 space = a->length - a->y - caption_height;
479 if (render_break_next_size (&y_break) > space)
482 ascii_close_page (a);
483 if (!ascii_open_page (a))
488 y_slice = render_break_next (&y_break, space);
491 struct table_cell cell;
492 int bb[TABLE_N_AXES][2];
494 ascii_init_caption_cell (caption, &cell);
498 bb[V][1] = caption_height;
499 ascii_draw_cell (a, &cell, bb, bb);
500 a->y += caption_height;
503 render_page_draw (y_slice);
504 a->y += render_page_get_size (y_slice, V);
505 render_page_unref (y_slice);
507 render_break_destroy (&y_break);
509 render_break_destroy (&x_break);
513 ascii_output_text (struct ascii_driver *a, const char *text)
515 struct table_item *table_item;
517 table_item = table_item_create (table_from_string (TAB_LEFT, text), NULL);
518 ascii_output_table_item (a, table_item);
519 table_item_unref (table_item);
523 ascii_submit (struct output_driver *driver,
524 const struct output_item *output_item)
526 struct ascii_driver *a = ascii_driver_cast (driver);
528 output_driver_track_current_command (output_item, &a->command_name);
533 if (is_table_item (output_item))
534 ascii_output_table_item (a, to_table_item (output_item));
536 else if (is_chart_item (output_item) && a->chart_file_name != NULL)
538 struct chart_item *chart_item = to_chart_item (output_item);
541 file_name = xr_draw_png_chart (chart_item, a->chart_file_name,
545 if (file_name != NULL)
547 struct text_item *text_item;
549 text_item = text_item_create_format (
550 TEXT_ITEM_PARAGRAPH, _("See %s for a chart."), file_name);
552 ascii_submit (driver, &text_item->output_item);
553 text_item_unref (text_item);
557 #endif /* HAVE_CAIRO */
558 else if (is_text_item (output_item))
560 const struct text_item *text_item = to_text_item (output_item);
561 enum text_item_type type = text_item_get_type (text_item);
562 const char *text = text_item_get_text (text_item);
566 case TEXT_ITEM_TITLE:
568 a->title = xstrdup (text);
571 case TEXT_ITEM_SUBTITLE:
573 a->subtitle = xstrdup (text);
576 case TEXT_ITEM_COMMAND_OPEN:
577 case TEXT_ITEM_COMMAND_CLOSE:
580 case TEXT_ITEM_BLANK_LINE:
585 case TEXT_ITEM_EJECT_PAGE:
587 ascii_close_page (a);
591 ascii_output_text (a, text);
595 else if (is_message_item (output_item))
597 const struct message_item *message_item = to_message_item (output_item);
598 const struct msg *msg = message_item_get_msg (message_item);
599 char *s = msg_to_string (msg, a->command_name);
600 ascii_output_text (a, s);
605 const struct output_driver_factory txt_driver_factory =
606 { "txt", "-", ascii_create };
607 const struct output_driver_factory list_driver_factory =
608 { "list", "-", ascii_create };
610 static const struct output_driver_class ascii_driver_class =
618 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
620 static void ascii_layout_cell (struct ascii_driver *,
621 const struct table_cell *,
622 int bb[TABLE_N_AXES][2],
623 int clip[TABLE_N_AXES][2],
624 int *width, int *height);
627 ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2],
628 enum render_line_style styles[TABLE_N_AXES][2])
630 struct ascii_driver *a = a_;
637 /* Clip to the page. */
638 if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length)
641 x1 = MIN (bb[H][1], a->width);
642 y1 = MIN (bb[V][1] + a->y, a->length);
645 uc = a->box[make_box_index (styles[V][0], styles[V][1],
646 styles[H][0], styles[H][1])];
647 mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6);
648 for (y = bb[V][0] + a->y; y < y1; y++)
650 char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0));
651 for (x = x0; x < x1; x++)
653 memcpy (p, mbchar, mblen);
660 ascii_measure_cell_width (void *a_, const struct table_cell *cell,
661 int *min_width, int *max_width)
663 struct ascii_driver *a = a_;
664 int bb[TABLE_N_AXES][2];
665 int clip[TABLE_N_AXES][2];
672 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
673 ascii_layout_cell (a, cell, bb, clip, max_width, &h);
675 if (strchr (cell->contents, ' '))
678 ascii_layout_cell (a, cell, bb, clip, min_width, &h);
681 *min_width = *max_width;
685 ascii_measure_cell_height (void *a_, const struct table_cell *cell, int width)
687 struct ascii_driver *a = a_;
688 int bb[TABLE_N_AXES][2];
689 int clip[TABLE_N_AXES][2];
696 clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
697 ascii_layout_cell (a, cell, bb, clip, &w, &h);
702 ascii_draw_cell (void *a_, const struct table_cell *cell,
703 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2])
705 struct ascii_driver *a = a_;
708 ascii_layout_cell (a, cell, bb, clip, &w, &h);
712 ascii_reserve (struct ascii_driver *a, int y, int x0, int x1, int n)
714 assert (y < a->allocated_lines);
715 return u8_line_reserve (&a->lines[y], x0, x1, n);
719 text_draw (struct ascii_driver *a, unsigned int options,
720 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
721 int y, const uint8_t *string, int n, size_t width)
723 int x0 = MAX (0, clip[H][0]);
724 int y0 = MAX (0, clip[V][0] + a->y);
726 int y1 = MIN (a->length, clip[V][1] + a->y);
730 if (y < y0 || y >= y1)
733 switch (options & TAB_ALIGNMENT)
739 x = (bb[H][0] + bb[H][1] - width + 1) / 2;
742 x = bb[H][1] - width;
758 mblen = u8_mbtouc (&uc, string, n);
763 w = uc_width (uc, "UTF-8");
778 for (ofs = 0; ofs < n; )
784 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
786 w = uc_width (uc, "UTF-8");
789 if (width + w > x1 - x)
800 if (!(options & TAB_EMPH) || a->emphasis == EMPH_NONE)
801 memcpy (ascii_reserve (a, y, x, x + width, n), string, n);
809 /* First figure out how many bytes need to be inserted. */
811 for (ofs = 0; ofs < n; ofs += mblen)
816 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
817 w = uc_width (uc, "UTF-8");
820 n_out += a->emphasis == EMPH_UNDERLINE ? 2 : 1 + mblen;
823 /* Then insert them. */
824 out = ascii_reserve (a, y, x, x + width, n_out);
825 for (ofs = 0; ofs < n; ofs += mblen)
830 mblen = u8_mbtouc (&uc, string + ofs, n - ofs);
831 w = uc_width (uc, "UTF-8");
835 if (a->emphasis == EMPH_UNDERLINE)
838 out = mempcpy (out, string + ofs, mblen);
841 out = mempcpy (out, string + ofs, mblen);
847 ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell,
848 int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
849 int *widthp, int *heightp)
851 const char *text = cell->contents;
852 size_t length = strlen (text);
863 breaks = xmalloc (length + 1);
864 u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length,
866 breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY
867 ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE);
870 bb_width = bb[H][1] - bb[H][0];
871 for (y = bb[V][0]; y < bb[V][1] && pos < length; y++)
873 const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos);
874 const char *b = breaks + pos;
875 size_t n = length - pos;
877 size_t last_break_ofs = 0;
878 int last_break_width = 0;
883 for (ofs = 0; ofs < n; )
889 mblen = u8_mbtouc (&uc, line + ofs, n - ofs);
890 if (b[ofs] == UC_BREAK_MANDATORY)
892 else if (b[ofs] == UC_BREAK_POSSIBLE)
894 last_break_ofs = ofs;
895 last_break_width = width;
898 w = uc_width (uc, "UTF-8");
901 if (width + w > bb_width)
903 if (isspace (line[ofs]))
905 else if (last_break_ofs != 0)
907 ofs = last_break_ofs;
908 width = last_break_width;
917 /* Trim any trailing spaces off the end of the text to be drawn. */
918 for (graph_ofs = ofs; graph_ofs > 0; graph_ofs--)
919 if (!isspace (line[graph_ofs - 1]))
921 width -= ofs - graph_ofs;
924 text_draw (a, cell->options, bb, clip, y, line, graph_ofs, width);
926 /* If a new-line ended the line, just skip the new-line. Otherwise, skip
927 past any spaces past the end of the line (but not past a new-line). */
928 if (b[ofs] == UC_BREAK_MANDATORY)
931 while (ofs < n && isspace (line[ofs]) && b[ofs] != UC_BREAK_MANDATORY)
938 *heightp = y - bb[V][0];
944 ascii_test_write (struct output_driver *driver,
945 const char *s, int x, int y, unsigned int options)
947 struct ascii_driver *a = ascii_driver_cast (driver);
948 struct table_cell cell;
949 int bb[TABLE_N_AXES][2];
952 if (a->file == NULL && !ascii_open_page (a))
956 memset (&cell, 0, sizeof cell);
958 cell.options = options | TAB_LEFT;
960 bb[TABLE_HORZ][0] = x;
961 bb[TABLE_HORZ][1] = a->width;
962 bb[TABLE_VERT][0] = y;
963 bb[TABLE_VERT][1] = a->length;
965 ascii_layout_cell (a, &cell, bb, bb, &width, &height);
971 ascii_test_set_length (struct output_driver *driver, int y, int length)
973 struct ascii_driver *a = ascii_driver_cast (driver);
975 if (a->file == NULL && !ascii_open_page (a))
978 if (y < 0 || y >= a->length)
980 u8_line_set_length (&a->lines[y], length);
983 /* ascii_close_page () and support routines. */
985 #if HAVE_DECL_SIGWINCH
986 static struct ascii_driver *the_driver;
989 winch_handler (int signum UNUSED)
991 update_page_size (the_driver, false);
996 ascii_open_page (struct ascii_driver *a)
1003 if (a->file == NULL)
1005 a->file = fn_open (a->file_name, a->append ? "a" : "w");
1006 if (a->file != NULL)
1008 if ( isatty (fileno (a->file)))
1010 #if HAVE_DECL_SIGWINCH
1011 struct sigaction action;
1012 sigemptyset (&action.sa_mask);
1013 action.sa_flags = 0;
1014 action.sa_handler = winch_handler;
1016 sigaction (SIGWINCH, &action, NULL);
1018 a->auto_width = true;
1019 a->auto_length = true;
1024 msg_error (errno, _("ascii: opening output file `%s'"),
1033 reallocate_lines (a);
1035 for (i = 0; i < a->length; i++)
1036 u8_line_clear (&a->lines[i]);
1042 output_title_line (FILE *out, int width, const char *left, const char *right)
1044 struct string s = DS_EMPTY_INITIALIZER;
1045 ds_put_byte_multiple (&s, ' ', width);
1048 size_t length = MIN (strlen (left), width);
1049 memcpy (ds_end (&s) - width, left, length);
1053 size_t length = MIN (strlen (right), width);
1054 memcpy (ds_end (&s) - length, right, length);
1056 ds_put_byte (&s, '\n');
1057 fputs (ds_cstr (&s), out);
1062 ascii_close_page (struct ascii_driver *a)
1068 if (a->file == NULL)
1071 if (!a->top_margin && !a->bottom_margin && a->squeeze_blank_lines
1072 && !a->paginate && a->page_number > 1)
1073 putc ('\n', a->file);
1075 for (i = 0; i < a->top_margin; i++)
1076 putc ('\n', a->file);
1081 r1 = xasprintf (_("%s - Page %d"), get_start_date (), a->page_number);
1082 r2 = xasprintf ("%s - %s" , version, host_system);
1084 output_title_line (a->file, a->width, a->title, r1);
1085 output_title_line (a->file, a->width, a->subtitle, r2);
1086 putc ('\n', a->file);
1093 for (y = 0; y < a->allocated_lines; y++)
1095 struct u8_line *line = &a->lines[y];
1097 if (a->squeeze_blank_lines && y > 0 && line->width == 0)
1103 putc ('\n', a->file);
1107 while (ds_chomp_byte (&line->s, ' '))
1109 fwrite (ds_data (&line->s), 1, ds_length (&line->s), a->file);
1110 putc ('\n', a->file);
1113 if (!a->squeeze_blank_lines)
1114 for (y = a->allocated_lines; y < a->length; y++)
1115 putc ('\n', a->file);
1117 for (i = 0; i < a->bottom_margin; i++)
1118 putc ('\n', a->file);
1120 putc ('\f', a->file);