render: Introduce render_pager to factor out code from drivers.
[pspp] / src / output / render.c
index 094e68799cf93b7e72be846075f8e04da80ec499..e1cec909f8b6ad1387d530e30f2270e79f12a5fe 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2009, 2010, 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
@@ -38,7 +38,7 @@
 
    May represent the layout of an entire table presented to
    render_page_create(), or a rectangular subregion of a table broken out using
-   render_page_next() to allow a table to be broken across multiple pages. */
+   render_break_next() to allow a table to be broken across multiple pages. */
 struct render_page
   {
     const struct render_params *params; /* Parameters of the target device. */
@@ -61,7 +61,7 @@ struct render_page
        Similarly, cp[V] represents y positions within the table.
        cp[V][0] = 0.
        cp[V][1] = the height of the topmost horizontal rule.
-       cp[V][2] = cp[V][1] + the height of the topmost column.
+       cp[V][2] = cp[V][1] + the height of the topmost row.
        cp[V][3] = cp[V][2] + the height of the second-from-top horizontal rule.
        and so on:
        cp[V][2 * nr] = y position of the bottommost horizontal rule.
@@ -178,6 +178,21 @@ cell_width (const struct render_page *page, int axis, int x)
   return axis_width (page, axis, cell_ofs (x), cell_ofs (x) + 1);
 }
 
+/* Returns the width of rule X along AXIS in PAGE. */
+static int
+rule_width (const struct render_page *page, int axis, int x)
+{
+  return axis_width (page, axis, rule_ofs (x), rule_ofs (x) + 1);
+}
+
+/* Returns the width of rule X along AXIS in PAGE. */
+static int
+rule_width_r (const struct render_page *page, int axis, int x)
+{
+  int ofs = rule_ofs_r (page, axis, x);
+  return axis_width (page, axis, ofs, ofs + 1);
+}
+
 /* Returns the width of cells X0 through X1, exclusive, along AXIS in PAGE. */
 static int
 joined_width (const struct render_page *page, int axis, int x0, int x1)
@@ -359,9 +374,14 @@ distribute_spanned_width (int width,
      w1 by the common denominator of all three calculations (d), dividing that
      out in the column width calculation, and then keeping the remainder for
      the next iteration.
+
+     (We actually compute the unspanned width of a column as twice the
+     unspanned width, plus the width of the rule on the left, plus the width of
+     the rule on the right.  That way each rule contributes to both the cell on
+     its left and on its right.)
   */
   d0 = n;
-  d1 = total_unspanned * 2.0;
+  d1 = 2.0 * (total_unspanned > 0 ? total_unspanned : 1.0);
   d = d0 * d1;
   if (total_unspanned > 0)
     d *= 2.0;
@@ -379,7 +399,7 @@ distribute_spanned_width (int width,
           w += width * unspanned * d0;
         }
 
-      rows[x].width = w / d;
+      rows[x].width = MAX (rows[x].width, w / d);
       w -= rows[x].width * d;
     }
 }
