render: Push all rendering, not just pagination, into render_pager.
[pspp] / src / output / render.c
index 62e0ef3ee9de19a1d4a3f80ed7a315dc55845290..35e0a7a94cb6d1382eb59d9237364e9e3f1c1f99 100644 (file)
@@ -827,6 +827,23 @@ render_page_get_size (const struct render_page *page, enum table_axis axis)
 {
   return page->cp[axis][page->n[axis] * 2 + 1];
 }
+
+int
+render_page_get_best_breakpoint (const struct render_page *page, int height)
+{
+  int y;
+
+  /* If there's no room for at least the top row and the rules above and below
+     it, don't include any of the table. */
+  if (page->cp[V][3] > height)
+    return 0;
+
+  /* Otherwise include as many rows and rules as we can. */
+  for (y = 5; y <= 2 * page->n[V] + 1; y += 2)
+    if (page->cp[V][y] > height)
+      return page->cp[V][y - 2];
+  return height;
+}
 \f
 /* Drawing render_pages. */
 
@@ -951,7 +968,7 @@ render_page_draw_cells (const struct render_page *page,
           struct table_cell cell;
 
           table_get_cell (page->table, x / 2, y / 2, &cell);
-          if (y == bb[V][0] || y / 2 == cell.d[V][0])
+          if (y / 2 == bb[V][0] / 2 || y / 2 == cell.d[V][0])
             render_cell (page, &cell);
           x = rule_ofs (cell.d[H][1]);
           table_cell_free (&cell);
@@ -998,7 +1015,7 @@ get_clip_min_extent (int x0, const int cp[], int n)
   return best;
 }
 
-/* Returns the least value i, 0 <= i < n, such that cp[i + 1] >= x1. */
+/* Returns the least value i, 0 <= i < n, such that cp[i] >= x1. */
 static int
 get_clip_max_extent (int x1, const int cp[], int n)
 {
@@ -1017,6 +1034,9 @@ get_clip_max_extent (int x1, const int cp[], int n)
         low = middle + 1;
     }
 
+  while (best > 0 && cp[best - 1] == cp[best])
+    best--;
+
   return best;
 }
 
@@ -1039,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 *,
@@ -1050,7 +1080,7 @@ 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)
 {
@@ -1063,7 +1093,7 @@ render_break_init (struct render_break *b, struct render_page *page,
 
 /* 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;
@@ -1074,7 +1104,7 @@ render_break_init_empty (struct render_break *b)
 }
 
 /* Frees B and unrefs the render_page that it owns. */
-void
+static void
 render_break_destroy (struct render_break *b)
 {
   if (b != NULL)
@@ -1086,7 +1116,7 @@ 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;
@@ -1095,25 +1125,12 @@ render_break_has_next (const struct render_break *b)
   return page != NULL && b->z < 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->z) ? needed_size (b, b->z + 1)
-          : b->hw + page->params->font_size[axis]);
-}
-
 /* Returns a new render_page that is up to SIZE pixels wide along B's axis.
    Returns a null pointer if B has already been completely broken up, or if
    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;
@@ -1170,6 +1187,43 @@ render_break_next (struct render_break *b, int size)
                  to make the output look a little better. */
               if (pixel + em > cell_size)
                 pixel = MAX (pixel - em, 0);
+
+              /* If we're breaking vertically, then consider whether the cells
+                 being broken have a better internal breakpoint than the exact
+                 number of pixels available, which might look bad e.g. because
+                 it breaks in the middle of a line of text. */
+              if (axis == TABLE_VERT && page->params->adjust_break)
+                {
+                  int x;
+
+                  for (x = 0; x < page->n[H]; )
+                    {
+                      struct table_cell cell;
+                      int better_pixel;
+                      int w;
+
+                      table_get_cell (page->table, x, z, &cell);
+                      w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
+                      better_pixel = page->params->adjust_break (
+                        page->params->aux, &cell, w, pixel);
+                      x = cell.d[H][1];
+                      table_cell_free (&cell);
+
+                      if (better_pixel < pixel)
+                        {
+                          if (better_pixel > (z == b->z ? b->pixel : 0))
+                            {
+                              pixel = better_pixel;
+                              break;
+                            }
+                          else if (better_pixel == 0 && z != b->z)
+                            {
+                              pixel = 0;
+                              break;
+                            }
+                        }
+                    }
+                }
             }
           break;
         }
@@ -1242,6 +1296,123 @@ cell_is_breakable (const struct render_break *b, int cell)
   return cell_width (page, axis, cell) >= page->params->min_break[axis];
 }
 \f
+/* render_pager. */
+
+struct render_pager
+  {
+    int width;
+    struct render_page *page;
+    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];
+  p->page = render_page_ref (page);
+  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);
+      render_page_unref (p->page);
+      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;
+}
+
+/* Draws a chunk of content from P to fit in a space that has vertical size
+   SPACE and the horizontal size specified in the render_params passed to
+   render_page_create().  Returns the amount of space actually used by the
+   rendered chunk, which will be 0 if SPACE is too small to render anything or
+   if no content remains (use render_pager_has_next() to distinguish these
+   cases). */
+int
+render_pager_draw_next (struct render_pager *p, int space)
+{
+  struct render_page *page = (render_pager_has_next (p)
+                              ? render_break_next (&p->y_break, space)
+                              : NULL);
+  if (page)
+    {
+      int used = render_page_get_size (page, V);
+
+      render_page_draw (page);
+      render_page_unref (page);
+      return used;
+    }
+  else
+    return 0;
+}
+
+/* Draws all of P's content. */
+void
+render_pager_draw (const struct render_pager *p)
+{
+  render_page_draw (p->page);
+}
+
+/* Draws the region of P's content that lies in the region (X,Y)-(X+W,Y+H).
+   Some extra content might be drawn; the device should perform clipping as
+   necessary. */
+void
+render_pager_draw_region (const struct render_pager *p,
+                          int x, int y, int w, int h)
+{
+  render_page_draw_region (p->page, x, y, w, h);
+}
+
+/* Returns the size of P's content along AXIS; i.e. the content's width if AXIS
+   is TABLE_HORZ and its length if AXIS is TABLE_VERT. */
+int
+render_pager_get_size (const struct render_pager *p, enum table_axis axis)
+{
+  return render_page_get_size (p->page, axis);
+}
+
+int
+render_pager_get_best_breakpoint (const struct render_pager *p, int height)
+{
+  return render_page_get_best_breakpoint (p->page, height);
+}
+\f
 /* render_page_select() and helpers. */
 
 struct render_page_selection