output: Add debugging code to rendering and table code.
[pspp] / src / output / cairo-fsm.c
index a9e4d4c68ddc2482a20d3891fc2bc2d7dd03db0d..51f48efb750d087d110930de8c14ec149ecf7eaf 100644 (file)
@@ -26,8 +26,7 @@
 #include "libpspp/assertion.h"
 #include "libpspp/str.h"
 #include "output/cairo-chart.h"
-#include "output/chart-item-provider.h"
-#include "output/chart-item.h"
+#include "output/chart-provider.h"
 #include "output/charts/barchart.h"
 #include "output/charts/boxplot.h"
 #include "output/charts/np-plot.h"
 #include "output/charts/scatterplot.h"
 #include "output/charts/scree.h"
 #include "output/charts/spreadlevel-plot.h"
-#include "output/group-item.h"
-#include "output/message-item.h"
-#include "output/page-eject-item.h"
-#include "output/page-setup-item.h"
+#include "output/pivot-output.h"
+#include "output/pivot-table.h"
 #include "output/render.h"
-#include "output/table-item.h"
-#include "output/text-item.h"
+#include "output/output-item.h"
 
 #include "gl/c-ctype.h"
 #include "gl/c-strcase.h"
@@ -52,7 +48,7 @@
 /* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
 #define H TABLE_HORZ
 #define V TABLE_VERT
-
+\f
 struct xr_fsm_style *
 xr_fsm_style_ref (const struct xr_fsm_style *style_)
 {
@@ -63,6 +59,23 @@ xr_fsm_style_ref (const struct xr_fsm_style *style_)
   return style;
 }
 
+struct xr_fsm_style *
+xr_fsm_style_unshare (struct xr_fsm_style *old)
+{
+  assert (old->ref_cnt > 0);
+  if (old->ref_cnt == 1)
+    return old;
+
+  xr_fsm_style_unref (old);
+
+  struct xr_fsm_style *new = xmemdup (old, sizeof *old);
+  new->ref_cnt = 1;
+  if (old->font)
+    new->font = pango_font_description_copy (old->font);
+
+  return new;
+}
+
 void
 xr_fsm_style_unref (struct xr_fsm_style *style)
 {
@@ -71,8 +84,7 @@ xr_fsm_style_unref (struct xr_fsm_style *style)
       assert (style->ref_cnt > 0);
       if (!--style->ref_cnt)
         {
-          for (size_t i = 0; i < XR_N_FONTS; i++)
-            pango_font_description_free (style->fonts[i]);
+          pango_font_description_free (style->font);
           free (style);
         }
     }
@@ -86,30 +98,42 @@ xr_fsm_style_equals (const struct xr_fsm_style *a,
       || a->size[V] != b->size[V]
       || a->min_break[H] != b->min_break[H]
       || a->min_break[V] != b->min_break[V]
+      || !pango_font_description_equal (a->font, b->font)
       || a->use_system_colors != b->use_system_colors
-      || a->transparent != b->transparent
+      || a->object_spacing != b->object_spacing
       || a->font_resolution != b->font_resolution)
     return false;
 
-  for (size_t i = 0; i < XR_N_FONTS; i++)
-    if (!pango_font_description_equal (a->fonts[i], b->fonts[i]))
-      return false;
-
   return true;
 }
 \f
+/* Renders a single output_item to an output device in one of two ways:
+
+   - 'print == true': Broken across multiple pages if necessary.
+
+   - 'print == false': In a single region that the user may scroll around if
+     needed.
+
+   Normally 'output_item' corresponds to a single rendering.  There is a
+   special case when 'print == true' and 'output_item' is a table_item with
+   multiple layers and 'item->pt->table_look->print_all_layers == true'.  In
+   that case, each layer is rendered separately from the FSM's internal point
+   of view; from the client's point of view, it is all one operation.
+*/
 struct xr_fsm
   {
     struct xr_fsm_style *style;
     struct output_item *item;
+    bool print;
+
+    /* Print mode only. */
+    bool done;
 
     /* Table items only. */
+    size_t *layer_indexes;
     struct render_params rp;
     struct render_pager *p;
     cairo_t *cairo;             /* XXX should this be here?! */
-
-    /* Chart and page-eject items only. */
-    bool done;
   };
 
 /* The unit used for internal measurements is inch/(72 * XR_POINT).
@@ -156,28 +180,33 @@ xr_layout_cell (struct xr_fsm *, const struct table_cell *,
                 int *width, int *height, int *brk);
 
 static void
-xr_set_source_rgba (cairo_t *cairo, const struct cell_color *color)
+xr_set_source_rgba (cairo_t *cairo, const struct cell_color color)
 {
   cairo_set_source_rgba (cairo,
-                         color->r / 255., color->g / 255., color->b / 255.,
-                         color->alpha / 255.);
+                         color.r / 255., color.g / 255., color.b / 255.,
+                         color.alpha / 255.);
 }
 
 static void
 xr_draw_line (struct xr_fsm *xr, int x0, int y0, int x1, int y1, int style,
-              const struct cell_color *color)
+              const struct cell_color color)
 {
   cairo_new_path (xr->cairo);
-  if (!xr->style->use_system_colors)
-    xr_set_source_rgba (xr->cairo, color);
   cairo_set_line_width (
     xr->cairo,
-    xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
-              : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
+    xr_to_pt (style == TABLE_STROKE_THICK ? XR_LINE_WIDTH * 2
+              : style == TABLE_STROKE_THIN ? XR_LINE_WIDTH / 2
               : XR_LINE_WIDTH));
   cairo_move_to (xr->cairo, xr_to_pt (x0), xr_to_pt (y0));
   cairo_line_to (xr->cairo, xr_to_pt (x1), xr_to_pt (y1));
+
+  if (!xr->style->use_system_colors)
+    xr_set_source_rgba (xr->cairo, color);
+  if (style == TABLE_STROKE_DASHED)
+    cairo_set_dash (xr->cairo, (double[]) { 2 }, 1, 0);
   cairo_stroke (xr->cairo);
+  if (style == TABLE_STROKE_DASHED)
+    cairo_set_dash (xr->cairo, NULL, 0, 0);
 }
 
 static void UNUSED
@@ -210,19 +239,19 @@ fill_rectangle (struct xr_fsm *xr, int x0, int y0, int x1, int y1)
    shortening it to X2...X3 if SHORTEN is true. */
 static void
 xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
