From: Ben Pfaff Date: Sun, 24 Aug 2014 00:12:33 +0000 (-0700) Subject: output: Implement nested tables. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp;a=commitdiff_plain;h=93ec42221da8b677420bf11435e0d24d0503601b output: Implement nested tables. This generalizes the output engine to handle cells with fairly flexible content, instead of just paragraphs of text. --- diff --git a/src/output/ascii.c b/src/output/ascii.c index 201248b448..251e22d443 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -179,7 +179,7 @@ struct ascii_driver struct u8_line *lines; /* Page content. */ int allocated_lines; /* Number of lines allocated. */ int chart_cnt; /* Number of charts so far. */ - int y; + int x, y; }; static const struct output_driver_class ascii_driver_class; @@ -409,8 +409,11 @@ ascii_flush (struct output_driver *driver) static void ascii_init_caption_cell (const char *caption, struct table_cell *cell) { - cell->contents = caption; - cell->options = TAB_LEFT; + cell->inline_contents.options = TAB_LEFT; + cell->inline_contents.text = CONST_CAST (char *, caption); + cell->inline_contents.table = NULL; + cell->contents = &cell->inline_contents; + cell->n_contents = 1; cell->destructor = NULL; } @@ -440,8 +443,9 @@ ascii_output_table_item (struct ascii_driver *a, params.draw_line = ascii_draw_line; params.measure_cell_width = ascii_measure_cell_width; params.measure_cell_height = ascii_measure_cell_height; - params.draw_cell = ascii_draw_cell, - params.aux = a; + params.adjust_break = NULL; + params.draw_cell = ascii_draw_cell; + params.aux = a; params.size[H] = a->width; params.size[V] = a->length - caption_height; params.font_size[H] = 1; @@ -629,23 +633,24 @@ ascii_draw_line (void *a_, int bb[TABLE_N_AXES][2], { struct ascii_driver *a = a_; char mbchar[6]; - int x0, x1, y1; + int x0, y0, x1, y1; ucs4_t uc; int mblen; int x, y; /* Clip to the page. */ - if (bb[H][0] >= a->width || bb[V][0] + a->y >= a->length) - return; - x0 = bb[H][0]; - x1 = MIN (bb[H][1], a->width); + x0 = MAX (bb[H][0] + a->x, 0); + y0 = MAX (bb[V][0] + a->y, 0); + x1 = MIN (bb[H][1] + a->x, a->width); y1 = MIN (bb[V][1] + a->y, a->length); + if (x1 <= 0 || y1 <= 0 || x0 >= a->width || y0 >= a->length) + return; /* Draw. */ uc = a->box[make_box_index (styles[V][0], styles[V][1], styles[H][0], styles[H][1])]; mblen = u8_uctomb (CHAR_CAST (uint8_t *, mbchar), uc, 6); - for (y = bb[V][0] + a->y; y < y1; y++) + for (y = y0; y < y1; y++) { char *p = ascii_reserve (a, y, x0, x1, mblen * (x1 - x0)); for (x = x0; x < x1; x++) @@ -672,7 +677,9 @@ ascii_measure_cell_width (void *a_, const struct table_cell *cell, clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; ascii_layout_cell (a, cell, bb, clip, max_width, &h); - if (strchr (cell->contents, ' ')) + if (cell->n_contents != 1 + || cell->contents[0].table + || strchr (cell->contents[0].text, ' ')) { bb[H][1] = 1; ascii_layout_cell (a, cell, bb, clip, min_width, &h); @@ -720,9 +727,9 @@ text_draw (struct ascii_driver *a, unsigned int options, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], int y, const uint8_t *string, int n, size_t width) { - int x0 = MAX (0, clip[H][0]); + int x0 = MAX (0, clip[H][0] + a->x); int y0 = MAX (0, clip[V][0] + a->y); - int x1 = clip[H][1]; + int x1 = MIN (a->width, clip[H][1] + a->x); int y1 = MIN (a->length, clip[V][1] + a->y); int x; @@ -744,6 +751,7 @@ text_draw (struct ascii_driver *a, unsigned int options, default: NOT_REACHED (); } + x += a->x; if (x >= x1) return; @@ -843,25 +851,24 @@ text_draw (struct ascii_driver *a, unsigned int options, } } -static void -ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, - int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], - int *widthp, int *heightp) +static int +ascii_layout_cell_text (struct ascii_driver *a, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *widthp) { - const char *text = cell->contents; - size_t length = strlen (text); + size_t length = strlen (contents->text); char *breaks; int bb_width; size_t pos; int y; - *widthp = 0; - *heightp = 0; + y = bb[V][0]; if (length == 0) - return; + return y; breaks = xmalloc (length + 1); - u8_possible_linebreaks (CHAR_CAST (const uint8_t *, text), length, + u8_possible_linebreaks (CHAR_CAST (const uint8_t *, contents->text), length, "UTF-8", breaks); breaks[length] = (breaks[length - 1] == UC_BREAK_MANDATORY ? UC_BREAK_PROHIBITED : UC_BREAK_POSSIBLE); @@ -870,7 +877,7 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, bb_width = bb[H][1] - bb[H][0]; for (y = bb[V][0]; y < bb[V][1] && pos < length; y++) { - const uint8_t *line = CHAR_CAST (const uint8_t *, text + pos); + const uint8_t *line = CHAR_CAST (const uint8_t *, contents->text + pos); const char *b = breaks + pos; size_t n = length - pos; @@ -921,7 +928,7 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, width -= ofs - graph_ofs; /* Draw text. */ - text_draw (a, cell->options, bb, clip, y, line, graph_ofs, width); + text_draw (a, contents->options, bb, clip, y, line, graph_ofs, width); /* If a new-line ended the line, just skip the new-line. Otherwise, skip past any spaces past the end of the line (but not past a new-line). */ @@ -935,9 +942,105 @@ ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, *widthp = width; pos += ofs; } - *heightp = y - bb[V][0]; free (breaks); + + return y; +} + +static int +ascii_layout_subtable (struct ascii_driver *a, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2] UNUSED, + int *widthp) +{ + const struct table *table = contents->table; + struct render_params params; + struct render_page *page; + int r[TABLE_N_AXES][2]; + int width, height; + int i; + + params.draw_line = ascii_draw_line; + params.measure_cell_width = ascii_measure_cell_width; + params.measure_cell_height = ascii_measure_cell_height; + params.adjust_break = NULL; + params.draw_cell = ascii_draw_cell, + params.aux = a; + params.size[H] = bb[TABLE_HORZ][1] - bb[TABLE_HORZ][0]; + params.size[V] = bb[TABLE_VERT][1] - bb[TABLE_VERT][0]; + params.font_size[H] = 1; + params.font_size[V] = 1; + for (i = 0; i < RENDER_N_LINES; i++) + { + int width = i == RENDER_LINE_NONE ? 0 : 1; + params.line_widths[H][i] = width; + params.line_widths[V][i] = width; + } + + page = render_page_create (¶ms, table); + width = render_page_get_size (page, TABLE_HORZ); + height = render_page_get_size (page, TABLE_VERT); + + /* r = intersect(bb, clip) - bb. */ + for (i = 0; i < TABLE_N_AXES; i++) + { + r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0]; + r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0]; + } + + if (r[H][0] < r[H][1] && r[V][0] < r[V][1]) + { + unsigned int alignment = contents->options & TAB_ALIGNMENT; + int save_x = a->x; + + a->x += bb[TABLE_HORZ][0]; + if (alignment == TAB_RIGHT) + a->x += params.size[H] - width; + else if (alignment == TAB_CENTER) + a->x += (params.size[H] - width) / 2; + a->y += bb[TABLE_VERT][0]; + render_page_draw (page); + a->y -= bb[TABLE_VERT][0]; + a->x = save_x; + } + render_page_unref (page); + + if (width > *widthp) + *widthp = width; + return bb[V][0] + height; +} + +static void +ascii_layout_cell (struct ascii_driver *a, const struct table_cell *cell, + int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *widthp, int *heightp) +{ + int bb[TABLE_N_AXES][2]; + size_t i; + + *widthp = 0; + *heightp = 0; + + memcpy (bb, bb_, sizeof bb); + for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) + { + const struct cell_contents *contents = &cell->contents[i]; + + /* Put a blank line between contents. */ + if (i > 0) + { + bb[V][0]++; + if (bb[V][0] >= bb[V][1]) + break; + } + + if (contents->text) + bb[V][0] = ascii_layout_cell_text (a, contents, bb, clip, widthp); + else + bb[V][0] = ascii_layout_subtable (a, contents, bb, clip, widthp); + } + *heightp = bb[V][0] - bb_[V][0]; } void @@ -945,6 +1048,7 @@ ascii_test_write (struct output_driver *driver, const char *s, int x, int y, unsigned int options) { struct ascii_driver *a = ascii_driver_cast (driver); + struct cell_contents contents; struct table_cell cell; int bb[TABLE_N_AXES][2]; int width, height; @@ -953,9 +1057,13 @@ ascii_test_write (struct output_driver *driver, return; a->y = 0; + contents.options = options | TAB_LEFT; + contents.text = CONST_CAST (char *, s); + contents.table = NULL; + memset (&cell, 0, sizeof cell); - cell.contents = s; - cell.options = options | TAB_LEFT; + cell.contents = &contents; + cell.n_contents = 1; bb[TABLE_HORZ][0] = x; bb[TABLE_HORZ][1] = a->width; diff --git a/src/output/automake.mk b/src/output/automake.mk index 7cdee5937e..0b3e1fd78c 100644 --- a/src/output/automake.mk +++ b/src/output/automake.mk @@ -52,6 +52,7 @@ src_output_liboutput_la_SOURCES = \ src/output/table-paste.c \ src/output/table-provider.h \ src/output/table-select.c \ + src/output/table-stomp.c \ src/output/table-transpose.c \ src/output/table.c \ src/output/table.h \ diff --git a/src/output/cairo.c b/src/output/cairo.c index cbc3afc563..e7fc7f931e 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -144,8 +144,9 @@ struct xr_driver char *subtitle; cairo_t *cairo; int page_number; /* Current page number. */ - int y; + int x, y; struct xr_render_fsm *fsm; + int nest; }; static const struct output_driver_class cairo_driver_class; @@ -162,6 +163,8 @@ static int xr_measure_cell_height (void *, const struct table_cell *, static void xr_draw_cell (void *, const struct table_cell *, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]); +static int xr_adjust_break (void *, const struct table_cell *, + int width, int height); static struct xr_render_fsm *xr_render_output_item ( struct xr_driver *, const struct output_item *); @@ -369,6 +372,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo) xr->params->draw_line = xr_draw_line; xr->params->measure_cell_width = xr_measure_cell_width; xr->params->measure_cell_height = xr_measure_cell_height; + xr->params->adjust_break = xr_adjust_break; xr->params->draw_cell = xr_draw_cell; xr->params->aux = xr; xr->params->size[H] = xr->width; @@ -528,10 +532,14 @@ xr_flush (struct output_driver *driver) } static void -xr_init_caption_cell (const char *caption, struct table_cell *cell) +xr_init_caption_cell (const char *caption, struct table_cell *cell, + struct cell_contents *contents) { - cell->contents = caption; - cell->options = TAB_LEFT; + contents->options = TAB_LEFT; + contents->text = CONST_CAST (char *, caption); + contents->table = NULL; + cell->contents = contents; + cell->n_contents = 1; cell->destructor = NULL; } @@ -544,10 +552,11 @@ xr_render_table_item (struct xr_driver *xr, const struct table_item *item, if (caption != NULL) { /* XXX doesn't do well with very large captions */ + struct cell_contents contents; int min_width, max_width; struct table_cell cell; - xr_init_caption_cell (caption, &cell); + xr_init_caption_cell (caption, &cell, &contents); xr_measure_cell_width (xr, &cell, &min_width, &max_width); *caption_widthp = MIN (max_width, xr->width); @@ -605,7 +614,7 @@ xr_driver_next_page (struct xr_driver *xr, cairo_t *cairo) xr->page_number++; xr->cairo = cairo; - xr->y = 0; + xr->x = xr->y = 0; xr_driver_run_fsm (xr); } @@ -657,14 +666,26 @@ xr_driver_run_fsm (struct xr_driver *xr) static void xr_layout_cell (struct xr_driver *, const struct table_cell *, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], - int *width, int *height); + int *width, int *height, int *brk); static void dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1) { cairo_new_path (xr->cairo); - cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0 + xr->y)); - cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1 + xr->y)); + cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_stroke (xr->cairo); +} + +static void UNUSED +dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1) +{ + cairo_new_path (xr->cairo); + cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_line_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y1 + xr->y)); + cairo_close_path (xr->cairo); cairo_stroke (xr->cairo); } @@ -833,10 +854,10 @@ xr_measure_cell_width (void *xr_, const struct table_cell *cell, bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, max_width, &h); + xr_layout_cell (xr, cell, bb, clip, max_width, &h, NULL); bb[H][1] = 1; - xr_layout_cell (xr, cell, bb, clip, min_width, &h); + xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL); } static int @@ -852,7 +873,7 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width) bb[V][0] = 0; bb[V][1] = INT_MAX; clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; - xr_layout_cell (xr, cell, bb, clip, &w, &h); + xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL); return h; } @@ -861,29 +882,67 @@ xr_draw_cell (void *xr_, const struct table_cell *cell, int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2]) { struct xr_driver *xr = xr_; - int w, h; + int w, h, brk; - xr_layout_cell (xr, cell, bb, clip, &w, &h); + xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); +} + +static int +xr_adjust_break (void *xr_, const struct table_cell *cell, + int width, int height) +{ + struct xr_driver *xr = xr_; + int bb[TABLE_N_AXES][2]; + int clip[TABLE_N_AXES][2]; + int w, h, brk; + + if (xr_measure_cell_height (xr_, cell, width) < height) + return -1; + + bb[H][0] = 0; + bb[H][1] = width; + bb[V][0] = 0; + bb[V][1] = height; + clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0; + xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk); + return brk; } static void -xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, - int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], - int *width, int *height) +xr_clip (struct xr_driver *xr, int clip[TABLE_N_AXES][2]) { + if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX) + { + double x0 = xr_to_pt (clip[H][0] + xr->x); + double y0 = xr_to_pt (clip[V][0] + xr->y); + double x1 = xr_to_pt (clip[H][1] + xr->x); + double y1 = xr_to_pt (clip[V][1] + xr->y); + + cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0); + cairo_clip (xr->cairo); + } +} + +static int +xr_layout_cell_text (struct xr_driver *xr, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int y, int *widthp, int *brk) +{ + unsigned int options = contents->options; struct xr_font *font; int w, h; - font = (cell->options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] - : cell->options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] + font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED] + : options & TAB_EMPH ? &xr->fonts[XR_FONT_EMPHASIS] : &xr->fonts[XR_FONT_PROPORTIONAL]); - pango_layout_set_text (font->layout, cell->contents, -1); + pango_layout_set_text (font->layout, contents->text, -1); pango_layout_set_alignment ( font->layout, - ((cell->options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT - : (cell->options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT + ((options & TAB_ALIGNMENT) == TAB_RIGHT ? PANGO_ALIGN_RIGHT + : (options & TAB_ALIGNMENT) == TAB_LEFT ? PANGO_ALIGN_LEFT : PANGO_ALIGN_CENTER)); pango_layout_set_width ( font->layout, @@ -893,38 +952,232 @@ xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, if (clip[H][0] != clip[H][1]) { cairo_save (xr->cairo); + xr_clip (xr, clip); + cairo_translate (xr->cairo, + xr_to_pt (bb[H][0] + xr->x), + xr_to_pt (y + xr->y)); + pango_cairo_show_layout (xr->cairo, font->layout); - if (clip[H][1] != INT_MAX || clip[V][1] != INT_MAX) + /* If enabled, this draws a blue rectangle around the extents of each + line of text, which can be rather useful for debugging layout + issues. */ + if (0) { - double x0 = xr_to_pt (clip[H][0]); - double y0 = xr_to_pt (clip[V][0] + xr->y); - double x1 = xr_to_pt (clip[H][1]); - double y1 = xr_to_pt (clip[V][1] + xr->y); - - cairo_rectangle (xr->cairo, x0, y0, x1 - x0, y1 - y0); - cairo_clip (xr->cairo); + PangoLayoutIter *iter; + iter = pango_layout_get_iter (font->layout); + do + { + PangoRectangle extents; + + pango_layout_iter_get_line_extents (iter, &extents, NULL); + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 1, 0, 0); + dump_rectangle (xr, + pango_to_xr (extents.x) - xr->x, + pango_to_xr (extents.y) - xr->y, + pango_to_xr (extents.x + extents.width) - xr->x, + pango_to_xr (extents.y + extents.height) - xr->y); + cairo_restore (xr->cairo); + } + while (pango_layout_iter_next_line (iter)); + pango_layout_iter_free (iter); } - cairo_translate (xr->cairo, - xr_to_pt (bb[H][0]), - xr_to_pt (bb[V][0] + xr->y)); - pango_cairo_show_layout (xr->cairo, font->layout); cairo_restore (xr->cairo); } pango_layout_get_size (font->layout, &w, &h); - *width = pango_to_xr (w); - *height = pango_to_xr (h); + w = pango_to_xr (w); + h = pango_to_xr (h); + if (w > *widthp) + *widthp = w; + if (y + h >= bb[V][1]) + { + PangoLayoutIter *iter; + int best UNUSED = 0; + + /* Choose a breakpoint between lines instead of in the middle of one. */ + iter = pango_layout_get_iter (font->layout); + do + { + PangoRectangle extents; + int y0, y1; + int bottom; + + pango_layout_iter_get_line_extents (iter, NULL, &extents); + pango_layout_iter_get_line_yrange (iter, &y0, &y1); + extents.x = pango_to_xr (extents.x); + extents.y = pango_to_xr (y0); + extents.width = pango_to_xr (extents.width); + extents.height = pango_to_xr (y1 - y0); + bottom = y + extents.y + extents.height; + if (bottom < bb[V][1]) + { + if (brk && clip[H][0] != clip[H][1]) + best = bottom; + *brk = bottom; + } + else + break; + } + while (pango_layout_iter_next_line (iter)); + + /* If enabled, draws a green line across the chosen breakpoint, which can + be useful for debugging issues with breaking. */ + if (0) + { + if (best && !xr->nest) + { + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 0, 1, 0); + dump_line (xr, -xr->left_margin, best, xr->width + xr->right_margin, best); + cairo_restore (xr->cairo); + } + } + } + return y + h; +} + +static int +xr_layout_cell_subtable (struct xr_driver *xr, + const struct cell_contents *contents, + int bb[TABLE_N_AXES][2], + int clip[TABLE_N_AXES][2], int *widthp, int *brk) +{ + const struct table *table = contents->table; + int single_width, double_width; + struct render_params params; + struct render_page *page; + int r[TABLE_N_AXES][2]; + int width, height; + int i; + + params.draw_line = xr_draw_line; + params.measure_cell_width = xr_measure_cell_width; + params.measure_cell_height = xr_measure_cell_height; + params.adjust_break = NULL; + params.draw_cell = xr_draw_cell; + params.aux = xr; + params.size[H] = bb[H][1] - bb[H][0]; + params.size[V] = bb[V][1] - bb[V][0]; + params.font_size[H] = xr->char_width; + params.font_size[V] = xr->char_height; + + single_width = 2 * xr->line_gutter + xr->line_width; + double_width = 2 * xr->line_gutter + xr->line_space + 2 * xr->line_width; + for (i = 0; i < TABLE_N_AXES; i++) + { + params.line_widths[i][RENDER_LINE_NONE] = 0; + params.line_widths[i][RENDER_LINE_SINGLE] = single_width; + params.line_widths[i][RENDER_LINE_DOUBLE] = double_width; + } + + xr->nest++; + page = render_page_create (¶ms, table); + width = render_page_get_size (page, H); + height = render_page_get_size (page, V); + if (bb[V][0] + height >= bb[V][1]) + *brk = bb[V][0] + render_page_get_best_breakpoint (page, bb[V][1] - bb[V][0]); + + /* r = intersect(bb, clip) - bb. */ + for (i = 0; i < TABLE_N_AXES; i++) + { + r[i][0] = MAX (bb[i][0], clip[i][0]) - bb[i][0]; + r[i][1] = MIN (bb[i][1], clip[i][1]) - bb[i][0]; + } + + if (r[H][0] < r[H][1] && r[V][0] < r[V][1]) + { + unsigned int alignment = contents->options & TAB_ALIGNMENT; + int save_x = xr->x; + + cairo_save (xr->cairo); + xr_clip (xr, clip); + xr->x += bb[H][0]; + if (alignment == TAB_RIGHT) + xr->x += params.size[H] - width; + else if (alignment == TAB_CENTER) + xr->x += (params.size[H] - width) / 2; + xr->y += bb[V][0]; + render_page_draw_region (page, r[H][0], r[V][0], + r[H][1] - r[H][0], r[V][1] - r[V][0]); + xr->y -= bb[V][0]; + xr->x = save_x; + cairo_restore (xr->cairo); + } + render_page_unref (page); + xr->nest--; + + if (width > *widthp) + *widthp = width; + return bb[V][0] + height; +} + +static void +xr_layout_cell (struct xr_driver *xr, const struct table_cell *cell, + int bb_[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2], + int *width, int *height, int *brk) +{ + int bb[TABLE_N_AXES][2]; + size_t i; + + *width = 0; + *height = 0; + if (brk) + *brk = 0; + + memcpy (bb, bb_, sizeof bb); + + /* If enabled, draws a blue rectangle around the cell extents, which can be + useful for debugging layout. */ + if (0) + { + if (clip[H][0] != clip[H][1]) + { + int offset = (xr->nest) * XR_POINT; + + cairo_save (xr->cairo); + cairo_set_source_rgb (xr->cairo, 0, 0, 1); + dump_rectangle (xr, + bb[H][0] + offset, bb[V][0] + offset, + bb[H][1] - offset, bb[V][1] - offset); + cairo_restore (xr->cairo); + } + } + + for (i = 0; i < cell->n_contents && bb[V][0] < bb[V][1]; i++) + { + const struct cell_contents *contents = &cell->contents[i]; + + if (brk) + *brk = bb[V][0]; + if (i > 0) + { + bb[V][0] += xr->char_height / 2; + if (bb[V][0] >= bb[V][1]) + break; + if (brk) + *brk = bb[V][0]; + } + + if (contents->text) + bb[V][0] = xr_layout_cell_text (xr, contents, bb, clip, + bb[V][0], width, brk); + else + bb[V][0] = xr_layout_cell_subtable (xr, contents, bb, clip, width, brk); + } + *height = bb[V][0] - bb_[V][0]; } static void xr_draw_title (struct xr_driver *xr, const char *title, int title_width, int title_height) { + struct cell_contents contents; struct table_cell cell; int bb[TABLE_N_AXES][2]; - xr_init_caption_cell (title, &cell); + xr_init_caption_cell (title, &cell, &contents); bb[H][0] = 0; bb[H][1] = title_width; bb[V][0] = 0; diff --git a/src/output/csv.c b/src/output/csv.c index 9b51ed79d3..9be53c8b0f 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010, 2012 Free Software Foundation, Inc. + Copyright (C) 2009, 2010, 2012, 2013 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "libpspp/assertion.h" #include "libpspp/compiler.h" #include "libpspp/message.h" +#include "libpspp/str.h" #include "libpspp/string-map.h" #include "output/text-item.h" #include "output/driver-provider.h" @@ -146,6 +147,78 @@ csv_output_field (struct csv_driver *csv, const char *field) fputs (field, csv->file); } +static void +csv_put_field (struct csv_driver *csv, struct string *s, const char *field) +{ + while (*field == ' ') + field++; + + if (csv->quote && field[strcspn (field, csv->quote_set)]) + { + const char *p; + + ds_put_byte (s, csv->quote); + for (p = field; *p != '\0'; p++) + { + if (*p == csv->quote) + ds_put_byte (s, csv->quote); + ds_put_byte (s, *p); + } + ds_put_byte (s, csv->quote); + } + else + ds_put_cstr (s, field); +} + +static void +csv_output_subtable (struct csv_driver *csv, struct string *s, + const struct table *t) +{ + int y, x; + + for (y = 0; y < table_nr (t); y++) + { + if (y > 0) + ds_put_byte (s, '\n'); + + for (x = 0; x < table_nc (t); x++) + { + struct table_cell cell; + + table_get_cell (t, x, y, &cell); + + if (x > 0) + ds_put_cstr (s, csv->separator); + + if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0]) + csv_put_field (csv, s, ""); + else if (cell.n_contents == 1 && cell.contents[0].text != NULL) + csv_put_field (csv, s, cell.contents[0].text); + else + { + struct string s2; + size_t i; + + ds_init_empty (&s2); + for (i = 0; i < cell.n_contents; i++) + { + if (i > 0) + ds_put_cstr (&s2, "\n\n"); + + if (cell.contents[i].text != NULL) + ds_put_cstr (&s2, cell.contents[i].text); + else + csv_output_subtable (csv, &s2, cell.contents[i].table); + } + csv_put_field (csv, s, ds_cstr (&s2)); + ds_destroy (&s2); + } + + table_cell_free (&cell); + } + } +} + static void csv_output_field_format (struct csv_driver *csv, const char *format, ...) PRINTF_FORMAT (2, 3); @@ -207,8 +280,27 @@ csv_submit (struct output_driver *driver, if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0]) csv_output_field (csv, ""); + else if (cell.n_contents == 1 && cell.contents[0].text != NULL) + csv_output_field (csv, cell.contents[0].text); else - csv_output_field (csv, cell.contents); + { + struct string s; + size_t i; + + ds_init_empty (&s); + for (i = 0; i < cell.n_contents; i++) + { + if (i > 0) + ds_put_cstr (&s, "\n\n"); + + if (cell.contents[i].text != NULL) + ds_put_cstr (&s, cell.contents[i].text); + else + csv_output_subtable (csv, &s, cell.contents[i].table); + } + csv_output_field (csv, ds_cstr (&s)); + ds_destroy (&s); + } table_cell_free (&cell); } diff --git a/src/output/html.c b/src/output/html.c index eeba70bd5f..c181ed2eda 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2014 Free Software Foundation, Inc. + Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -64,7 +64,8 @@ struct html_driver static const struct output_driver_class html_driver_class; -static void html_output_table (struct html_driver *, struct table_item *); +static void html_output_table (struct html_driver *, const struct table *, + const char *caption); static void escape_string (FILE *file, const char *text, size_t length, const char *space); @@ -235,7 +236,8 @@ html_submit (struct output_driver *driver, if (is_table_item (output_item)) { struct table_item *table_item = to_table_item (output_item); - html_output_table (html, table_item); + html_output_table (html, table_item_get_table (table_item), + table_item_get_caption (table_item)); } #ifdef HAVE_CAIRO else if (is_chart_item (output_item) && html->chart_file_name != NULL) @@ -370,15 +372,13 @@ put_border (FILE *file, int n_borders, int style, const char *border_name) } static void -html_output_table (struct html_driver *html, struct table_item *item) +html_output_table (struct html_driver *html, + const struct table *t, const char *caption) { - const struct table *t = table_item_get_table (item); - const char *caption; int x, y; fputs ("\n", html->file); - caption = table_item_get_caption (item); if (caption != NULL) { fputs (" \n", html->file); for (x = 0; x < table_nc (t); x++) { + const struct cell_contents *c; struct table_cell cell; const char *tag; bool is_header; int alignment, colspan, rowspan; int top, left, right, bottom, n_borders; - const char *s; table_get_cell (t, x, y, &cell); if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0]) @@ -410,7 +410,9 @@ html_output_table (struct html_driver *html, struct table_item *item) tag = is_header ? "TH" : "TD"; fprintf (html->file, " <%s", tag); - alignment = cell.options & TAB_ALIGNMENT; + alignment = (cell.n_contents > 0 + ? cell.contents[0].options & TAB_ALIGNMENT + : TAB_LEFT); if (alignment != TAB_LEFT) fprintf (html->file, " ALIGN=\"%s\"", alignment == TAB_RIGHT ? "RIGHT" : "CENTER"); @@ -457,22 +459,31 @@ html_output_table (struct html_driver *html, struct table_item *item) putc ('>', html->file); /* Output cell contents. */ - s = cell.contents; - if (cell.options & TAB_EMPH) - fputs ("", html->file); - if (cell.options & TAB_FIX) + for (c = cell.contents; c < &cell.contents[cell.n_contents]; c++) { - fputs ("", html->file); - escape_string (html->file, s, strlen (s), " "); - fputs ("", html->file); + if (c->text) + { + const char *s = c->text; + + if (c->options & TAB_EMPH) + fputs ("", html->file); + if (c->options & TAB_FIX) + { + fputs ("", html->file); + escape_string (html->file, s, strlen (s), " "); + fputs ("", html->file); + } + else + { + s += strspn (s, CC_SPACES); + escape_string (html->file, s, strlen (s), " "); + } + if (c->options & TAB_EMPH) + fputs ("", html->file); + } + else + html_output_table (html, c->table, NULL); } - else - { - s += strspn (s, CC_SPACES); - escape_string (html->file, s, strlen (s), " "); - } - if (cell.options & TAB_EMPH) - fputs ("", html->file); /* Output or . */ fprintf (html->file, "\n", tag); diff --git a/src/output/odt.c b/src/output/odt.c index c0a386e653..24da4522c0 100644 --- a/src/output/odt.c +++ b/src/output/odt.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2010, 2011, 2012, 2014 Free Software Foundation, Inc. + Copyright (C) 2009-2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -75,6 +75,8 @@ struct odt_driver static const struct output_driver_class odt_driver_class; +static void write_table (struct odt_driver *, const struct table *); + static struct odt_driver * odt_driver_cast (struct output_driver *driver) { @@ -413,9 +415,7 @@ write_xml_with_line_breaks (xmlTextWriterPtr writer, char *line) static void odt_submit_table (struct odt_driver *odt, struct table_item *item) { - const struct table *tab = table_item_get_table (item); const char *caption = table_item_get_caption (item); - int r, c; /* Write a heading for the table */ if (caption != NULL) @@ -428,6 +428,14 @@ odt_submit_table (struct odt_driver *odt, struct table_item *item) xmlTextWriterEndElement (odt->content_wtr); } + write_table (odt, table_item_get_table (item)); +} + +static void +write_table (struct odt_driver *odt, const struct table *tab) +{ + int r, c; + /* Start table */ xmlTextWriterStartElement (odt->content_wtr, _xml("table:table")); xmlTextWriterWriteFormatAttribute (odt->content_wtr, _xml("table:name"), @@ -455,6 +463,7 @@ odt_submit_table (struct odt_driver *odt, struct table_item *item) for (c = 0 ; c < table_nc (tab) ; ++c) { struct table_cell cell; + size_t i; table_get_cell (tab, c, r, &cell); @@ -476,24 +485,37 @@ odt_submit_table (struct odt_driver *odt, struct table_item *item) odt->content_wtr, _xml("table:number-rows-spanned"), "%d", rowspan); - xmlTextWriterStartElement (odt->content_wtr, _xml("text:p")); - - if ( r < table_ht (tab) || c < table_hl (tab) ) - xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading")); - else - xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents")); - - if (strchr (cell.contents, '\n')) - { - char *line = xstrdup (cell.contents); - write_xml_with_line_breaks (odt->content_wtr, line); - free (line); - } - else - xmlTextWriterWriteString (odt->content_wtr, _xml(cell.contents)); - - xmlTextWriterEndElement (odt->content_wtr); /* text:p */ - xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */ + for (i = 0; i < cell.n_contents; i++) + { + const struct cell_contents *contents = &cell.contents[i]; + + if (contents->text) + { + xmlTextWriterStartElement (odt->content_wtr, _xml("text:p")); + + if ( r < table_ht (tab) || c < table_hl (tab) ) + xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Heading")); + else + xmlTextWriterWriteAttribute (odt->content_wtr, _xml("text:style-name"), _xml("Table_20_Contents")); + + if (strchr (contents->text, '\n')) + { + char *line = xstrdup (contents->text); + write_xml_with_line_breaks (odt->content_wtr, line); + free (line); + } + else + xmlTextWriterWriteString (odt->content_wtr, _xml(contents->text)); + + xmlTextWriterEndElement (odt->content_wtr); /* text:p */ + } + else if (contents->table) + { + write_table (odt, contents->table); + continue; + } + } + xmlTextWriterEndElement (odt->content_wtr); /* table:table-cell */ } else { diff --git a/src/output/render.c b/src/output/render.c index 62e0ef3ee9..503d5f692d 100644 --- a/src/output/render.c +++ b/src/output/render.c @@ -827,6 +827,23 @@ render_page_get_size (const struct render_page *page, enum table_axis axis) { return page->cp[axis][page->n[axis] * 2 + 1]; } + +int +render_page_get_best_breakpoint (const struct render_page *page, int height) +{ + int y; + + /* If there's no room for at least the top row and the rules above and below + it, don't include any of the table. */ + if (page->cp[V][3] > height) + return 0; + + /* Otherwise include as many rows and rules as we can. */ + for (y = 5; y <= 2 * page->n[V] + 1; y += 2) + if (page->cp[V][y] > height) + return page->cp[V][y - 2]; + return height; +} /* Drawing render_pages. */ @@ -951,7 +968,7 @@ render_page_draw_cells (const struct render_page *page, struct table_cell cell; table_get_cell (page->table, x / 2, y / 2, &cell); - if (y == bb[V][0] || y / 2 == cell.d[V][0]) + if (y / 2 == bb[V][0] / 2 || y / 2 == cell.d[V][0]) render_cell (page, &cell); x = rule_ofs (cell.d[H][1]); table_cell_free (&cell); @@ -998,7 +1015,7 @@ get_clip_min_extent (int x0, const int cp[], int n) return best; } -/* Returns the least value i, 0 <= i < n, such that cp[i + 1] >= x1. */ +/* Returns the least value i, 0 <= i < n, such that cp[i] >= x1. */ static int get_clip_max_extent (int x1, const int cp[], int n) { @@ -1017,6 +1034,9 @@ get_clip_max_extent (int x1, const int cp[], int n) low = middle + 1; } + while (best > 0 && cp[best - 1] == cp[best]) + best--; + return best; } @@ -1170,6 +1190,43 @@ render_break_next (struct render_break *b, int size) to make the output look a little better. */ if (pixel + em > cell_size) pixel = MAX (pixel - em, 0); + + /* If we're breaking vertically, then consider whether the cells + being broken have a better internal breakpoint than the exact + number of pixels available, which might look bad e.g. because + it breaks in the middle of a line of text. */ + if (axis == TABLE_VERT && page->params->adjust_break) + { + int x; + + for (x = 0; x < page->n[H]; ) + { + struct table_cell cell; + int better_pixel; + int w; + + table_get_cell (page->table, x, z, &cell); + w = joined_width (page, H, cell.d[H][0], cell.d[H][1]); + better_pixel = page->params->adjust_break ( + page->params->aux, &cell, w, pixel); + x = cell.d[H][1]; + table_cell_free (&cell); + + if (better_pixel < pixel) + { + if (better_pixel > (z == b->z ? b->pixel : 0)) + { + pixel = better_pixel; + break; + } + else if (better_pixel == 0 && z != b->z) + { + pixel = 0; + break; + } + } + } + } } break; } diff --git a/src/output/render.h b/src/output/render.h index aa1980adf5..f2ded79d6c 100644 --- a/src/output/render.h +++ b/src/output/render.h @@ -44,6 +44,19 @@ struct render_params int (*measure_cell_height) (void *aux, const struct table_cell *cell, int width); + /* Given that there is space measuring WIDTH by HEIGHT to render CELL, + where HEIGHT is insufficient to render the entire height of the cell, + returns the largest height less than HEIGHT at which it is appropriate + to break the cell. For example, if breaking at the specified HEIGHT + would break in the middle of a line of text, the return value would be + just sufficiently less that the breakpoint would be between lines of + text. + + Optional. If NULL, the rendering engine assumes that all breakpoints + are acceptable. */ + int (*adjust_break) (void *aux, const struct table_cell *cell, + int width, int height); + /* Draws a generalized intersection of lines in the rectangle whose top-left corner is (BB[TABLE_HORZ][0], BB[TABLE_VERT][0]) and whose bottom-right corner is (BB[TABLE_HORZ][1], BB[TABLE_VERT][1]). @@ -100,6 +113,8 @@ int render_page_get_size (const struct render_page *, enum table_axis); void render_page_draw (const struct render_page *); void render_page_draw_region (const struct render_page *, int x, int y, int w, int h); + +int render_page_get_best_breakpoint (const struct render_page *, int height); /* An iterator for breaking render_pages into smaller chunks. */ struct render_break diff --git a/src/output/tab.c b/src/output/tab.c index d9872c7214..2e788e5a37 100644 --- a/src/output/tab.c +++ b/src/output/tab.c @@ -44,14 +44,21 @@ #include "gettext.h" #define _(msgid) gettext (msgid) -/* Set in the options field of cells that */ -#define TAB_JOIN (1u << TAB_FIRST_AVAILABLE) +/* Cell options. */ +#define TAB_JOIN (1u << TAB_FIRST_AVAILABLE) +#define TAB_SUBTABLE (1u << (TAB_FIRST_AVAILABLE + 1)) +#define TAB_BARE (1u << (TAB_FIRST_AVAILABLE + 2)) /* Joined cell. */ struct tab_joined_cell { int d[TABLE_N_AXES][2]; /* Table region, same as struct table_cell. */ - char *contents; + union + { + char *text; + struct table *subtable; + } + u; }; static const struct table_class tab_table_class; @@ -500,9 +507,9 @@ tab_text_format (struct tab_table *table, int c, int r, unsigned opt, va_end (args); } -static void -do_tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2, - unsigned opt, char *text) +static struct tab_joined_cell * +add_joined_cell (struct tab_table *table, int x1, int y1, int x2, int y2, + unsigned opt) { struct tab_joined_cell *j; @@ -537,9 +544,6 @@ do_tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2, j->d[TABLE_VERT][0] = y1 + table->row_ofs; j->d[TABLE_HORZ][1] = ++x2 + table->col_ofs; j->d[TABLE_VERT][1] = ++y2 + table->row_ofs; - j->contents = text; - - opt |= TAB_JOIN; { void **cc = &table->cc[x1 + y1 * table->cf]; @@ -555,13 +559,15 @@ do_tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2, for (x = x1; x < x2; x++) { *cc++ = j; - *ct++ = opt; + *ct++ = opt | TAB_JOIN; } cc += ofs; ct += ofs; } } + + return j; } /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with @@ -570,8 +576,8 @@ void tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2, unsigned opt, const char *text) { - do_tab_joint_text (table, x1, y1, x2, y2, opt, - pool_strdup (table->container, text)); + char *s = pool_strdup (table->container, text); + add_joined_cell (table, x1, y1, x2, y2, opt)->u.text = s; } /* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them @@ -582,11 +588,45 @@ tab_joint_text_format (struct tab_table *table, int x1, int y1, int x2, int y2, unsigned opt, const char *format, ...) { va_list args; + char *s; va_start (args, format); - do_tab_joint_text (table, x1, y1, x2, y2, opt, - pool_vasprintf (table->container, format, args)); + s = pool_vasprintf (table->container, format, args); va_end (args); + + add_joined_cell (table, x1, y1, x2, y2, opt)->u.text = s; +} + +static void +subtable_unref (void *subtable) +{ + table_unref (subtable); +} + +/* Places SUBTABLE as the content for cells (X1,X2)-(Y1,Y2) inclusive in TABLE + with options OPT. */ +void +tab_subtable (struct tab_table *table, int x1, int y1, int x2, int y2, + unsigned opt, struct table *subtable) +{ + add_joined_cell (table, x1, y1, x2, y2, opt | TAB_SUBTABLE)->u.subtable + = subtable; + pool_register (table->container, subtable_unref, subtable); +} + +/* Places the contents of SUBTABLE as the content for cells (X1,X2)-(Y1,Y2) + inclusive in TABLE with options OPT. + + SUBTABLE must have exactly one row and column. The contents of its single + cell are used as the contents of TABLE's cell; that is, SUBTABLE is not used + as a nested table but its contents become part of TABLE. */ +void +tab_subtable_bare (struct tab_table *table, int x1, int y1, int x2, int y2, + unsigned opt, struct table *subtable) +{ + assert (table_nc (subtable) == 1); + assert (table_nr (subtable) == 1); + tab_subtable (table, x1, y1, x2, y2, opt | TAB_BARE, subtable); } bool @@ -707,17 +747,39 @@ tab_get_cell (const struct table *table, int x, int y, struct table_cell *cell) const struct tab_table *t = tab_cast (table); int index = x + y * t->cf; unsigned char opt = t->ct[index]; - const void *content = t->cc[index]; + const void *cc = t->cc[index]; + + cell->inline_contents.options = opt; + cell->inline_contents.table = NULL; + cell->destructor = NULL; - cell->options = opt; if (opt & TAB_JOIN) { - const struct tab_joined_cell *jc = content; + const struct tab_joined_cell *jc = cc; + if (opt & TAB_BARE) + { + assert (opt & TAB_SUBTABLE); + + /* This overwrites all of the members of CELL. */ + table_get_cell (jc->u.subtable, 0, 0, cell); + } + else + { + cell->contents = &cell->inline_contents; + cell->n_contents = 1; + if (opt & TAB_SUBTABLE) + { + cell->inline_contents.table = jc->u.subtable; + cell->inline_contents.text = NULL; + } + else + cell->inline_contents.text = jc->u.text; + } + cell->d[TABLE_HORZ][0] = jc->d[TABLE_HORZ][0]; cell->d[TABLE_HORZ][1] = jc->d[TABLE_HORZ][1]; cell->d[TABLE_VERT][0] = jc->d[TABLE_VERT][0]; cell->d[TABLE_VERT][1] = jc->d[TABLE_VERT][1]; - cell->contents = jc->contents; } else { @@ -725,9 +787,18 @@ tab_get_cell (const struct table *table, int x, int y, struct table_cell *cell) cell->d[TABLE_HORZ][1] = x + 1; cell->d[TABLE_VERT][0] = y; cell->d[TABLE_VERT][1] = y + 1; - cell->contents = content != NULL ? content : ""; + if (cc != NULL) + { + cell->contents = &cell->inline_contents; + cell->n_contents = 1; + cell->inline_contents.text = CONST_CAST (char *, cc); + } + else + { + cell->contents = NULL; + cell->n_contents = 0; + } } - cell->destructor = NULL; } static int diff --git a/src/output/tab.h b/src/output/tab.h index ae3f4aeaf8..f0cffc17fd 100644 --- a/src/output/tab.h +++ b/src/output/tab.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997, 1998, 1999, 2000, 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 1997, 1998, 1999, 2000, 2009, 2011, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -143,6 +143,11 @@ void tab_joint_text_format (struct tab_table *, int x1, int y1, int x2, int y2, unsigned opt, const char *, ...) PRINTF_FORMAT (7, 8); +void tab_subtable (struct tab_table *, int x1, int y1, int x2, int y2, + unsigned opt, struct table *subtable); +void tab_subtable_bare (struct tab_table *, int x1, int y1, int x2, int y2, + unsigned opt, struct table *subtable); + bool tab_cell_is_empty (const struct tab_table *, int c, int r); /* Editing. */ diff --git a/src/output/table-casereader.c b/src/output/table-casereader.c index 5d5154ef40..a6a370f1bd 100644 --- a/src/output/table-casereader.c +++ b/src/output/table-casereader.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 2009, 2011, 2013 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -110,13 +110,16 @@ table_casereader_get_cell (const struct table *t, int x, int y, cell->d[TABLE_HORZ][1] = x + 1; cell->d[TABLE_VERT][0] = y; cell->d[TABLE_VERT][1] = y + 1; - cell->options = TAB_RIGHT; + cell->contents = &cell->inline_contents; + cell->n_contents = 1; + cell->inline_contents.options = TAB_RIGHT; + cell->inline_contents.table = NULL; if (tc->heading != NULL) { if (y == 0) { s = xstrdup (tc->heading); - cell->contents = s; + cell->inline_contents.text = s; cell->destructor = free_string; cell->destructor_aux = s; return; @@ -132,7 +135,7 @@ table_casereader_get_cell (const struct table *t, int x, int y, s = data_out (case_data_idx (c, 0), UTF8, &tc->format); case_unref (c); } - cell->contents = s; + cell->inline_contents.text = s; cell->destructor = free_string; cell->destructor_aux = s; } diff --git a/src/output/table-provider.h b/src/output/table-provider.h index 36a666534f..97c264b4f2 100644 --- a/src/output/table-provider.h +++ b/src/output/table-provider.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997, 1998, 1999, 2000, 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 1997, 1998, 1999, 2000, 2009, 2011, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,16 @@ #include "output/table.h" +/* An item of contents within a table cell. */ +struct cell_contents + { + unsigned int options; /* TAB_*. */ + + /* Exactly one of these must be nonnull. */ + char *text; /* A paragraph of text. */ + struct table *table; /* A table nested within the cell. */ + }; + /* A cell in a table. */ struct table_cell { @@ -39,8 +49,18 @@ struct table_cell or both. */ int d[TABLE_N_AXES][2]; - const char *contents; /* Text string contents. */ - unsigned int options; /* TAB_* values. */ + /* The cell's contents. + + Most table cells contain only one item (a paragraph of text), but cells + are allowed to be empty (n_contents == 0) or contain a nested table, or + multiple items. + + 'inline_contents' provides a place to store a single item to handle the + common case. + */ + const struct cell_contents *contents; + size_t n_contents; + struct cell_contents inline_contents; /* Called to free the cell's data, if nonnull. */ void (*destructor) (void *destructor_aux); @@ -152,7 +172,7 @@ struct table_class RECT[TABLE_VERT][1], exclusive, and the TABLE's columns RECT[TABLE_HORZ][0] through RECT[TABLE_HORZ][1]. - Called only if TABLE is not shared (as returned by table_is_shared()).p + Called only if TABLE is not shared (as returned by table_is_shared()). This function may return a null pointer if it cannot implement the select operation, in which case the caller will use a fallback diff --git a/src/output/table-stomp.c b/src/output/table-stomp.c new file mode 100644 index 0000000000..e9df26a95f --- /dev/null +++ b/src/output/table-stomp.c @@ -0,0 +1,164 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2009, 2011, 2013, 2014 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include + +#include "libpspp/assertion.h" +#include "libpspp/tower.h" +#include "output/table-provider.h" + +#include "gl/minmax.h" +#include "gl/xalloc.h" + +/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */ +#define H TABLE_HORZ +#define V TABLE_VERT + +struct table_stomp + { + struct table table; + struct table *subtable; + }; + +static const struct table_class table_stomp_class; + +static struct table_stomp * +table_stomp_cast (const struct table *table) +{ + assert (table->klass == &table_stomp_class); + return UP_CAST (table, struct table_stomp, table); +} + +/* Returns a new table based on SUBTABLE with exactly one row. Each cell in + that row consists of the contents of all of the rows stacked together into a + single cell. So, for example, if SUBTABLE has one column and three rows, + then the returned table has one column and one row, and the single cell in + the returned table has all of the content of the three cells in + SUBTABLE. + + SUBTABLE should have the same column structure in every row, i.e. don't + stomp a table that has rows with differently joined cells. */ +struct table * +table_stomp (struct table *subtable) +{ + struct table_stomp *ts; + + if (subtable->n[V] == 1) + return subtable; + + ts = xmalloc (sizeof *ts); + table_init (&ts->table, &table_stomp_class); + ts->table.n[H] = subtable->n[H]; + ts->table.n[V] = 1; + ts->subtable = subtable; + return &ts->table; +} + +static void +table_stomp_destroy (struct table *t) +{ + struct table_stomp *ts = table_stomp_cast (t); + + table_unref (ts->subtable); + free (ts); +} + +struct table_stomp_subcells + { + struct cell_contents *contents; + + size_t n_subcells; + struct table_cell subcells[]; + }; + +static void +table_stomp_free_cell (void *sc_) +{ + struct table_stomp_subcells *sc = sc_; + size_t i; + + for (i = 0; i < sc->n_subcells; i++) + table_cell_free (&sc->subcells[i]); + free (sc->contents); + free (sc); +} + +static void +table_stomp_get_cell (const struct table *t, int x, int y UNUSED, + struct table_cell *cell) +{ + struct table_stomp *ts = table_stomp_cast (t); + size_t n_rows = ts->subtable->n[V]; + struct table_stomp_subcells *sc; + size_t row; + size_t ofs; + size_t i; + + sc = xzalloc (sizeof *sc + n_rows * sizeof *sc->subcells); + sc->n_subcells = 0; + + cell->n_contents = 0; + for (row = 0; row < n_rows; ) + { + struct table_cell *subcell = &sc->subcells[sc->n_subcells++]; + + table_get_cell (ts->subtable, x, row, subcell); + cell->n_contents += subcell->n_contents; + row = subcell->d[V][1]; + } + + cell->d[H][0] = sc->subcells[0].d[H][0]; + cell->d[V][0] = 0; + cell->d[H][1] = sc->subcells[0].d[H][1]; + cell->d[V][1] = 1; + + sc->contents = xmalloc (cell->n_contents * sizeof *cell->contents); + cell->contents = sc->contents; + + ofs = 0; + for (i = 0; i < sc->n_subcells; i++) + { + struct table_cell *subcell = &sc->subcells[i]; + + memcpy (&sc->contents[ofs], subcell->contents, + subcell->n_contents * sizeof *subcell->contents); + ofs += subcell->n_contents; + } + + cell->destructor = table_stomp_free_cell; + cell->destructor_aux = sc; +} + +static int +table_stomp_get_rule (const struct table *t, + enum table_axis axis, int x, int y) +{ + struct table_stomp *ts = table_stomp_cast (t); + + return table_get_rule (ts->subtable, axis, x, + axis == H || y == 0 ? y : ts->subtable->n[V]); +} + +static const struct table_class table_stomp_class = + { + table_stomp_destroy, + table_stomp_get_cell, + table_stomp_get_rule, + NULL, /* paste */ + NULL, /* select */ + }; diff --git a/src/output/table.c b/src/output/table.c index e9abca85e3..2a08e14b55 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2009, 2011 Free Software Foundation, Inc. + Copyright (C) 2009, 2011, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -316,8 +316,11 @@ table_string_get_cell (const struct table *ts_, int x UNUSED, int y UNUSED, cell->d[TABLE_HORZ][1] = 1; cell->d[TABLE_VERT][0] = 0; cell->d[TABLE_VERT][1] = 1; - cell->contents = ts->string; - cell->options = ts->options; + cell->contents = &cell->inline_contents; + cell->inline_contents.options = ts->options; + cell->inline_contents.text = ts->string; + cell->inline_contents.table = NULL; + cell->n_contents = 1; cell->destructor = NULL; } @@ -337,3 +340,71 @@ static const struct table_class table_string_class = NULL, /* paste */ NULL, /* select */ }; + +struct table_nested + { + struct table table; + struct table *inner; + }; + +static const struct table_class table_nested_class; + +/* Creates and returns a table with a single cell that contains INNER. */ +struct table * +table_create_nested (const struct table *inner) +{ + struct table_nested *tn = xmalloc (sizeof *tn); + table_init (&tn->table, &table_nested_class); + tn->table.n[TABLE_HORZ] = tn->table.n[TABLE_VERT] = 1; + tn->inner = table_ref (inner); + return &tn->table; +} + +static struct table_nested * +table_nested_cast (const struct table *table) +{ + assert (table->klass == &table_nested_class); + return UP_CAST (table, struct table_nested, table); +} + +static void +table_nested_destroy (struct table *tn_) +{ + struct table_nested *tn = table_nested_cast (tn_); + table_unref (tn->inner); + free (tn); +} + +static void +table_nested_get_cell (const struct table *tn_, int x UNUSED, int y UNUSED, + struct table_cell *cell) +{ + struct table_nested *tn = table_nested_cast (tn_); + cell->d[TABLE_HORZ][0] = 0; + cell->d[TABLE_HORZ][1] = 1; + cell->d[TABLE_VERT][0] = 0; + cell->d[TABLE_VERT][1] = 1; + cell->contents = &cell->inline_contents; + cell->inline_contents.options = TAB_LEFT; + cell->inline_contents.text = NULL; + cell->inline_contents.table = tn->inner; + cell->n_contents = 1; + cell->destructor = NULL; +} + +static int +table_nested_get_rule (const struct table *tn UNUSED, + enum table_axis axis UNUSED, int x UNUSED, int y UNUSED) +{ + return TAL_0; +} + +static const struct table_class table_nested_class = + { + table_nested_destroy, + table_nested_get_cell, + table_nested_get_rule, + NULL, /* paste */ + NULL, /* select */ + }; + diff --git a/src/output/table.h b/src/output/table.h index e245b72760..cf187f10dc 100644 --- a/src/output/table.h +++ b/src/output/table.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 1997, 1998, 1999, 2000, 2009 Free Software Foundation, Inc. + Copyright (C) 1997, 1998, 1999, 2000, 2009, 2013, 2014 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -162,18 +162,22 @@ void table_set_hb (struct table *, int hb); /* Simple kinds of tables. */ struct table *table_from_string (unsigned int options, const char *); +struct table *table_from_string_span (unsigned int options, const char *, + int colspan, int rowspan); struct table *table_from_variables (unsigned int options, struct variable **, size_t); struct table *table_from_casereader (const struct casereader *, size_t column, const char *heading, const struct fmt_spec *); +struct table *table_create_nested (const struct table *); /* Combining tables. */ struct table *table_paste (struct table *, struct table *, enum table_axis orientation); struct table *table_hpaste (struct table *left, struct table *right); struct table *table_vpaste (struct table *top, struct table *bottom); +struct table *table_stomp (struct table *); /* Taking subsets of tables. */ struct table *table_select (struct table *, int rect[TABLE_N_AXES][2]); diff --git a/tests/output/render-test.c b/tests/output/render-test.c index ab1045926e..fbc0f20848 100644 --- a/tests/output/render-test.c +++ b/tests/output/render-test.c @@ -64,7 +64,7 @@ static const char *output_base = "render"; static const char *parse_options (int argc, char **argv); static void usage (void) NO_RETURN; -static struct table *read_table (FILE *); +static struct table *read_table (FILE *, struct table **tables, size_t n_tables); static void draw (FILE *); int @@ -87,13 +87,30 @@ main (int argc, char **argv) if (!draw_mode) { + struct table **tables = NULL; + size_t allocated_tables = 0; + size_t n_tables = 0; struct table *table; - table = read_table (input); + for (;;) + { + int ch; + + if (n_tables >= allocated_tables) + tables = x2nrealloc (tables, &allocated_tables, sizeof *tables); + + tables[n_tables] = read_table (input, tables, n_tables); + n_tables++; + + ch = getc (input); + if (ch == EOF) + break; + ungetc (ch, input); + } + table = tables[n_tables - 1]; if (transpose) table = table_transpose (table); - table_item_submit (table_item_create (table, NULL)); } else @@ -316,7 +333,7 @@ replace_newlines (char *p) } static struct table * -read_table (FILE *stream) +read_table (FILE *stream, struct table **tables, size_t n_tables) { struct tab_table *tab; char buffer[1024]; @@ -344,7 +361,9 @@ read_table (FILE *stream) for (c = 0; c < nc; c++) if (tab_cell_is_empty (tab, c, r)) { + unsigned int opt; char *new_line; + unsigned int i; char *text; int rs, cs; @@ -369,7 +388,8 @@ read_table (FILE *stream) cs = 1; } - while (*text && strchr ("<>^,@", *text)) + opt = 0; + while (*text && strchr ("<>^,@()|", *text)) switch (*text++) { case '<': @@ -393,18 +413,56 @@ read_table (FILE *stream) c + cs - 1, r + rs - 1); break; + case '(': + opt &= ~TAB_ALIGNMENT; + opt |= TAB_LEFT; + break; + + case ')': + opt &= ~TAB_ALIGNMENT; + opt |= TAB_RIGHT; + break; + + case '|': + opt &= ~TAB_ALIGNMENT; + opt |= TAB_CENTER; + break; + default: NOT_REACHED (); } replace_newlines (text); - tab_joint_text (tab, c, r, c + cs - 1, r + rs - 1, 0, text); + if (sscanf (text, "{%u}", &i) == 1) + { + struct table *table; + + if (i >= n_tables) + error (1, 0, "bad table number %u", i); + table = table_ref (tables[i]); + + text = strchr (text, '}') + 1; + while (*text) + switch (*text++) + { + case 's': + table = table_stomp (table); + break; + + case 't': + table = table_transpose (table); + break; + + default: + error (1, 0, "unexpected subtable modifier \"%c\"", *text); + } + tab_subtable (tab, c, r, c + cs - 1, r + rs - 1, opt, table); + } + else + tab_joint_text (tab, c, r, c + cs - 1, r + rs - 1, opt, text); } - if (getc (stream) != EOF) - error (1, 0, "unread data at end of input"); - return &tab->table; } diff --git a/tests/output/render.at b/tests/output/render.at index e6370c00d3..388674ba39 100644 --- a/tests/output/render.at +++ b/tests/output/render.at @@ -167,6 +167,17 @@ AT_CHECK([render-test input], [0], [abc ]) AT_CLEANUP +AT_SETUP([nested single cell]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [1 1 +abc +1 1 +{0} +]) +AT_CHECK([render-test input], [0], [abc +]) +AT_CLEANUP + AT_SETUP([single cell with border]) AT_KEYWORDS([render rendering]) AT_DATA([input], [1 1 @@ -179,6 +190,22 @@ AT_CHECK([render-test input], [0], [dnl ]) AT_CLEANUP +AT_SETUP([nested single cell with border]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [1 1 +@abc +1 1 +@{0} +]) +AT_CHECK([render-test input], [0], [dnl ++-----+ +|+---+| +||abc|| +|+---+| ++-----+ +]) +AT_CLEANUP + AT_SETUP([joined columns]) AT_KEYWORDS([render rendering]) AT_DATA([input], [2 2 @@ -322,6 +349,26 @@ AT_CHECK([render-test input], [0], [dnl ]) AT_CLEANUP +AT_SETUP([nested joined rows]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [2 2 +2*1 @ab\ncd\nef +@hij +@klm +1 1 +@{0} +]) +AT_CHECK([render-test input], [0], [dnl ++--------+ +|+--+---+| +||ab|hij|| +||cd+---+| +||ef|klm|| +|+--+---+| ++--------+ +]) +AT_CLEANUP + dnl This checks for bug #31346, a segmentation fault that surfaced dnl when two or more rows had no unspanned cells and no rules. AT_SETUP([joined rows only, no rules]) @@ -463,6 +510,203 @@ AT_CHECK([render-test input], [0],[dnl +-----+------+----+ ]) AT_CLEANUP + +AT_SETUP([nested 8x8]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [WEAVE_8X8[]dnl +1 1 +@{0} +]) +AT_CHECK([render-test input], [0], [dnl ++-----------------+ +|+-+-+-+-+-+-+-+-+| +||a|b|c|d|e|f|g|h|| +|+-+-+-+-+-+-+-+-+| +||i|jkl|m|nop|q|t|| +|+-+-+-+-+-+-+r+-+| +||u|v|wxy|z|A|s|D|| +|+-+-+-+-+-+B+-+-+| +||E|F|I|JKL|C|M|P|| +|+-+G+-+---+-+N+-+| +||Q|H|R|UVW|X|O|Y|| +|+-+-+S+-+-+-+-+-+| +||Z|0|T|3|456|7|8|| +|+-+1+-+-+-+-+-+-+| +||9|2|abc|d|efg|h|| +|+-+-+-+-+-+-+-+-+| +||i|j|k|l|m|n|o|p|| +|+-+-+-+-+-+-+-+-+| ++-----------------+ +]) +AT_CLEANUP + +AT_SETUP([nested 8x8s and 6x6s]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [WEAVE_8X8[]WEAVE_6X6[]dnl +4 2 +@{0} +@{1} +@{1} +@|{1} +@|{1} +@({1} +@({1} +@{0} +]) +AT_CHECK([render-test input], [0], [dnl ++-----------------+-----------------+ +|+-+-+-+-+-+-+-+-+| +-+---+-+-+-+| +||a|b|c|d|e|f|g|h|| |a|bcd|e|f|i|| +|+-+-+-+-+-+-+-+-+| +-+-+-+-+g+-+| +||i|jkl|m|nop|q|t|| |j|m|nop|h|q|| +|+-+-+-+-+-+-+r+-+| |k+-+-+-+-+r|| +||u|v|wxy|z|A|s|D|| |l|t|w|xyz|s|| +|+-+-+-+-+-+B+-+-+| +-+u+-+-+-+-+| +||E|F|I|JKL|C|M|P|| |A|v|B|E|FGH|| +|+-+G+-+---+-+N+-+| +-+-+C+-+-+-+| +||Q|H|R|UVW|X|O|Y|| |IJK|D|L|O|P|| +|+-+-+S+-+-+-+-+-+| +-+-+-+M+-+-+| +||Z|0|T|3|456|7|8|| |Q|RST|N|U|V|| +|+-+1+-+-+-+-+-+-+| +-+---+-+-+-+| +||9|2|abc|d|efg|h|| | +|+-+-+-+-+-+-+-+-+| | +||i|j|k|l|m|n|o|p|| | +|+-+-+-+-+-+-+-+-+| | ++-----------------+-----------------+ +| +-+---+-+-+-+| +-+---+-+-+-+ | +| |a|bcd|e|f|i|| |a|bcd|e|f|i| | +| +-+-+-+-+g+-+| +-+-+-+-+g+-+ | +| |j|m|nop|h|q|| |j|m|nop|h|q| | +| |k+-+-+-+-+r|| |k+-+-+-+-+r| | +| |l|t|w|xyz|s|| |l|t|w|xyz|s| | +| +-+u+-+-+-+-+| +-+u+-+-+-+-+ | +| |A|v|B|E|FGH|| |A|v|B|E|FGH| | +| +-+-+C+-+-+-+| +-+-+C+-+-+-+ | +| |IJK|D|L|O|P|| |IJK|D|L|O|P| | +| +-+-+-+M+-+-+| +-+-+-+M+-+-+ | +| |Q|RST|N|U|V|| |Q|RST|N|U|V| | +| +-+---+-+-+-+| +-+---+-+-+-+ | ++-----------------+-----------------+ +| +-+---+-+-+-+ |+-+---+-+-+-+ | +| |a|bcd|e|f|i| ||a|bcd|e|f|i| | +| +-+-+-+-+g+-+ |+-+-+-+-+g+-+ | +| |j|m|nop|h|q| ||j|m|nop|h|q| | +| |k+-+-+-+-+r| ||k+-+-+-+-+r| | +| |l|t|w|xyz|s| ||l|t|w|xyz|s| | +| +-+u+-+-+-+-+ |+-+u+-+-+-+-+ | +| |A|v|B|E|FGH| ||A|v|B|E|FGH| | +| +-+-+C+-+-+-+ |+-+-+C+-+-+-+ | +| |IJK|D|L|O|P| ||IJK|D|L|O|P| | +| +-+-+-+M+-+-+ |+-+-+-+M+-+-+ | +| |Q|RST|N|U|V| ||Q|RST|N|U|V| | +| +-+---+-+-+-+ |+-+---+-+-+-+ | ++-----------------+-----------------+ +|+-+---+-+-+-+ |+-+-+-+-+-+-+-+-+| +||a|bcd|e|f|i| ||a|b|c|d|e|f|g|h|| +|+-+-+-+-+g+-+ |+-+-+-+-+-+-+-+-+| +||j|m|nop|h|q| ||i|jkl|m|nop|q|t|| +||k+-+-+-+-+r| |+-+-+-+-+-+-+r+-+| +||l|t|w|xyz|s| ||u|v|wxy|z|A|s|D|| +|+-+u+-+-+-+-+ |+-+-+-+-+-+B+-+-+| +||A|v|B|E|FGH| ||E|F|I|JKL|C|M|P|| +|+-+-+C+-+-+-+ |+-+G+-+---+-+N+-+| +||IJK|D|L|O|P| ||Q|H|R|UVW|X|O|Y|| +|+-+-+-+M+-+-+ |+-+-+S+-+-+-+-+-+| +||Q|RST|N|U|V| ||Z|0|T|3|456|7|8|| +|+-+---+-+-+-+ |+-+1+-+-+-+-+-+-+| +| ||9|2|abc|d|efg|h|| +| |+-+-+-+-+-+-+-+-+| +| ||i|j|k|l|m|n|o|p|| +| |+-+-+-+-+-+-+-+-+| ++-----------------+-----------------+ +]) +AT_CLEANUP + +AT_SETUP([doubly nested cells]) +AT_KEYWORDS([render rendering]) +AT_DATA([input], [WEAVE_8X8[]WEAVE_6X6[]dnl +4 2 +@{0} +@{1} +@{1} +@|{1} +@|{1} +@({1} +@({1} +@{0} +1 1 +@{2} +]) +AT_CHECK([render-test input --length=70], [0], [dnl ++-------------------------------------+ +|+-----------------+-----------------+| +||+-+-+-+-+-+-+-+-+| +-+---+-+-+-+|| +|||a|b|c|d|e|f|g|h|| |a|bcd|e|f|i||| +||+-+-+-+-+-+-+-+-+| +-+-+-+-+g+-+|| +|||i|jkl|m|nop|q|t|| |j|m|nop|h|q||| +||+-+-+-+-+-+-+r+-+| |k+-+-+-+-+r||| +|||u|v|wxy|z|A|s|D|| |l|t|w|xyz|s||| +||+-+-+-+-+-+B+-+-+| +-+u+-+-+-+-+|| +|||E|F|I|JKL|C|M|P|| |A|v|B|E|FGH||| +||+-+G+-+---+-+N+-+| +-+-+C+-+-+-+|| +|||Q|H|R|UVW|X|O|Y|| |IJK|D|L|O|P||| +||+-+-+S+-+-+-+-+-+| +-+-+-+M+-+-+|| +|||Z|0|T|3|456|7|8|| |Q|RST|N|U|V||| +||+-+1+-+-+-+-+-+-+| +-+---+-+-+-+|| +|||9|2|abc|d|efg|h|| || +||+-+-+-+-+-+-+-+-+| || +|||i|j|k|l|m|n|o|p|| || +||+-+-+-+-+-+-+-+-+| || +|+-----------------+-----------------+| +|| +-+---+-+-+-+| +-+---+-+-+-+ || +|| |a|bcd|e|f|i|| |a|bcd|e|f|i| || +|| +-+-+-+-+g+-+| +-+-+-+-+g+-+ || +|| |j|m|nop|h|q|| |j|m|nop|h|q| || +|| |k+-+-+-+-+r|| |k+-+-+-+-+r| || +|| |l|t|w|xyz|s|| |l|t|w|xyz|s| || +|| +-+u+-+-+-+-+| +-+u+-+-+-+-+ || +|| |A|v|B|E|FGH|| |A|v|B|E|FGH| || +|| +-+-+C+-+-+-+| +-+-+C+-+-+-+ || +|| |IJK|D|L|O|P|| |IJK|D|L|O|P| || +|| +-+-+-+M+-+-+| +-+-+-+M+-+-+ || +|| |Q|RST|N|U|V|| |Q|RST|N|U|V| || +|| +-+---+-+-+-+| +-+---+-+-+-+ || +|+-----------------+-----------------+| +|| +-+---+-+-+-+ |+-+---+-+-+-+ || +|| |a|bcd|e|f|i| ||a|bcd|e|f|i| || +|| +-+-+-+-+g+-+ |+-+-+-+-+g+-+ || +|| |j|m|nop|h|q| ||j|m|nop|h|q| || +|| |k+-+-+-+-+r| ||k+-+-+-+-+r| || +|| |l|t|w|xyz|s| ||l|t|w|xyz|s| || +|| +-+u+-+-+-+-+ |+-+u+-+-+-+-+ || +|| |A|v|B|E|FGH| ||A|v|B|E|FGH| || +|| +-+-+C+-+-+-+ |+-+-+C+-+-+-+ || +|| |IJK|D|L|O|P| ||IJK|D|L|O|P| || +|| +-+-+-+M+-+-+ |+-+-+-+M+-+-+ || +|| |Q|RST|N|U|V| ||Q|RST|N|U|V| || +|| +-+---+-+-+-+ |+-+---+-+-+-+ || +|+-----------------+-----------------+| +||+-+---+-+-+-+ |+-+-+-+-+-+-+-+-+|| +|||a|bcd|e|f|i| ||a|b|c|d|e|f|g|h||| +||+-+-+-+-+g+-+ |+-+-+-+-+-+-+-+-+|| +|||j|m|nop|h|q| ||i|jkl|m|nop|q|t||| +|||k+-+-+-+-+r| |+-+-+-+-+-+-+r+-+|| +|||l|t|w|xyz|s| ||u|v|wxy|z|A|s|D||| +||+-+u+-+-+-+-+ |+-+-+-+-+-+B+-+-+|| +|||A|v|B|E|FGH| ||E|F|I|JKL|C|M|P||| +||+-+-+C+-+-+-+ |+-+G+-+---+-+N+-+|| +|||IJK|D|L|O|P| ||Q|H|R|UVW|X|O|Y||| +||+-+-+-+M+-+-+ |+-+-+S+-+-+-+-+-+|| +|||Q|RST|N|U|V| ||Z|0|T|3|456|7|8||| +||+-+---+-+-+-+ |+-+1+-+-+-+-+-+-+|| +|| ||9|2|abc|d|efg|h||| +|| |+-+-+-+-+-+-+-+-+|| +|| ||i|j|k|l|m|n|o|p||| +|| |+-+-+-+-+-+-+-+-+|| +|+-----------------+-----------------+| ++-------------------------------------+ +]) +AT_CLEANUP AT_BANNER([output rendering -- horizontal page breaks]) @@ -2091,6 +2335,38 @@ AT_CHECK([render-test --width=7 --length=6 input], [0], [expout]) AT_CHECK([render-test -o mb0 --min-break=0 --width=7 --length=6 input], [0], [expout]) AT_CLEANUP + +AT_SETUP([breaking nested cell too tall for page]) +AT_KEYWORDS([render rendering]) +AT_CAPTURE_FILE([input]) +AT_DATA([input], [WEAVE_8X8[]WEAVE_6X6[]dnl +1 2 +@{0} +@{1} +]) +AT_CHECK([render-test input --length=10], [0], [dnl ++-----------------+-------------+ +|+-+-+-+-+-+-+-+-+|+-+---+-+-+-+| +||a|b|c|d|e|f|g|h|||a|bcd|e|f|i|| +|+-+-+-+-+-+-+-+-+|+-+-+-+-+g+-+| +||i|jkl|m|nop|q|t|||j|m|nop|h|q|| +|+-+-+-+-+-+-+r+-+||k+-+-+-+-+r|| +||u|v|wxy|z|A|s|D|||l|t|w|xyz|s|| +|+-+-+-+-+-+B+-+-+|+-+u+-+-+-+-+| +||E|F|I|JKL|C|M|P|||A|v|B|E|FGH|| +|+-+G+-+---+-+N+-+|+-+-+C+-+-+-+| + +||Q|H|R|UVW|X|O|Y|||IJK|D|L|O|P|| +|+-+-+S+-+-+-+-+-+|+-+-+-+M+-+-+| +||Z|0|T|3|456|7|8|||Q|RST|N|U|V|| +|+-+1+-+-+-+-+-+-+|+-+---+-+-+-+| +||9|2|abc|d|efg|h|| | +|+-+-+-+-+-+-+-+-+| | +||i|j|k|l|m|n|o|p|| | +|+-+-+-+-+-+-+-+-+| | ++-----------------+-------------+ +]) +AT_CLEANUP AT_BANNER([output rendering -- double page breaks])
", html->file); @@ -391,12 +391,12 @@ html_output_table (struct html_driver *html, struct table_item *item) fputs ("