@@ -450,7 +470,7 @@ measure_rule (const struct render_params *params, const struct table *table,
   enum table_axis b = !a;
   unsigned int rules;
   int d[TABLE_N_AXES];
-  int width, i;
+  int width;
 
   /* Determine all types of rules that are present, as a bitmap in 'rules'
      where rule type 't' is present if bit 2**t is set. */
@@ -461,10 +481,11 @@ measure_rule (const struct render_params *params, const struct table *table,
 
   /* Calculate maximum width of the rules that are present. */
   width = 0;
-  for (i = 0; i < N_LINES; i++)
-    if (rules & (1u << i))
-      width = MAX (width, params->line_widths[a][rule_to_render_type (i)]);
-
+  if (rules & (1u << TAL_1)
+      || (z > 0 && z < table->n[a] && rules & (1u << TAL_GAP)))
+    width = params->line_widths[a][RENDER_LINE_SINGLE];
+  if (rules & (1u << TAL_2))
+    width = MAX (width, params->line_widths[a][RENDER_LINE_DOUBLE]);
   return width;
 }
 
@@ -806,10 +827,27 @@ render_page_get_size (const struct render_page *page, enum table_axis axis)
 {
   return page->cp[axis][page->n[axis] * 2 + 1];
 }
+
+int
+render_page_get_best_breakpoint (const struct render_page *page, int height)
+{
+  int y;
+
+  /* If there's no room for at least the top row and the rules above and below
+     it, don't include any of the table. */
+  if (page->cp[V][3] > height)
+    return 0;
+
+  /* Otherwise include as many rows and rules as we can. */
+  for (y = 5; y <= 2 * page->n[V] + 1; y += 2)
+    if (page->cp[V][y] > height)
+      return page->cp[V][y - 2];
+  return height;
+}
 \f
 /* Drawing render_pages. */
 
-static enum render_line_style
+static inline enum render_line_style
 get_rule (const struct render_page *page, enum table_axis axis,
           const int d[TABLE_N_AXES])
 {
@@ -893,13 +931,13 @@ render_cell (const struct render_page *page, const struct table_cell *cell)
           if (of->overflow[axis][0])
             {
               bb[axis][0] -= of->overflow[axis][0];
-              if (cell->d[axis][0] == 0)
+              if (cell->d[axis][0] == 0 && !page->is_edge_cutoff[axis][0])
                 clip[axis][0] = page->cp[axis][cell->d[axis][0] * 2];
             }
           if (of->overflow[axis][1])
             {
               bb[axis][1] += of->overflow[axis][1];
-              if (cell->d[axis][1] == page->n[axis])
+              if (cell->d[axis][1] == page->n[axis] && !page->is_edge_cutoff[axis][1])
                 clip[axis][1] = page->cp[axis][cell->d[axis][1] * 2 + 1];
             }
         }
@@ -930,7 +968,7 @@ render_page_draw_cells (const struct render_page *page,
           struct table_cell cell;
 
           table_get_cell (page->table, x / 2, y / 2, &cell);
-          if (y == bb[V][0] || y / 2 == cell.d[V][0])
+          if (y / 2 == bb[V][0] / 2 || y / 2 == cell.d[V][0])
             render_cell (page, &cell);
           x = rule_ofs (cell.d[H][1]);
           table_cell_free (&cell);
@@ -977,7 +1015,7 @@ get_clip_min_extent (int x0, const int cp[], int n)
   return best;
 }
 
-/* Returns the least value i, 0 <= i < n, such that cp[i + 1] >= x1. */
+/* Returns the least value i, 0 <= i < n, such that cp[i] >= x1. */
 static int
 get_clip_max_extent (int x1, const int cp[], int n)
 {
@@ -996,6 +1034,9 @@ get_clip_max_extent (int x1, const int cp[], int n)
         low = middle + 1;
     }
 
+  while (best > 0 && cp[best - 1] == cp[best])
+    best--;
+
   return best;
 }
 