-                   enum render_line_style left, enum render_line_style right,
-                   const struct cell_color *left_color,
-                   const struct cell_color *right_color,
+                   enum table_stroke left, enum table_stroke right,
+                   const struct cell_color left_color,
+                   const struct cell_color right_color,
                    bool shorten)
 {
-  if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
+  if (left != TABLE_STROKE_NONE && right != TABLE_STROKE_NONE && !shorten
       && cell_color_equal (left_color, right_color))
     xr_draw_line (xr, x0, y, x3, y, left, left_color);
   else
     {
-      if (left != RENDER_LINE_NONE)
+      if (left != TABLE_STROKE_NONE)
         xr_draw_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
-      if (right != RENDER_LINE_NONE)
+      if (right != TABLE_STROKE_NONE)
         xr_draw_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
     }
 }
@@ -233,43 +262,42 @@ xr_draw_horz_line (struct xr_fsm *xr, int x0, int x1, int x2, int x3, int y,
    shortening it to Y2...Y3 if SHORTEN is true. */
 static void
 xr_draw_vert_line (struct xr_fsm *xr, int y0, int y1, int y2, int y3, int x,
-                   enum render_line_style top, enum render_line_style bottom,
-                   const struct cell_color *top_color,
-                   const struct cell_color *bottom_color,
+                   enum table_stroke top, enum table_stroke bottom,
+                   const struct cell_color top_color,
+                   const struct cell_color bottom_color,
                    bool shorten)
 {
-  if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
+  if (top != TABLE_STROKE_NONE && bottom != TABLE_STROKE_NONE && !shorten
       && cell_color_equal (top_color, bottom_color))
     xr_draw_line (xr, x, y0, x, y3, top, top_color);
   else
     {
-      if (top != RENDER_LINE_NONE)
+      if (top != TABLE_STROKE_NONE)
         xr_draw_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
-      if (bottom != RENDER_LINE_NONE)
+      if (bottom != TABLE_STROKE_NONE)
         xr_draw_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
     }
 }
 
 static void
 xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
-               enum render_line_style styles[TABLE_N_AXES][2],
-               struct cell_color colors[TABLE_N_AXES][2])
+               const struct table_border_style styles[TABLE_N_AXES][2])
 {
   const int x0 = bb[H][0];
   const int y0 = bb[V][0];
   const int x3 = bb[H][1];
   const int y3 = bb[V][1];
-  const int top = styles[H][0];
-  const int bottom = styles[H][1];
+  const enum table_stroke top = styles[H][0].stroke;
+  const enum table_stroke bottom = styles[H][1].stroke;
 
   int start_side = render_direction_rtl();
   int end_side = !start_side;
-  const int start_of_line = styles[V][start_side];
-  const int end_of_line   = styles[V][end_side];
-  const struct cell_color *top_color = &colors[H][0];
-  const struct cell_color *bottom_color = &colors[H][1];
-  const struct cell_color *start_color = &colors[V][start_side];
-  const struct cell_color *end_color = &colors[V][end_side];
+  const int start_of_line = styles[V][start_side].stroke;
+  const int end_of_line   = styles[V][end_side].stroke;
+  const struct cell_color top_color = styles[H][0].color;
+  const struct cell_color bottom_color = styles[H][1].color;
+  const struct cell_color start_color = styles[V][start_side].color;
+  const struct cell_color end_color = styles[V][end_side].color;
 
   /* The algorithm here is somewhat subtle, to allow it to handle
      all the kinds of intersections that we need.
@@ -308,8 +336,8 @@ xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
   /* Are the lines along each axis single or double?
      (It doesn't make sense to have different kinds of line on the
      same axis, so we don't try to gracefully handle that case.) */
-  bool double_vert = top == RENDER_LINE_DOUBLE || bottom == RENDER_LINE_DOUBLE;
-  bool double_horz = start_of_line == RENDER_LINE_DOUBLE || end_of_line == RENDER_LINE_DOUBLE;
+  bool double_vert = top == TABLE_STROKE_DOUBLE || bottom == TABLE_STROKE_DOUBLE;
+  bool double_horz = start_of_line == TABLE_STROKE_DOUBLE || end_of_line == TABLE_STROKE_DOUBLE;
 
   /* When horizontal lines are doubled,
      the left-side line along y1 normally runs from x0 to x2,
@@ -336,16 +364,16 @@ xrr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
      single.  We actually choose to cut off the line anyhow, as
      shown in the first diagram above.
   */
-  bool shorten_y1_lines = top == RENDER_LINE_DOUBLE;
-  bool shorten_y2_lines = bottom == RENDER_LINE_DOUBLE;
+  bool shorten_y1_lines = top == TABLE_STROKE_DOUBLE;
+  bool shorten_y2_lines = bottom == TABLE_STROKE_DOUBLE;
   bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
   int horz_line_ofs = double_vert ? double_line_ofs : 0;
   int xc = (x0 + x3) / 2;
   int x1 = xc - horz_line_ofs;
   int x2 = xc + horz_line_ofs;
 
-  bool shorten_x1_lines = start_of_line == RENDER_LINE_DOUBLE;
-  bool shorten_x2_lines = end_of_line == RENDER_LINE_DOUBLE;
+  bool shorten_x1_lines = start_of_line == TABLE_STROKE_DOUBLE;
+  bool shorten_x2_lines = end_of_line == TABLE_STROKE_DOUBLE;
   bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
   int vert_line_ofs = double_horz ? double_line_ofs : 0;
   int yc = (y0 + y3) / 2;
@@ -394,31 +422,32 @@ xrr_measure_cell_width (void *xr_, const struct table_cell *cell,
   bb[H][1] = 1;
   xr_layout_cell (xr, cell, bb, clip, min_width, &h, NULL);
 
+  const int (*margin)[2] = cell->cell_style->margin;
   if (*min_width > 0)
-    *min_width += px_to_xr (cell->style->cell_style.margin[H][0]
-                            + cell->style->cell_style.margin[H][1]);
+    *min_width += px_to_xr (margin[H][0] + margin[H][1]);
   if (*max_width > 0)
-    *max_width += px_to_xr (cell->style->cell_style.margin[H][0]
-                            + cell->style->cell_style.margin[H][1]);
+    *max_width += px_to_xr (margin[H][0] + margin[H][1]);
 }
 
 static int
 xrr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
 {
   struct xr_fsm *xr = xr_;
-  int bb[TABLE_N_AXES][2];
-  int clip[TABLE_N_AXES][2];
-  int w, h;
 
-  bb[H][0] = 0;
-  bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
-                               + cell->style->cell_style.margin[H][1]);
-  bb[V][0] = 0;
-  bb[V][1] = INT_MAX;
-  clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
+  const int (*margin)[2] = cell->cell_style->margin;
+
+  int bb[TABLE_N_AXES][2] = {
+    [H][0] = 0,
+    [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
+    [V][0] = 0,
+    [V][1] = INT_MAX,
+  };
+
+  int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
+
+  int w, h;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, NULL);
-  h += px_to_xr (cell->style->cell_style.margin[V][0]
-                 + cell->style->cell_style.margin[V][1]);
+  h += px_to_xr (margin[V][0] + margin[V][1]);
   return h;
 }
 
