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;
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;
}
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;
{
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++)
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);
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;
default:
NOT_REACHED ();
}
+ x += a->x;
if (x >= x1)
return;
}
}
-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);
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;
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). */
*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
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;
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;
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 \
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;
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 *);
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;
}
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;
}
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);
xr->page_number++;
xr->cairo = cairo;
- xr->y = 0;
+ xr->x = xr->y = 0;
xr_driver_run_fsm (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);
}
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
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;
}
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;
}
\f
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,
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;
/* 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
#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"
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);
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);
}
/* 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
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);
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)
}
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 ("<TABLE>\n", html->file);
- caption = table_item_get_caption (item);
if (caption != NULL)
{
fputs (" <CAPTION>", html->file);
fputs (" <TR>\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])
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");
putc ('>', html->file);
/* Output cell contents. */
- s = cell.contents;
- if (cell.options & TAB_EMPH)
- fputs ("<EM>", html->file);
- if (cell.options & TAB_FIX)
+ for (c = cell.contents; c < &cell.contents[cell.n_contents]; c++)
{
- fputs ("<TT>", html->file);
- escape_string (html->file, s, strlen (s), " ");
- fputs ("</TT>", html->file);
+ if (c->text)
+ {
+ const char *s = c->text;
+
+ if (c->options & TAB_EMPH)
+ fputs ("<EM>", html->file);
+ if (c->options & TAB_FIX)
+ {
+ fputs ("<TT>", html->file);
+ escape_string (html->file, s, strlen (s), " ");
+ fputs ("</TT>", html->file);
+ }
+ else
+ {
+ s += strspn (s, CC_SPACES);
+ escape_string (html->file, s, strlen (s), " ");
+ }
+ if (c->options & TAB_EMPH)
+ fputs ("</EM>", 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 ("</EM>", html->file);
/* Output </TH> or </TD>. */
fprintf (html->file, "</%s>\n", tag);
/* 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
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)
{
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)
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"),
for (c = 0 ; c < table_nc (tab) ; ++c)
{
struct table_cell cell;
+ size_t i;
table_get_cell (tab, c, r, &cell);
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
{
{
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;
+}
\f
/* Drawing render_pages. */
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);
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)
{
low = middle + 1;
}
+ while (best > 0 && cp[best - 1] == cp[best])
+ best--;
+
return best;
}
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;
}
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]).
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);
\f
/* An iterator for breaking render_pages into smaller chunks. */
struct render_break
#include "gettext.h"
#define _(msgid) gettext (msgid)
\f
-/* 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;
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;
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];
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
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
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
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
{
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
/* 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
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. */
/* 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
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;
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;
}
/* 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
#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
{
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);
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
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <string.h>
+
+#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 */
+ };
/* 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
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;
}
NULL, /* paste */
NULL, /* select */
};
+\f
+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 */
+ };
+
/* 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
/* 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]);
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
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
}
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];
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;
cs = 1;
}
- while (*text && strchr ("<>^,@", *text))
+ opt = 0;
+ while (*text && strchr ("<>^,@()|", *text))
switch (*text++)
{
case '<':
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;
}
])
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
])
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
])
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])
+-----+------+----+
])
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
\f
AT_BANNER([output rendering -- horizontal page breaks])
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
\f
AT_BANNER([output rendering -- double page breaks])