+/* render_pager. */
+
+struct render_pager
+ {
+ const struct render_params *params;
+ double scale;
+
+ /* An array of "render_page"s to be rendered, in order, vertically. There
+ may be up to 5 pages, for the pivot table's title, layers, body,
+ captions, and footnotes. */
+ struct render_page *pages[5];
+ size_t n_pages;
+
+ size_t cur_page;
+ struct render_break x_break;
+ struct render_break y_break;
+ };
+
+static void
+render_pager_add_table (struct render_pager *p, struct table *table,
+ int min_width)
+{
+ if (table)
+ p->pages[p->n_pages++] = render_page_create (p->params, table, min_width);
+}
+
+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);
+}
+
+/* Creates and returns a new render_pager for rendering PT on the device
+ with the given PARAMS. */
+struct render_pager *
+render_pager_create (const struct render_params *params,
+ const struct pivot_table *pt,
+ const size_t *layer_indexes)
+{
+ if (!layer_indexes)
+ layer_indexes = pt->current_layer;
+
+ struct table *title, *layers, *body, *caption, *footnotes;
+ pivot_output (pt, layer_indexes, params->printing,
+ &title, &layers, &body, &caption, &footnotes, NULL, NULL);
+
+ /* Figure out the width of the body of the table. Use this to determine the
+ base scale. */
+ struct render_page *body_page = render_page_create (params, body, 0);
+ int body_width = table_width (body_page, H);
+ double scale = 1.0;
+ if (body_width > params->size[H])
+ {
+ if (pt->look->shrink_to_fit[H] && params->ops->scale)
+ scale = params->size[H] / (double) body_width;
+ else
+ {
+ struct render_break b;
+ render_break_init (&b, render_page_ref (body_page), H);
+ struct render_page *subpage
+ = render_break_next (&b, params->size[H]);
+ body_width = subpage ? subpage->cp[H][2 * subpage->n[H] + 1] : 0;
+ render_page_unref (subpage);
+ render_break_destroy (&b);
+ }
+ }
+
+ /* Create the pager. */
+ struct render_pager *p = xmalloc (sizeof *p);
+ *p = (struct render_pager) { .params = params, .scale = scale };
+ render_pager_add_table (p, title, body_width);
+ render_pager_add_table (p, layers, body_width);
+ p->pages[p->n_pages++] = body_page;
+ render_pager_add_table (p, caption, 0);
+ render_pager_add_table (p, footnotes, 0);
+ assert (p->n_pages <= sizeof p->pages / sizeof *p->pages);
+
+ /* If we're shrinking tables to fit the page length, then adjust the scale
+ factor.
+
+ XXX This will sometimes shrink more than needed, because adjusting the
+ scale factor allows for cells to be "wider", which means that sometimes
+ they won't break across as much vertical space, thus shrinking the table
+ vertically more than the scale would imply. Shrinking only as much as
+ necessary would require an iterative search. */
+ if (pt->look->shrink_to_fit[V] && params->ops->scale)
+ {
+ int total_height = 0;
+ for (size_t i = 0; i < p->n_pages; i++)
+ total_height += table_width (p->pages[i], V);
+ if (total_height * p->scale >= params->size[V])
+ p->scale *= params->size[V] / (double) total_height;
+ }
+
+ 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);
+ }
+}
+
+/* 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] / p->scale), 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)
+{
+ if (p->scale != 1.0)
+ {
+ p->params->ops->scale (p->params->aux, p->scale);
+ space /= p->scale;
+ }
+
+ 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);
+ }
+
+ if (p->scale != 1.0)
+ ofs[V] *= p->scale;
+
+ 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;
+}
+\f