@@ -433,7 +462,8 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
   struct xr_fsm *xr = xr_;
   int w, h, brk;
 
-  if (!xr->style->transparent)
+  const struct cell_color bg = cell->font_style->bg[color_idx];
+  if ((bg.r != 255 || bg.g != 255 || bg.b != 255) && bg.alpha)
     {
       cairo_save (xr->cairo);
       int bg_clip[TABLE_N_AXES][2];
@@ -448,7 +478,7 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
            bg_clip[axis][1] += spill[axis][1];
        }
       xr_clip (xr, bg_clip);
-      xr_set_source_rgba (xr->cairo, &cell->style->font_style.bg[color_idx]);
+      xr_set_source_rgba (xr->cairo, bg);
       fill_rectangle (xr,
                      bb[H][0] - spill[H][0],
                      bb[V][0] - spill[V][0],
@@ -458,14 +488,14 @@ xrr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
     }
   cairo_save (xr->cairo);
   if (!xr->style->use_system_colors)
-    xr_set_source_rgba (xr->cairo, &cell->style->font_style.fg[color_idx]);
+    xr_set_source_rgba (xr->cairo, cell->font_style->fg[color_idx]);
 
   bb[V][0] += valign_offset;
 
   for (int axis = 0; axis < TABLE_N_AXES; axis++)
     {
-      bb[axis][0] += px_to_xr (cell->style->cell_style.margin[axis][0]);
-      bb[axis][1] -= px_to_xr (cell->style->cell_style.margin[axis][1]);
+      bb[axis][0] += px_to_xr (cell->cell_style->margin[axis][0]);
+      bb[axis][1] -= px_to_xr (cell->cell_style->margin[axis][1]);
     }
   if (bb[H][0] < bb[H][1] && bb[V][0] < bb[V][1])
     xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
@@ -477,25 +507,34 @@ xrr_adjust_break (void *xr_, const struct table_cell *cell,
                   int width, int height)
 {
   struct xr_fsm *xr = xr_;
-  int bb[TABLE_N_AXES][2];
-  int clip[TABLE_N_AXES][2];
-  int w, h, brk;
 
   if (xrr_measure_cell_height (xr_, cell, width) < height)
     return -1;
 
-  bb[H][0] = 0;
-  bb[H][1] = width - px_to_xr (cell->style->cell_style.margin[H][0]
-                               + cell->style->cell_style.margin[H][1]);
+  const int (*margin)[2] = cell->cell_style->margin;
+
+  int bb[TABLE_N_AXES][2] = {
+    [H][0] = 0,
+    [V][0] = 0,
+    [H][1] = width - px_to_xr (margin[H][0] + margin[H][1]),
+    [V][1] = height - px_to_xr (margin[V][0] + margin[V][1]),
+  };
   if (bb[H][1] <= 0)
     return 0;
-  bb[V][0] = 0;
-  bb[V][1] = height - px_to_xr (cell->style->cell_style.margin[V][0]
-                                + cell->style->cell_style.margin[V][1]);
-  clip[H][0] = clip[H][1] = clip[V][0] = clip[V][1] = 0;
+
+  int clip[TABLE_N_AXES][2] = { { 0, 0 }, { 0, 0 } };
+
+  int w, h, brk;
   xr_layout_cell (xr, cell, bb, clip, &w, &h, &brk);
   return brk;
 }
+
+static void
+xrr_scale (void *xr_, double scale)
+{
+  struct xr_fsm *xr = xr_;
+  cairo_scale (xr->cairo, scale, scale);
+}
 \f
 static void
 xr_clip (struct xr_fsm *xr, int clip[TABLE_N_AXES][2])
@@ -522,10 +561,9 @@ add_attr (PangoAttrList *list, PangoAttribute *attr,
 }
 
 static void
