zip-writer: Write size and CRC in local directory when possible.
[pspp] / src / output / render.c
index 094e68799cf93b7e72be846075f8e04da80ec499..503d5f692d7d7d3d3cfb654d869573c5f30978f4 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* 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
 
    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
 
    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. */
 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.
        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.
        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);
 }
 
   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)
 /* 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.
      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;
   */
   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;
   d = d0 * d1;
   if (total_unspanned > 0)
     d *= 2.0;
@@ -379,7 +399,7 @@ distribute_spanned_width (int width,
           w += width * unspanned * d0;
         }
 
           w += width * unspanned * d0;
         }
 
-      rows[x].width = w / d;
+      rows[x].width = MAX (rows[x].width, w / d);
       w -= rows[x].width * 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];
   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. */
 
   /* 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;
 
   /* 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;
 }
 
   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];
 }
 {
   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. */
 
 \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])
 {
 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 (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];
                 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];
             }
         }
                 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);
           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);
             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;
 }
 
   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)
 {
 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;
     }
 
         low = middle + 1;
     }
 
+  while (best > 0 && cp[best - 1] == cp[best])
+    best--;
+
   return best;
 }
 
   return best;
 }
 
@@ -1035,7 +1076,7 @@ render_break_init (struct render_break *b, struct render_page *page,
 {
   b->page = page;
   b->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);
 }
   b->pixel = 0;
   b->hw = headers_width (page, axis);
 }
@@ -1047,7 +1088,7 @@ render_break_init_empty (struct render_break *b)
 {
   b->page = NULL;
   b->axis = TABLE_HORZ;
 {
   b->page = NULL;
   b->axis = TABLE_HORZ;
-  b->cell = 0;
+  b->z = 0;
   b->pixel = 0;
   b->hw = 0;
 }
   b->pixel = 0;
   b->hw = 0;
 }
@@ -1071,7 +1112,7 @@ render_break_has_next (const struct render_break *b)
   const struct render_page *page = b->page;
   enum table_axis axis = b->axis;
 
   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];
+  return page != NULL && b->z < page->n[axis] - page->h[axis][1];
 }
 
 /* Returns the minimum SIZE argument that, if passed to render_break_next(),
 }
 
 /* Returns the minimum SIZE argument that, if passed to render_break_next(),
@@ -1083,7 +1124,7 @@ render_break_next_size (const struct render_break *b)
   enum table_axis axis = b->axis;
 
   return (!render_break_has_next (b) ? 0
   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)
+          : !cell_is_breakable (b, b->z) ? needed_size (b, b->z + 1)
           : b->hw + page->params->font_size[axis]);
 }
 
           : b->hw + page->params->font_size[axis]);
 }
 
@@ -1098,32 +1139,107 @@ 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;
   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;
 
   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);
                                 : 0);
-  b->cell = cell;
+  b->z = z;
   b->pixel = pixel;
   return subpage;
 }
   b->pixel = pixel;
   return subpage;
 }
@@ -1137,9 +1253,35 @@ needed_size (const struct render_break *b, int cell)
   enum table_axis axis = b->axis;
   int size;
 
   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])
   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;
 }
 
   return size;
 }
@@ -1154,7 +1296,7 @@ cell_is_breakable (const struct render_break *b, int cell)
   const struct render_page *page = b->page;
   enum table_axis axis = b->axis;
 
   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_page_select() and helpers. */
 }
 \f
 /* render_page_select() and helpers. */
@@ -1251,7 +1393,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 = 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]);
   for (z = cell_ofs (z0); z <= cell_ofs (z1 - 1); z++, dcp++)
     {
       dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
@@ -1266,7 +1413,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++)
     }
   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++)
   assert (dcp == &subpage->cp[a][2 * subpage->n[a] + 1]);
 
   for (z = 0; z < page->n[b] * 2 + 2; z++)
@@ -1282,50 +1434,64 @@ render_page_select (const struct render_page *page, enum table_axis axis,
   s.p1 = p1;
   s.subpage = subpage;
 
   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)
 
   /* Copy overflows from PAGE into subpage. */
   HMAP_FOR_EACH (ro, struct render_overflow, node, &page->overflows)