+ return cell_width (page, axis, cell) >= page->params->min_break[axis];
+}
+\f
+/* render_pager. */
+
+struct render_pager
+ {
+ const struct render_params *params;
+
+ struct render_page **pages;
+ size_t n_pages, allocated_pages;
+
+ size_t cur_page;
+ struct render_break x_break;
+ struct render_break y_break;
+ };
+
+static const struct render_page *
+render_pager_add_table (struct render_pager *p, struct table *table,
+ int min_width)
+{
+ if (p->n_pages >= p->allocated_pages)
+ p->pages = x2nrealloc (p->pages, &p->allocated_pages, sizeof *p->pages);
+
+ struct render_page *page = render_page_create (p->params, table, min_width);
+ p->pages[p->n_pages++] = page;
+ return page;
+}
+
+static void
+render_pager_start_page (struct render_pager *p)
+{
+ render_break_init (&p->x_break, render_page_ref (p->pages[p->cur_page++]),
+ H);
+ render_break_init_empty (&p->y_break);
+}
+
+static void
+add_footnote_page (struct render_pager *p, const struct table_item *item)
+{
+ const struct footnote **f;
+ size_t n_footnotes = table_collect_footnotes (item, &f);
+ if (!n_footnotes)
+ return;
+
+ struct table *t = table_create (1, n_footnotes, 0, 0, 0, 0);
+
+ const struct area_style *style = item->table->styles[PIVOT_AREA_FOOTER];
+ if (!style)
+ style = pivot_area_get_default_style (PIVOT_AREA_FOOTER);
+ t->styles[PIVOT_AREA_FOOTER] = area_style_clone (t->container, style);
+
+ for (size_t i = 0; i < n_footnotes; i++)
+ {
+ table_text_format (t, 0, i, PIVOT_AREA_FOOTER << TAB_STYLE_SHIFT,
+ "%s. %s", f[i]->marker, f[i]->content);
+ if (f[i]->style)
+ table_add_style (t, 0, i, f[i]->style);
+ }
+ render_pager_add_table (p, t, 0);
+
+ free (f);
+}
+
+static void
+add_text_page (struct render_pager *p, const struct table_item_text *t,
+ int min_width)
+{
+ if (!t)
+ return;
+
+ struct table *tab = table_create (1, 1, 0, 0, 0, 0);
+ table_text (tab, 0, 0, 0, t->content);
+ for (size_t i = 0; i < t->n_footnotes; i++)
+ table_add_footnote (tab, 0, 0, t->footnotes[i]);
+ if (t->style)
+ tab->styles[0] = area_style_clone (tab->container, t->style);
+ render_pager_add_table (p, tab, min_width);
+}
+
+static void
+add_layers_page (struct render_pager *p,
+ const struct table_item_layers *layers, int min_width)
+{
+ if (!layers)
+ return;
+
+ struct table *tab = table_create (1, layers->n_layers, 0, 0, 0, 0);
+ for (size_t i = 0; i < layers->n_layers; i++)
+ {
+ const struct table_item_layer *layer = &layers->layers[i];
+ table_text (tab, 0, i, 0, layer->content);
+ for (size_t j = 0; j < layer->n_footnotes; j++)
+ table_add_footnote (tab, 0, i, layer->footnotes[j]);
+ }
+ if (layers->style)
+ tab->styles[0] = area_style_clone (tab->container, layers->style);
+ render_pager_add_table (p, tab, min_width);
+}
+
+/* Creates and returns a new render_pager for rendering TABLE_ITEM on the
+ device with the given PARAMS. */
+struct render_pager *
+render_pager_create (const struct render_params *params,
+ const struct table_item *table_item)
+{
+ const struct table *table = table_item_get_table (table_item);
+
+ struct render_pager *p = xzalloc (sizeof *p);
+ p->params = params;
+
+ struct render_page *page = render_page_create (params, table_ref (table), 0);
+ struct render_break b;
+ render_break_init (&b, page, H);
+ struct render_page *subpage = render_break_next (&b, p->params->size[H]);
+ int title_width = subpage ? subpage->cp[H][2 * subpage->n[H] + 1] : 0;
+ render_page_unref (subpage);
+ render_break_destroy (&b);
+
+ /* Title. */
+ add_text_page (p, table_item_get_title (table_item), title_width);
+
+ /* Layers. */
+ add_layers_page (p, table_item_get_layers (table_item), title_width);
+
+ /* Body. */
+ render_pager_add_table (p, table_ref (table_item_get_table (table_item)), 0);
+
+ /* Caption. */
+ add_text_page (p, table_item_get_caption (table_item), 0);
+
+ /* Footnotes. */
+ add_footnote_page (p, table_item);
+
+ render_pager_start_page (p);
+
+ 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);
+ for (size_t i = 0; i < p->n_pages; i++)
+ render_page_unref (p->pages[i]);
+ free (p->pages);
+ 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))
+ {
+ render_break_destroy (&p->x_break);
+ if (p->cur_page >= p->n_pages)
+ {
+ render_break_init_empty (&p->x_break);
+ render_break_init_empty (&p->y_break);
+ return false;
+ }
+ render_pager_start_page (p);
+ }
+ else
+ render_break_init (
+ &p->y_break, render_break_next (&p->x_break, p->params->size[H]), V);
+ }
+ 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)
+{
+ int ofs[TABLE_N_AXES] = { 0, 0 };
+ size_t start_page = SIZE_MAX;
+
+ while (render_pager_has_next (p))
+ {
+ if (start_page == p->cur_page)
+ break;
+ start_page = p->cur_page;
+
+ struct render_page *page
+ = render_break_next (&p->y_break, space - ofs[V]);
+ if (!page)
+ break;
+
+ render_page_draw (page, ofs);
+ ofs[V] += render_page_get_size (page, V);
+ render_page_unref (page);
+ }
+ return ofs[V];
+}
+
+/* Draws all of P's content. */
+void
+render_pager_draw (const struct render_pager *p)
+{
+ render_pager_draw_region (p, 0, 0, INT_MAX, INT_MAX);
+}
+
+/* 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)
+{
+ int ofs[TABLE_N_AXES] = { 0, 0 };
+ int clip[TABLE_N_AXES][2];
+
+ clip[H][0] = x;
+ clip[H][1] = x + w;
+ for (size_t i = 0; i < p->n_pages; i++)
+ {
+ const struct render_page *page = p->pages[i];
+ int size = render_page_get_size (page, V);
+
+ clip[V][0] = MAX (y, ofs[V]) - ofs[V];
+ clip[V][1] = MIN (y + h, ofs[V] + size) - ofs[V];
+ if (clip[V][1] > clip[V][0])
+ render_page_draw_region (page, ofs, clip);
+
+ ofs[V] += size;
+ }
+}
+
+/* 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)
+{
+ int size = 0;
+
+ for (size_t i = 0; i < p->n_pages; i++)
+ {
+ int subsize = render_page_get_size (p->pages[i], axis);
+ size = axis == H ? MAX (size, subsize) : size + subsize;
+ }
+
+ return size;
+}
+
+int
+render_pager_get_best_breakpoint (const struct render_pager *p, int height)
+{
+ int y = 0;
+ size_t i;
+
+ for (i = 0; i < p->n_pages; i++)
+ {
+ int size = render_page_get_size (p->pages[i], V);
+ if (y + size >= height)
+ return render_page_get_best_breakpoint (p->pages[i], height - y) + y;
+ y += size;
+ }
+
+ return height;