-markup_escape (struct string *out, unsigned int options,
-               const char *in, size_t len)
+markup_escape (struct string *out, bool markup, const char *in, size_t len)
 {
-  if (!(options & TAB_MARKUP))
+  if (!markup)
     {
       ds_put_substring (out, ss_buffer (in, len == -1 ? strlen (in) : len));
       return;
@@ -593,17 +631,15 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                      int bb[TABLE_N_AXES][2], int clip[TABLE_N_AXES][2],
                      int *widthp, int *brk)
 {
-  const struct font_style *font_style = &cell->style->font_style;
-  const struct cell_style *cell_style = &cell->style->cell_style;
+  const struct pivot_table *pt = xr->item->table;
+  const struct font_style *font_style = cell->font_style;
+  const struct cell_style *cell_style = cell->cell_style;
   unsigned int options = cell->options;
 
-  enum table_axis X = options & TAB_ROTATE ? V : H;
+  enum table_axis X = options & TABLE_CELL_ROTATE ? V : H;
   enum table_axis Y = !X;
-  int R = options & TAB_ROTATE ? 0 : 1;
+  int R = options & TABLE_CELL_ROTATE ? 0 : 1;
 
-  enum xr_font_type font_type = (options & TAB_FIX
-                                 ? XR_FONT_FIXED
-                                 : XR_FONT_PROPORTIONAL);
   PangoFontDescription *desc = NULL;
   if (font_style->typeface)
       desc = parse_font (
@@ -611,23 +647,29 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
         font_style->size ? font_style->size * 1000 : 10000,
         font_style->bold, font_style->italic);
   if (!desc)
-    desc = xr->style->fonts[font_type];
+    desc = xr->style->font;
 
   assert (xr->cairo);
   PangoContext *context = pango_cairo_create_context (xr->cairo);
   pango_cairo_context_set_resolution (context, xr->style->font_resolution);
   PangoLayout *layout = pango_layout_new (context);
   g_object_unref (context);
+
   pango_layout_set_font_description (layout, desc);
 
-  const char *text = cell->text;
+  struct string body = DS_EMPTY_INITIALIZER;
+  bool numeric = pivot_value_format_body (cell->value, pt, &body);
+
   enum table_halign halign = table_halign_interpret (
-    cell_style->halign, cell->options & TAB_NUMERIC);
-  if (cell_style->halign == TABLE_HALIGN_DECIMAL && !(options & TAB_ROTATE))
+    cell->cell_style->halign, numeric);
+
+  if (cell_style->halign == TABLE_HALIGN_DECIMAL
+      && !(cell->options & TABLE_CELL_ROTATE))
     {
       int margin_adjustment = -px_to_xr (cell_style->decimal_offset);
 
-      const char *decimal = strrchr (text, cell_style->decimal_char);
+      const char *decimal = strrchr (ds_cstr (&body),
+                                     cell_style->decimal_char);
       if (decimal)
         {
           pango_layout_set_text (layout, decimal, strlen (decimal));
@@ -639,7 +681,6 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
         bb[H][1] += margin_adjustment;
     }
 
-  struct string tmp = DS_EMPTY_INITIALIZER;
   PangoAttrList *attrs = NULL;
 
   /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
@@ -656,33 +697,39 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
      happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
      are present then there will always be a digit on both sides of every
      period and comma. */
-  if (options & TAB_MARKUP)
+  bool markup = cell->font_style->markup;
+  if (markup)
     {
       PangoAttrList *new_attrs;
       char *new_text;
-      if (pango_parse_markup (text, -1, 0, &new_attrs, &new_text, NULL, NULL))
+      if (pango_parse_markup (ds_cstr (&body), -1, 0,
+                              &new_attrs, &new_text, NULL, NULL))
         {
           attrs = new_attrs;
-          tmp.ss = ss_cstr (new_text);
-          tmp.capacity = tmp.ss.length;
+          ds_destroy (&body);
+          body.ss = ss_cstr (new_text);
+          body.capacity = body.ss.length;
         }
       else
         {
           /* XXX should we report the error? */
-          ds_put_cstr (&tmp, text);
         }
     }
-  else if (options & TAB_ROTATE || bb[H][1] != INT_MAX)
+  else if (options & TABLE_CELL_ROTATE || bb[H][1] != INT_MAX)
     {
+      const char *text = ds_cstr (&body);
       const char *decimal = text + strcspn (text, ".,");
       if (decimal[0]
           && c_isdigit (decimal[1])
           && (decimal == text || !c_isdigit (decimal[-1])))
         {
-          ds_extend (&tmp, strlen (text) + 16);
-          markup_escape (&tmp, options, text, decimal - text + 1);
+          struct string tmp = DS_EMPTY_INITIALIZER;
+          ds_extend (&tmp, ds_length (&body) + 16);
+          markup_escape (&tmp, markup, text, decimal - text + 1);
           ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
-          markup_escape (&tmp, options, decimal + 1, -1);
+          markup_escape (&tmp, markup, decimal + 1, -1);
+          ds_swap (&tmp, &body);
+          ds_destroy (&tmp);
         }
     }
 
@@ -694,43 +741,39 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                                 PANGO_UNDERLINE_SINGLE));
     }
 
-  if (cell->n_footnotes || cell->n_subscripts || cell->superscript)
+  const struct pivot_value_ex *ex = pivot_value_ex (cell->value);
+  if (ex->n_footnotes || ex->n_subscripts)
     {
-      /* If we haven't already put TEXT into tmp, do it now. */
-      if (ds_is_empty (&tmp))
-        {
-          ds_extend (&tmp, strlen (text) + 16);
-          markup_escape (&tmp, options, text, -1);
-        }
-
-      size_t subscript_ofs = ds_length (&tmp);
-      for (size_t i = 0; i < cell->n_subscripts; i++)
+      size_t subscript_ofs = ds_length (&body);
+      for (size_t i = 0; i < ex->n_subscripts; i++)
         {
           if (i)
-            ds_put_byte (&tmp, ',');
-          ds_put_cstr (&tmp, cell->subscripts[i]);
+            ds_put_byte (&body, ',');
+          ds_put_cstr (&body, ex->subscripts[i]);
         }
 
-      size_t superscript_ofs = ds_length (&tmp);
-      if (cell->superscript)
-        ds_put_cstr (&tmp, cell->superscript);
-
-      size_t footnote_ofs = ds_length (&tmp);
-      for (size_t i = 0; i < cell->n_footnotes; i++)
+      size_t footnote_ofs = ds_length (&body);
+      size_t n_footnotes = 0;
+      for (size_t i = 0; i < ex->n_footnotes; i++)
         {
-          if (i)
-            ds_put_byte (&tmp, ',');
-          ds_put_cstr (&tmp, cell->footnotes[i]->marker);
+          const struct pivot_footnote *f
+            = pt->footnotes[ex->footnote_indexes[i]];
+          if (f->show)
+            {
+              if (n_footnotes++)
+                ds_put_byte (&body, ',');
+              pivot_footnote_format_marker (f, pt, &body);
+            }
         }
 
       /* Allow footnote markers to occupy the right margin.  That way, numbers
          in the column are still aligned. */
-      if (cell->n_footnotes && halign == TABLE_HALIGN_RIGHT)
+      if (ex->n_footnotes && halign == TABLE_HALIGN_RIGHT)
         {
           /* Measure the width of the footnote marker, so we know how much we
              need to make room for. */
-          pango_layout_set_text (layout, ds_cstr (&tmp) + footnote_ofs,
-                                 ds_length (&tmp) - footnote_ofs);
+          pango_layout_set_text (layout, ds_cstr (&body) + footnote_ofs,
+                                 ds_length (&body) - footnote_ofs);
 
           PangoAttrList *fn_attrs = pango_attr_list_new ();
           pango_attr_list_insert (
@@ -745,7 +788,7 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
           int footnote_adjustment = MIN (footnote_width, right_margin);
 
           /* Adjust the bounding box. */
-          if (options & TAB_ROTATE)
+          if (options & TABLE_CELL_ROTATE)
             footnote_adjustment = -footnote_adjustment;
           bb[X][R] += footnote_adjustment;
 
@@ -760,12 +803,15 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
                 PANGO_ATTR_INDEX_TO_TEXT_END);
       add_attr (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL),
                 subscript_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
-      if (cell->n_subscripts)
+      if (ex->n_subscripts)
         add_attr (attrs, pango_attr_rise_new (-3000), subscript_ofs,
-                  superscript_ofs - subscript_ofs);
-      if (cell->superscript || cell->n_footnotes)
-        add_attr (attrs, pango_attr_rise_new (3000), superscript_ofs,
-                  PANGO_ATTR_INDEX_TO_TEXT_END);
+                  footnote_ofs - subscript_ofs);
+      if (ex->n_footnotes)
+        {
+          bool superscript = pt->look->footnote_marker_superscripts;
+          add_attr (attrs, pango_attr_rise_new (superscript ? 3000 : -3000),
+                    footnote_ofs, PANGO_ATTR_INDEX_TO_TEXT_END);
+        }
     }
 
   /* Set the attributes, if any. */