@@ -1018,6 +1059,16 @@ render_page_draw_region (const struct render_page *page,
 \f
 /* Breaking up tables to fit on a page. */
 
+/* An iterator for breaking render_pages into smaller chunks. */
+struct render_break
+  {
+    struct render_page *page;   /* Page being broken up. */
+    enum table_axis axis;       /* Axis along which 'page' is being broken. */
+    int z;                      /* Next cell along 'axis'. */
+    int pixel;                  /* Pixel offset within cell 'z' (usually 0). */
+    int hw;                     /* Width of headers of 'page' along 'axis'. */
+  };
+
 static int needed_size (const struct render_break *, int cell);
 static bool cell_is_breakable (const struct render_break *, int cell);
 static struct render_page *render_page_select (const struct render_page *,
@@ -1029,31 +1080,31 @@ static struct render_page *render_page_select (const struct render_page *,
 
    Ownership of PAGE is transferred to B.  The caller must use
    render_page_ref() if it needs to keep a copy of PAGE. */
-void
+static void
 render_break_init (struct render_break *b, struct render_page *page,
                    enum table_axis axis)
 {
   b->page = page;
   b->axis = axis;
-  b->cell = page->h[axis][0];
+  b->z = page->h[axis][0];
   b->pixel = 0;
   b->hw = headers_width (page, axis);
 }
 
 /* Initializes B as a render_break structure for which
    render_break_has_next() always returns false. */
-void
+static void
 render_break_init_empty (struct render_break *b)
 {
   b->page = NULL;
   b->axis = TABLE_HORZ;
-  b->cell = 0;
+  b->z = 0;
   b->pixel = 0;
   b->hw = 0;
 }
 
 /* Frees B and unrefs the render_page that it owns. */
-void
+static void
 render_break_destroy (struct render_break *b)
 {
   if (b != NULL)
@@ -1065,26 +1116,13 @@ render_break_destroy (struct render_break *b)
 
 /* Returns true if B still has cells that are yet to be returned,
    false if all of B's page has been processed. */
-bool
+static bool
 render_break_has_next (const struct render_break *b)
 {
   const struct render_page *page = b->page;
   enum table_axis axis = b->axis;
 
-  return page != NULL && b->cell < page->n[axis] - page->h[axis][1];
-}
-
-/* Returns the minimum SIZE argument that, if passed to render_break_next(),
-   will avoid a null return value (if cells are still left). */
-int
-render_break_next_size (const struct render_break *b)
-{
-  const struct render_page *page = b->page;
-  enum table_axis axis = b->axis;
-
-  return (!render_break_has_next (b) ? 0
-          : !cell_is_breakable (b, b->cell) ? needed_size (b, b->cell + 1)
-          : b->hw + page->params->font_size[axis]);
+  return page != NULL && b->z < page->n[axis] - page->h[axis][1];
 }
 
 /* Returns a new render_page that is up to SIZE pixels wide along B's axis.
@@ -1092,38 +1130,113 @@ render_break_next_size (const struct render_break *b)
    SIZE is too small to reasonably render any cells.  The latter will never
    happen if SIZE is at least as large as the page size passed to
    render_page_create() along B's axis. */
-struct render_page *
+static struct render_page *
 render_break_next (struct render_break *b, int size)
 {
   const struct render_page *page = b->page;
   enum table_axis axis = b->axis;
   struct render_page *subpage;
-  int cell, pixel;
+  int z, pixel;
 
   if (!render_break_has_next (b))
     return NULL;
 
   pixel = 0;
-  for (cell = b->cell; cell < page->n[axis] - page->h[axis][1]; cell++)
-    if (needed_size (b, cell + 1) > size)
-      {
-        if (!cell_is_breakable (b, cell))
-          {
-            if (cell == b->cell)
-              return NULL;
-          }
-        else
-          pixel = (cell == b->cell
-                   ? b->pixel + size - b->hw
-                   : size - needed_size (b, cell));
-        break;
-      }
+  for (z = b->z; z < page->n[axis] - page->h[axis][1]; z++)
+    {
+      int needed = needed_size (b, z + 1);
+      if (needed > size)
+        {
+          if (cell_is_breakable (b, z))
+            {
+              /* If there is no right header and we render a partial cell on
+                 the right side of the body, then we omit the rightmost rule of
+                 the body.  Otherwise the rendering is deceptive because it
+                 looks like the whole cell is present instead of a partial
+                 cell.
+
+                 This is similar to code for the left side in needed_size(). */
+              int rule_allowance = (page->h[axis][1]
+                                    ? 0
+                                    : rule_width (page, axis, z));
+
+              /* The amount that, if we added cell 'z', the rendering would
+                 overfill the allocated 'size'. */
+              int overhang = needed - size - rule_allowance;
+
+              /* The width of cell 'z'. */
+              int cell_size = cell_width (page, axis, z);
+
+              /* The amount trimmed off the left side of 'z',
+                 and the amount left to render. */
+              int cell_ofs = z == b->z ? b->pixel : 0;
+              int cell_left = cell_size - cell_ofs;
+
+              /* A small but visible width.  */
+              int em = page->params->font_size[axis];
+
+              /* If some of the cell remains to render,
+                 and there would still be some of the cell left afterward,
+                 then partially render that much of the cell. */
+              pixel = (cell_left && cell_left > overhang
+                       ? cell_left - overhang + cell_ofs
+                       : 0);
+
+              /* If there would be only a tiny amount of the cell left after
+                 rendering it partially, reduce the amount rendered slightly
+                 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;
+        }
+    }
+
+  if (z == b->z && !pixel)
+    return NULL;
 
-  subpage = render_page_select (page, axis, b->cell, b->pixel,
-                                pixel ? cell + 1 : cell,
-                                pixel ? cell_width (page, axis, cell) - pixel
+  subpage = render_page_select (page, axis, b->z, b->pixel,
+                                pixel ? z + 1 : z,
+                                pixel ? cell_width (page, axis, z) - pixel
                                 : 0);
-  b->cell = cell;
+  b->z = z;
   b->pixel = pixel;
   return subpage;
 }
@@ -1137,9 +1250,35 @@ needed_size (const struct render_break *b, int cell)
   enum table_axis axis = b->axis;
   int size;
 
-  size = joined_width (page, axis, b->cell, cell) + b->hw - b->pixel;
+  /* Width of left header not including its rightmost rule.  */
+  size = axis_width (page, axis, 0, rule_ofs (page->h[axis][0]));
+
+  /* If we have a pixel offset and there is no left header, then we omit the
+     leftmost rule of the body.  Otherwise the rendering is deceptive because
+     it looks like the whole cell is present instead of a partial cell.
+
+     Otherwise (if there are headers) we will be merging two rules: the
+     rightmost rule in the header and the leftmost rule in the body.  We assume
+     that the width of a merged rule is the larger of the widths of either rule
+     invidiually. */
+  if (b->pixel == 0 || page->h[axis][0])
+    size += MAX (rule_width (page, axis, page->h[axis][0]),
+                 rule_width (page, axis, b->z));
+
+  /* Width of body, minus any pixel offset in the leftmost cell. */
+  size += joined_width (page, axis, b->z, cell) - b->pixel;
+
+  /* Width of rightmost rule in body merged with leftmost rule in headers. */
+  size += MAX (rule_width_r (page, axis, page->h[axis][1]),
+               rule_width (page, axis, cell));
+
+  /* Width of right header not including its leftmost rule. */
+  size += axis_width (page, axis, rule_ofs_r (page, axis, page->h[axis][1]),
+                      rule_ofs_r (page, axis, 0));
+
+  /* Join crossing. */
   if (page->h[axis][0] && page->h[axis][1])
-    size += page->join_crossing[axis][b->cell];
+    size += page->join_crossing[axis][b->z];
 
   return size;
 }
@@ -1154,7 +1293,79 @@ cell_is_breakable (const struct render_break *b, int cell)
   const struct render_page *page = b->page;
   enum table_axis axis = b->axis;
 
-  return cell_width (page, axis, cell) > page->params->size[axis] / 2;
+  return cell_width (page, axis, cell) >= page->params->min_break[axis];
+}
+\f
+/* render_pager. */
+
+struct render_pager
+  {
+    int width;
+    struct render_break x_break;
+    struct render_break y_break;
+  };
+
+/* Creates and returns a new render_pager for breaking PAGE into smaller
+   chunks.  Takes ownership of PAGE. */
+struct render_pager *
+render_pager_create (struct render_page *page)
+{
+  struct render_pager *p = xmalloc (sizeof *p);
+  p->width = page->params->size[H];
+  render_break_init (&p->x_break, page, H);
+  render_break_init_empty (&p->y_break);
+  return p;
+}
+
+/* Destroys P. */
+void
+render_pager_destroy (struct render_pager *p)
+{
+  if (p)
+    {
+      render_break_destroy (&p->x_break);
+      render_break_destroy (&p->y_break);
+      free (p);
+    }
+}
+
+/* Returns true if P has content remaining to render, false if rendering is
+   done. */
+bool
+render_pager_has_next (const struct render_pager *p_)
+{
+  struct render_pager *p = CONST_CAST (struct render_pager *, p_);
+
+  while (!render_break_has_next (&p->y_break))
+    {
+      render_break_destroy (&p->y_break);
+      if (render_break_has_next (&p->x_break))
+        {
+          struct render_page *x_slice;
+
+          x_slice = render_break_next (&p->x_break, p->width);
+          render_break_init (&p->y_break, x_slice, V);
+        }
+      else
+        {
+          render_break_init_empty (&p->y_break);
+          return false;
+        }
+    }
+  return true;
+}
+
+/* Returns the next render_page from P to render in a space that has vertical
+   size SPACE and the horizontal size as specified in render_params passed to
+   render_page_create().  The caller takes ownership of the returned
+   render_page.  If no content remains to render, or if SPACE is too small to
+   render anything, returns NULL. */
+struct render_page *
+render_pager_next (struct render_pager *p, int space)
+{
+  return (render_pager_has_next (p)
+          ? render_break_next (&p->y_break, space)
+          : NULL);
 }
 \f
 /* render_page_select() and helpers. */
@@ -1251,7 +1462,12 @@ render_page_select (const struct render_page *page, enum table_axis axis,
   dcp = subpage->cp[a];
   *dcp = 0;
   for (z = 0; z <= rule_ofs (subpage->h[a][0]); z++, dcp++)
-    dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
+    {
+      if (z == 0 && subpage->is_edge_cutoff[a][0])
+        dcp[1] = dcp[0];
+      else
+        dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
+    }
   for (z = cell_ofs (z0); z <= cell_ofs (z1 - 1); z++, dcp++)
     {
       dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
@@ -1266,7 +1482,12 @@ render_page_select (const struct render_page *page, enum table_axis axis,
     }
   for (z = rule_ofs_r (page, a, subpage->h[a][1]);
        z <= rule_ofs_r (page, a, 0); z++, dcp++)
-    dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
+    {
+      if (z == rule_ofs_r (page, a, 0) && subpage->is_edge_cutoff[a][1])
+        dcp[1] = dcp[0];
+      else
+        dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
+    }
   assert (dcp == &subpage->cp[a][2 * subpage->n[a] + 1]);
 
   for (z = 0; z < page->n[b] * 2 + 2; z++)
@@ -1282,50 +1503,64 @@ render_page_select (const struct render_page *page, enum table_axis axis,
   s.p1 = p1;
   s.subpage = subpage;
 
-  for (z = 0; z < page->n[b]; z++)
-    {
-      struct table_cell cell;
-      int d[TABLE_N_AXES];
+  if (!page->h[a][0] || z0 > page->h[a][0] || p0)
+    for (z = 0; z < page->n[b]; )
+      {
+        struct table_cell cell;
+        int d[TABLE_N_AXES];
+        bool overflow0;
+        bool overflow1;
 
-      d[a] = z0;
-      d[b] = z;
-      table_get_cell (page->table, d[H], d[V], &cell);
-      if ((z == cell.d[b][0] && (p0 || cell.d[a][0] < z0))
-          || (z == cell.d[b][1] - 1 && p1))
-        {
-          ro = insert_overflow (&s, &cell);
-          ro->overflow[a][0] += p0 + axis_width (page, a,
-                                                 cell_ofs (cell.d[a][0]),
-                                                 cell_ofs (z0));
-          if (z1 == z0 + 1)
-            ro->overflow[a][1] += p1;
-          if (page->h[a][0] && page->h[a][1])
-            ro->overflow[a][0] -= page->join_crossing[a][cell.d[a][0] + 1];
-          if (cell.d[a][1] > z1)
-            ro->overflow[a][1] += axis_width (page, a, cell_ofs (z1),
-                                              cell_ofs (cell.d[a][1]));
-        }
-      table_cell_free (&cell);
-    }
+        d[a] = z0;
+        d[b] = z;
 
-  for (z = 0; z < page->n[b]; z++)
-    {
-      struct table_cell cell;
-      int d[TABLE_N_AXES];
+        table_get_cell (page->table, d[H], d[V], &cell);
+        overflow0 = p0 || cell.d[a][0] < z0;
+        overflow1 = cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1);
+        if (overflow0 || overflow1)
+          {
+            ro = insert_overflow (&s, &cell);
+
+            if (overflow0)
+              {
+                ro->overflow[a][0] += p0 + axis_width (
+                  page, a, cell_ofs (cell.d[a][0]), cell_ofs (z0));
+                if (page->h[a][0] && page->h[a][1])
+                  ro->overflow[a][0] -= page->join_crossing[a][cell.d[a][0]
+                                                               + 1];
+              }
+
+            if (overflow1)
+              {
+                ro->overflow[a][1] += p1 + axis_width (
+                  page, a, cell_ofs (z1), cell_ofs (cell.d[a][1]));
+                if (page->h[a][0] && page->h[a][1])
+                  ro->overflow[a][1] -= page->join_crossing[a][cell.d[a][1]];
+              }
+          }
+        z = cell.d[b][1];
+        table_cell_free (&cell);
+      }
 
-      /* XXX need to handle p1 below */
-      d[a] = z1 - 1;
-      d[b] = z;
-      table_get_cell (page->table, d[H], d[V], &cell);
-      if (z == cell.d[b][0] && cell.d[a][1] > z1
-          && find_overflow_for_cell (&s, &cell) == NULL)
-        {
-          ro = insert_overflow (&s, &cell);
-          ro->overflow[a][1] += axis_width (page, a, cell_ofs (z1),
-                                            cell_ofs (cell.d[a][1]));
-        }
-      table_cell_free (&cell);
-    }
+  if (!page->h[a][1] || z1 < page->n[a] - page->h[a][1] || p1)
+    for (z = 0; z < page->n[b]; )
+      {
+        struct table_cell cell;
+        int d[TABLE_N_AXES];
+
+        d[a] = z1 - 1;
+        d[b] = z;
+        table_get_cell (page->table, d[H], d[V], &cell);
+        if ((cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1))
+            && find_overflow_for_cell (&s, &cell) == NULL)
+          {
+            ro = insert_overflow (&s, &cell);
+            ro->overflow[a][1] += p1 + axis_width (page, a, cell_ofs (z1),
+                                                   cell_ofs (cell.d[a][1]));
+          }
+        z = cell.d[b][1];
+        table_cell_free (&cell);
+      }
 
   /* Copy overflows from PAGE into subpage. */
   HMAP_FOR_EACH (ro, struct render_overflow, node, &page->overflows)