@@ -776,11 +822,7 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
     }
 
   /* Set the text. */
-  if (ds_is_empty (&tmp))
-    pango_layout_set_text (layout, text, -1);
-  else
-    pango_layout_set_text (layout, ds_cstr (&tmp), ds_length (&tmp));
-  ds_destroy (&tmp);
+  pango_layout_set_text (layout, ds_cstr (&body), ds_length (&body));
 
   pango_layout_set_alignment (layout,
                               (halign == TABLE_HALIGN_RIGHT ? PANGO_ALIGN_RIGHT
@@ -791,15 +833,20 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
     bb[X][1] == INT_MAX ? -1 : xr_to_pango (bb[X][1] - bb[X][0]));
   pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
 
+  int size[TABLE_N_AXES];
+  pango_layout_get_size (layout, &size[H], &size[V]);
+
   if (clip[H][0] != clip[H][1])
     {
       cairo_save (xr->cairo);
-      if (!(options & TAB_ROTATE))
+      if (!(options & TABLE_CELL_ROTATE))
         xr_clip (xr, clip);
-      if (options & TAB_ROTATE)
+      if (options & TABLE_CELL_ROTATE)
         {
+          int extra = bb[H][1] - bb[H][0] - size[V];
+          int halign_offset = extra > 0 ? extra / 2 : 0;
           cairo_translate (xr->cairo,
-                           xr_to_pt (bb[H][0]),
+                           xr_to_pt (bb[H][0] + halign_offset),
                            xr_to_pt (bb[V][1]));
           cairo_rotate (xr->cairo, -M_PI_2);
         }
@@ -838,13 +885,11 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
       cairo_restore (xr->cairo);
     }
 
-  int size[TABLE_N_AXES];
-  pango_layout_get_size (layout, &size[H], &size[V]);
   int w = pango_to_xr (size[X]);
   int h = pango_to_xr (size[Y]);
   if (w > *widthp)
     *widthp = w;
-  if (bb[V][0] + h >= bb[V][1] && !(options & TAB_ROTATE))
+  if (bb[V][0] + h >= bb[V][1] && !(options & TABLE_CELL_ROTATE))
     {
       PangoLayoutIter *iter;
       int best = 0;
@@ -884,16 +929,17 @@ xr_layout_cell_text (struct xr_fsm *xr, const struct table_cell *cell,
           if (best)
             xr_draw_line (xr, 0, best,
                           xr->style->size[H], best,
-                          RENDER_LINE_SINGLE,
-                          &(struct cell_color) CELL_COLOR (0, 255, 0));
+                          TABLE_STROKE_SOLID,
+                          (struct cell_color) CELL_COLOR (0, 255, 0));
         }
     }
 
   pango_layout_set_attributes (layout, NULL);
 
-  if (desc != xr->style->fonts[font_type])
+  if (desc != xr->style->font)
     pango_font_description_free (desc);
   g_object_unref (G_OBJECT (layout));
+  ds_destroy (&body);
 
   return h;
 }
@@ -923,152 +969,57 @@ xr_layout_cell (struct xr_fsm *xr, const struct table_cell *cell,
     *brk = bb[V][0];
   *height = xr_layout_cell_text (xr, cell, bb, clip, width, brk);
 }
-\f
-#if 0
-static bool
-xr_table_render (struct xr_render_fsm *fsm, struct xr_fsm *xr)
-{
-  struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
-
-  while (render_pager_has_next (ts->p))
-    {
-      int used;
-
-      used = render_pager_draw_next (ts->p, xr->length);
-      if (!used)
-        {
-          assert (xr->y > 0);
-          return true;
-        }
-      else
-        xr->y += used;
-    }
-  return false;
-}
-
-static void
-xr_table_destroy (struct xr_render_fsm *fsm)
-{
-  struct xr_table_state *ts = UP_CAST (fsm, struct xr_table_state, fsm);
-
-  render_pager_destroy (ts->p);
-  free (ts);
-}
-
-static struct xr_render_fsm *
-xr_render_table (struct xr_fsm *xr, struct table_item *table_item)
-{
-  struct xr_table_state *ts;
-
-  ts = xmalloc (sizeof *ts);
-  ts->fsm.render = xr_table_render;
-  ts->fsm.destroy = xr_table_destroy;
-
-  if (xr->y > 0)
-    xr->y += xr->char_height;
-
-  ts->p = render_pager_create (xr->params, table_item);
-  table_item_unref (table_item);
-
-  return &ts->fsm;
-}
-\f
-static bool
-xr_eject_render (struct xr_render_fsm *fsm UNUSED, struct xr_fsm *xr)
-{
-  return xr->y > 0;
-}
-
-static void
-xr_eject_destroy (struct xr_render_fsm *fsm UNUSED)
-{
-  /* Nothing to do. */
-}
-
-static struct xr_render_fsm *
-xr_render_eject (void)
-{
-  static struct xr_render_fsm eject_renderer =
-    {
-      xr_eject_render,
-      xr_eject_destroy
-    };
 
-  return &eject_renderer;
-}
-\f
 #define CHART_WIDTH 500
 #define CHART_HEIGHT 375
 
-static struct xr_render_fsm *
-xr_render_text (struct xr_fsm *xr, const struct text_item *text_item)
+static struct xr_fsm *
+xr_fsm_create (const struct output_item *item_,
+               const struct xr_fsm_style *style, cairo_t *cr,
+               bool print)
 {
-  enum text_item_type type = text_item_get_type (text_item);
+  struct output_item *item;
 
-  switch (type)
+  switch (item_->type)
     {
-    case TEXT_ITEM_PAGE_TITLE:
+    case OUTPUT_ITEM_CHART:
+    case OUTPUT_ITEM_IMAGE:
+    case OUTPUT_ITEM_PAGE_BREAK:
+    case OUTPUT_ITEM_TABLE:
+      item = output_item_ref (item_);
       break;
 
-    case TEXT_ITEM_EJECT_PAGE:
-      if (xr->y > 0)
-        return xr_render_eject ();
+    case OUTPUT_ITEM_GROUP:
+      return NULL;
+
+    case OUTPUT_ITEM_MESSAGE:
+      item = text_item_to_table_item (message_item_to_text_item (
+                                        output_item_ref (item_)));
+      break;
+
+    case OUTPUT_ITEM_TEXT:
+      if (item_->text.subtype == TEXT_ITEM_PAGE_TITLE)
+        return NULL;
+
+      item = text_item_to_table_item (output_item_ref (item_));
       break;
 
     default:
-      return xr_render_table (
-        xr, text_item_to_table_item (text_item_ref (text_item)));
+      NOT_REACHED ();
     }
 
-  return NULL;
-}
-#endif
-
-#define CHART_WIDTH 500
-#define CHART_HEIGHT 375
+  assert (item->type == OUTPUT_ITEM_TABLE
+          || item->type == OUTPUT_ITEM_CHART
+          || item->type == OUTPUT_ITEM_IMAGE
+          || item->type == OUTPUT_ITEM_PAGE_BREAK);
 
-struct xr_fsm *
-xr_fsm_create (const struct output_item *item_,
-               const struct xr_fsm_style *style, cairo_t *cr)
-{
-  if (is_page_setup_item (item_)
-      || is_group_open_item (item_)
-      || is_group_close_item (item_))
-    return NULL;
-
-  struct output_item *item;
-  if (is_table_item (item_)
-      || is_chart_item (item_)
-      || is_page_eject_item (item_))
-    item = output_item_ref (item_);
-  else if (is_message_item (item_))
-    item = table_item_super (
-      text_item_to_table_item (
-        message_item_to_text_item (
-          to_message_item (
-            output_item_ref (item_)))));
-  else if (is_text_item (item_))
+  size_t *layer_indexes = NULL;
+  if (item->type == OUTPUT_ITEM_TABLE)
     {
-      if (to_text_item (item_)->type == TEXT_ITEM_PAGE_TITLE)
+      layer_indexes = pivot_output_next_layer (item->table, NULL, print);
+      if (!layer_indexes)
         return NULL;
-
-      item = table_item_super (
-        text_item_to_table_item (
-          to_text_item (
-            output_item_ref (item_))));
-    }
-  else if (is_group_open_item (item_))
-    {
-      item = table_item_super (
-        text_item_to_table_item (
-          text_item_create (TEXT_ITEM_TITLE,
-                            to_group_open_item (item_)->command_name)));
     }
-  else
-    NOT_REACHED ();
-  assert (is_table_item (item)
-          || is_chart_item (item)
-          || is_page_eject_item (item));
 
   static const struct render_ops xrr_render_ops = {
     .measure_cell_width = xrr_measure_cell_width,
@@ -1076,66 +1027,75 @@ xr_fsm_create (const struct output_item *item_,
     .adjust_break = xrr_adjust_break,
     .draw_line = xrr_draw_line,
     .draw_cell = xrr_draw_cell,
+    .scale = xrr_scale,
   };
 
   enum { LW = XR_LINE_WIDTH, LS = XR_LINE_SPACE };
-  static const int xr_line_widths[RENDER_N_LINES] =
+  static const int xr_line_widths[TABLE_N_STROKES] =
     {
-      [RENDER_LINE_NONE] = 0,
-      [RENDER_LINE_SINGLE] = LW,
-      [RENDER_LINE_DASHED] = LW,
-      [RENDER_LINE_THICK] = LW * 2,
-      [RENDER_LINE_THIN] = LW / 2,
-      [RENDER_LINE_DOUBLE] = 2 * LW + LS,
+      [TABLE_STROKE_NONE] = 0,
+      [TABLE_STROKE_SOLID] = LW,
+      [TABLE_STROKE_DASHED] = LW,
+      [TABLE_STROKE_THICK] = LW * 2,
+      [TABLE_STROKE_THIN] = LW / 2,
+      [TABLE_STROKE_DOUBLE] = 2 * LW + LS,
     };
 
   struct xr_fsm *fsm = xmalloc (sizeof *fsm);
   *fsm = (struct xr_fsm) {
     .style = xr_fsm_style_ref (style),
     .item = item,
+    .print = print,
+    .layer_indexes = layer_indexes,
     .rp = {
       .ops = &xrr_render_ops,
       .aux = fsm,
       .size = { [H] = style->size[H], [V] = style->size[V] },
-      /* XXX font_size */
       .line_widths = xr_line_widths,
       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
       .supports_margins = true,
       .rtl = render_direction_rtl (),
+      .printing = print,
     }
   };
 
-  if (is_table_item (item))
-    {
-      fsm->cairo = cr;
-      fsm->p = render_pager_create (&fsm->rp, to_table_item (item));
-      fsm->cairo = NULL;
-    }
+  /* Get font size. */
+  PangoContext *context = pango_cairo_create_context (cr);
+  pango_cairo_context_set_resolution (context, style->font_resolution);
+  PangoLayout *layout = pango_layout_new (context);
+  g_object_unref (context);
 
-  for (int i = 0; i < XR_N_FONTS; i++)
-    {
-      PangoContext *context = pango_cairo_create_context (cr);
-      pango_cairo_context_set_resolution (context, style->font_resolution);
-      PangoLayout *layout = pango_layout_new (context);
-      g_object_unref (context);
-      pango_layout_set_font_description (layout, style->fonts[i]);
+  pango_layout_set_font_description (layout, style->font);
 
-      pango_layout_set_text (layout, "0", 1);
+  pango_layout_set_text (layout, "0", 1);
 
-      int char_size[TABLE_N_AXES];
-      pango_layout_get_size (layout, &char_size[H], &char_size[V]);
-      for (int j = 0; j < TABLE_N_AXES; j++)
-        {
-          int csj = pango_to_xr (char_size[j]);
-          fsm->rp.font_size[j] = MAX (fsm->rp.font_size[j], csj);
-        }
+  int char_size[TABLE_N_AXES];
+  pango_layout_get_size (layout, &char_size[H], &char_size[V]);
+  for (int a = 0; a < TABLE_N_AXES; a++)
+    {
+      int csa = pango_to_xr (char_size[a]);
+      fsm->rp.font_size[a] = MAX (fsm->rp.font_size[a], csa);
+    }
+
+  g_object_unref (G_OBJECT (layout));
 
-      g_object_unref (G_OBJECT (layout));
+  if (item->type == OUTPUT_ITEM_TABLE)
+    {
+      fsm->cairo = cr;
+      fsm->p = render_pager_create (&fsm->rp, item->table, fsm->layer_indexes);
+      fsm->cairo = NULL;
     }
 
   return fsm;
 }
 
+struct xr_fsm *
+xr_fsm_create_for_printing (const struct output_item *item,
+                            const struct xr_fsm_style *style, cairo_t *cr)
+{
+  return xr_fsm_create (item, style, cr, true);
+}
+
 void
 xr_fsm_destroy (struct xr_fsm *fsm)
 {
@@ -1143,34 +1103,55 @@ xr_fsm_destroy (struct xr_fsm *fsm)
     {
       xr_fsm_style_unref (fsm->style);
       output_item_unref (fsm->item);
+      free (fsm->layer_indexes);
       render_pager_destroy (fsm->p);
       assert (!fsm->cairo);
       free (fsm);
     }
 }
+\f
+/* Scrolling API. */
 
+struct xr_fsm *
+xr_fsm_create_for_scrolling (const struct output_item *item,
+                             const struct xr_fsm_style *style, cairo_t *cr)
+{
+  return xr_fsm_create (item, style, cr, false);
+}
 
-/* This is primarily meant for use with screen rendering since the result is a
-   fixed value for charts. */
 void
 xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
 {
+  assert (!fsm->print);
+
   int w, h;
 
-  if (is_table_item (fsm->item))
+  switch (fsm->item->type)
     {
+    case OUTPUT_ITEM_CHART:
+      w = CHART_WIDTH;
+      h = CHART_HEIGHT;
+      break;
+
+    case OUTPUT_ITEM_IMAGE:
+      w = cairo_image_surface_get_width (fsm->item->image);
+      h = cairo_image_surface_get_height (fsm->item->image);
+      break;
+
+    case OUTPUT_ITEM_TABLE:
       fsm->cairo = cr;
       w = render_pager_get_size (fsm->p, H) / XR_POINT;
       h = render_pager_get_size (fsm->p, V) / XR_POINT;
       fsm->cairo = NULL;
+      break;
+
+    case OUTPUT_ITEM_GROUP:
+    case OUTPUT_ITEM_MESSAGE:
+    case OUTPUT_ITEM_PAGE_BREAK:
+    case OUTPUT_ITEM_TEXT:
+    default:
+      NOT_REACHED ();
     }
-  else if (is_chart_item (fsm->item))
-    {
-      w = CHART_WIDTH;
-      h = CHART_HEIGHT;
-    }
-  else
-    NOT_REACHED ();
 
   if (wp)
     *wp = w;
@@ -1178,20 +1159,91 @@ xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
     *hp = h;
 }
 
+void
+xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
+{
+  assert (!fsm->print);
+  xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
+}
+
+static int
+mul_XR_POINT (int x)
+{
+  return (x >= INT_MAX / XR_POINT ? INT_MAX
+          : x <= INT_MIN / XR_POINT ? INT_MIN
+          : x * XR_POINT);
+}
+
+static void
+draw_image (cairo_surface_t *image, cairo_t *cr)
+{
+  cairo_save (cr);
+  cairo_set_source_surface (cr, image, 0, 0);
+  cairo_rectangle (cr, 0, 0, cairo_image_surface_get_width (image),
+                   cairo_image_surface_get_height (image));
+  cairo_clip (cr);
+  cairo_paint (cr);
+  cairo_restore (cr);
+}
+
+void
+xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
+                    int x, int y, int w, int h)
+{
+  assert (!fsm->print);
+  switch (fsm->item->type)
+    {
+    case OUTPUT_ITEM_CHART:
+      xr_draw_chart (fsm->item->chart, cr, CHART_WIDTH, CHART_HEIGHT);
+      break;
+
+    case OUTPUT_ITEM_IMAGE:
+      draw_image (fsm->item->image, cr);
+      break;
+
+    case OUTPUT_ITEM_TABLE:
+      fsm->cairo = cr;
+      render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
+                                mul_XR_POINT (w), mul_XR_POINT (h));
+      fsm->cairo = NULL;
+      break;
+
+    case OUTPUT_ITEM_GROUP:
+    case OUTPUT_ITEM_MESSAGE:
+    case OUTPUT_ITEM_PAGE_BREAK:
+    case OUTPUT_ITEM_TEXT:
+      NOT_REACHED ();
+    }
+}
+\f
+/* Printing API. */
+
 static int
 xr_fsm_draw_table (struct xr_fsm *fsm, int space)
 {
-  int used = 0;
-  while (render_pager_has_next (fsm->p))
+  int used = render_pager_draw_next (fsm->p, space);
+  if (!render_pager_has_next (fsm->p))
     {
-      int chunk = render_pager_draw_next (fsm->p, space - used);
-      if (!chunk)
-        return used;
+      render_pager_destroy (fsm->p);
 
-      used += chunk;
-      cairo_translate (fsm->cairo, 0, chunk);
+      fsm->layer_indexes = pivot_output_next_layer (fsm->item->table,
+                                                    fsm->layer_indexes, true);
+      if (fsm->layer_indexes)
+        {
+          fsm->p = render_pager_create (&fsm->rp, fsm->item->table,
+                                        fsm->layer_indexes);
+          if (fsm->item->table->look->paginate_layers)
+            used = space;
+          else
+            used += fsm->style->object_spacing;
+        }
+      else
+        {
+          fsm->p = NULL;
+          fsm->done = true;
+        }
     }
-  return used;
+  return MIN (used, space);
 }
 
 static int
@@ -1202,77 +1254,107 @@ xr_fsm_draw_chart (struct xr_fsm *fsm, int space)
     return 0;
 
   fsm->done = true;
-  xr_draw_chart (to_chart_item (fsm->item), fsm->cairo,
+  xr_draw_chart (fsm->item->chart, fsm->cairo,
                  xr_to_pt (fsm->rp.size[H]), xr_to_pt (chart_height));
   return chart_height;
 }
 
 static int
-xr_fsm_draw_eject (struct xr_fsm *fsm, int space)
-{
-  if (space >= fsm->rp.size[V])
-    fsm->done = true;
-  return 0;
-}
-
-void
-xr_fsm_draw_all (struct xr_fsm *fsm, cairo_t *cr)
-{
-  xr_fsm_draw_region (fsm, cr, 0, 0, INT_MAX, INT_MAX);
-}
-
-static int
-mul_XR_POINT (int x)
+xr_fsm_draw_image (struct xr_fsm *fsm, int space)
 {
-  return (x >= INT_MAX / XR_POINT ? INT_MAX
-          : x <= INT_MIN / XR_POINT ? INT_MIN
-          : x * XR_POINT);
-}
+  cairo_surface_t *image = fsm->item->image;
+  int width = cairo_image_surface_get_width (image) * XR_POINT;
+  int height = cairo_image_surface_get_height (image) * XR_POINT;
+  if (!width || !height)
+    goto error;
 
-void
-xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
-                    int x, int y, int w, int h)
-{
-  if (is_table_item (fsm->item))
+  if (height > fsm->rp.size[V])
     {
-      fsm->cairo = cr;
-      render_pager_draw_region (fsm->p, mul_XR_POINT (x), mul_XR_POINT (y),
-                                mul_XR_POINT (w), mul_XR_POINT (h));
-      fsm->cairo = NULL;
+      double scale = fsm->rp.size[V] / (double) height;
+      width *= scale;
+      height *= scale;
+      if (!width || !height)
+        goto error;
+
+      cairo_scale (fsm->cairo, scale, scale);
     }
-  else if (is_chart_item (fsm->item))
-    xr_draw_chart (to_chart_item (fsm->item), cr, CHART_WIDTH, CHART_HEIGHT);
-  else if (is_page_eject_item (fsm->item))
+
+  if (width > fsm->rp.size[H])
     {
-      /* Nothing to do. */
+      double scale = fsm->rp.size[H] / (double) width;
+      width *= scale;
+      height *= scale;
+      if (!width || !height)
+        goto error;
+
+      cairo_scale (fsm->cairo, scale, scale);
     }
-  else
-    NOT_REACHED ();
+
+  if (space < height)
+    return 0;
+
+  draw_image (image, fsm->cairo);
+  fsm->done = true;
+  return height;
+
+error:
+  fsm->done = true;
+  return 0;
+}
+
+static int
+xr_fsm_draw_page_break (struct xr_fsm *fsm, int space)
+{
+  if (space >= fsm->rp.size[V])
+    fsm->done = true;
+  return 0;
 }
 
 int
 xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
 {
-  if (xr_fsm_is_empty (fsm))
+  assert (fsm->print);
+
+  if (fsm->done || space <= 0)
     return 0;
 
   cairo_save (cr);
   fsm->cairo = cr;
-  int used = (is_table_item (fsm->item) ? xr_fsm_draw_table (fsm, space)
-              : is_chart_item (fsm->item) ? xr_fsm_draw_chart (fsm, space)
-              : is_page_eject_item (fsm->item) ? xr_fsm_draw_eject (fsm, space)
-              : (abort (), 0));
+  int used;
+  switch (fsm->item->type)
+    {
+    case OUTPUT_ITEM_CHART:
+      used = xr_fsm_draw_chart (fsm, space);
+      break;
+
+    case OUTPUT_ITEM_IMAGE:
+      used = xr_fsm_draw_image (fsm, space);
+      break;
+
+    case OUTPUT_ITEM_PAGE_BREAK:
+      used = xr_fsm_draw_page_break (fsm, space);
+      break;
+
+    case OUTPUT_ITEM_TABLE:
+      used = xr_fsm_draw_table (fsm, space);
+      break;
+
+    case OUTPUT_ITEM_GROUP:
+    case OUTPUT_ITEM_MESSAGE:
+    case OUTPUT_ITEM_TEXT:
+    default:
+      NOT_REACHED ();
+    }
   fsm->cairo = NULL;
   cairo_restore (cr);
 
   return used;
 }
 
-
 bool
 xr_fsm_is_empty (const struct xr_fsm *fsm)
 {
-  return (is_table_item (fsm->item)
-          ? !render_pager_has_next (fsm->p)
-          : fsm->done);
+  assert (fsm->print);
+
+  return fsm->done;
 }