/* PSPP - a program for statistical analysis.
- Copyright (C) 2009, 2010, 2011, 2013 Free Software Foundation, Inc.
+ Copyright (C) 2009, 2010, 2011, 2013, 2014, 2016 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
#include <math.h>
#include <stdio.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "libpspp/assertion.h"
#include "libpspp/hash-functions.h"
#include "libpspp/hmap.h"
+#include "libpspp/pool.h"
#include "output/render.h"
+#include "output/table-item.h"
#include "output/table.h"
#include "gl/minmax.h"
#include "gl/xalloc.h"
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */
#define H TABLE_HORZ
#define V TABLE_VERT
May represent the layout of an entire table presented to
render_page_create(), or a rectangular subregion of a table broken out using
- render_break_next() to allow a table to be broken across multiple pages. */
+ render_break_next() to allow a table to be broken across multiple pages.
+
+ A page's size is not limited to the size passed in as part of render_params.
+ render_pager breaks a render_page into smaller render_pages that will fit in
+ the available space. */
struct render_page
{
const struct render_params *params; /* Parameters of the target device. */
struct table *table; /* Table rendered. */
int ref_cnt;
- /* Local copies of table->n and table->h, for convenience. */
- int n[TABLE_N_AXES];
+ /* Region of 'table' to render.
+
+ The horizontal cells rendered are the leftmost h[H][0], then
+ r[H][0] through r[H][1], exclusive, then the rightmost h[H][1].
+
+ The vertical cells rendered are the topmost h[V][0], then r[V][0]
+ through r[V][1], exclusive, then the bottommost h[V][1].
+
+ n[H] = h[H][0] + (r[H][1] - r[H][0]) + h[H][1]
+ n[V] = h[V][0] + (r[V][1] - r[V][0]) + h[V][1]
+ */
int h[TABLE_N_AXES][2];
+ int r[TABLE_N_AXES][2];
+ int n[TABLE_N_AXES];
+
+ /* "Cell positions".
- /* cp[H] represents x positions within the table.
+ cp[H] represents x positions within the table.
cp[H][0] = 0.
cp[H][1] = the width of the leftmost vertical rule.
cp[H][2] = cp[H][1] + the width of the leftmost column.
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.
int *join_crossing[TABLE_N_AXES];
};
+static struct render_page *render_page_create (const struct render_params *,
+ struct table *, int min_width);
+
+struct render_page *render_page_ref (const struct render_page *page_);
+static void render_page_unref (struct render_page *);
+
/* Returns the offset in struct render_page's cp[axis] array of the rule with
index RULE_IDX. That is, if RULE_IDX is 0, then the offset is that of the
leftmost or topmost rule; if RULE_IDX is 1, then the offset is that of the
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)
int n = page->n[axis];
int x0 = page->h[axis][0];
int x1 = n - page->h[axis][1];
- int x, max;
- max = 0;
- for (x = x0; x < x1; x++)
+ int max = 0;
+ for (int x = x0; x < x1; x++)
{
int w = cell_width (page, axis, x);
if (w > max)
int overflow[TABLE_N_AXES][2];
};
-/* Returns a hash value for (X,Y). */
+/* Returns a hash value for (,Y). */
static unsigned int
-hash_overflow (int x, int y)
+hash_cell (int x, int y)
{
return hash_int (x + (y << 16), 0);
}
const struct render_overflow *of;
HMAP_FOR_EACH_WITH_HASH (of, struct render_overflow, node,
- hash_overflow (x, y), &page->overflows)
+ hash_cell (x, y), &page->overflows)
if (x == of->d[H] && y == of->d[V])
return of;
}
distribute_spanned_width (int width,
struct render_row *rows, const int *rules, int n)
{
- int total_unspanned;
- double w, d0, d1, d;
- int x;
-
/* Sum up the unspanned widths of the N rows for use as weights. */
- total_unspanned = 0;
- for (x = 0; x < n; x++)
+ int total_unspanned = 0;
+ for (int x = 0; x < n; x++)
total_unspanned += rows[x].unspanned;
- for (x = 0; x < n - 1; x++)
+ for (int x = 0; x < n - 1; x++)
total_unspanned += rules[x + 1];
if (total_unspanned >= width)
return;
unspanned weights when 'total_unspanned' is 0 (because that would cause a
division by zero).
- This implementation uses floating-point types and operators, but all the
- values involved are integers. For integers smaller than 53 bits, this
- should not lose any precision, and it should degrade gracefully for larger
- values.
-
The calculation we want to do is this:
w0 = width / n
the rule on the right. That way each rule contributes to both the cell on
its left and on its right.)
*/
- d0 = n;
- d1 = 2.0 * (total_unspanned > 0 ? total_unspanned : 1.0);
- d = d0 * d1;
+ long long int d0 = n;
+ long long int d1 = 2LL * MAX (total_unspanned, 1);
+ long long int d = d0 * d1;
if (total_unspanned > 0)
- d *= 2.0;
- w = floor (d / 2.0);
- for (x = 0; x < n; x++)
+ d *= 2;
+ long long int w = d / 2;
+ for (int x = 0; x < n; x++)
{
w += width * d1;
if (total_unspanned > 0)
{
- double unspanned = rows[x].unspanned * 2.0;
+ long long int unspanned = rows[x].unspanned * 2LL;
if (x < n - 1)
unspanned += rules[x + 1];
if (x > 0)
const struct render_row *rows, const int *rules)
{
int n = page->n[axis];
- int *cp;
- int z;
-
- cp = page->cp[axis];
+ int *cp = page->cp[axis];
cp[0] = 0;
- for (z = 0; z < n; z++)
+ for (int z = 0; z < n; z++)
{
cp[1] = cp[0] + rules[z];
cp[2] = cp[1] + rows[z].width;
static int
calculate_table_width (int n, const struct render_row *rows, int *rules)
{
- int width;
- int x;
-
- width = 0;
- for (x = 0; x < n; x++)
+ int width = 0;
+ for (int x = 0; x < n; x++)
width += rows[x].width;
- for (x = 0; x <= n; x++)
+ for (int x = 0; x <= n; x++)
width += rules[x];
return width;
{
switch (type)
{
- case TAL_0:
- case TAL_GAP:
+ case TABLE_STROKE_NONE:
return RENDER_LINE_NONE;
- case TAL_1:
+ case TABLE_STROKE_SOLID:
return RENDER_LINE_SINGLE;
- case TAL_2:
+ case TABLE_STROKE_DASHED:
+ return RENDER_LINE_DASHED;
+ case TABLE_STROKE_THICK:
+ return RENDER_LINE_THICK;
+ case TABLE_STROKE_THIN:
+ return RENDER_LINE_THIN;
+ case TABLE_STROKE_DOUBLE:
return RENDER_LINE_DOUBLE;
default:
NOT_REACHED ();
enum table_axis a, int z)
{
enum table_axis b = !a;
- unsigned int rules;
- int d[TABLE_N_AXES];
- 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. */
- rules = 0;
+ struct cell_color color;
+ unsigned int rules = 0;
+ int d[TABLE_N_AXES];
d[a] = z;
for (d[b] = 0; d[b] < table->n[b]; d[b]++)
- rules |= 1u << table_get_rule (table, a, d[H], d[V]);
+ rules |= 1u << table_get_rule (table, a, d[H], d[V], &color);
+
+ /* Turn off TABLE_STROKE_NONE because it has width 0 and we needn't bother.
+ However, if the device doesn't support margins, make sure that there is at
+ least a small gap between cells (but we don't need any at the left or
+ right edge of the table). */
+ if (rules & (1u << TABLE_STROKE_NONE))
+ {
+ rules &= ~(1u << TABLE_STROKE_NONE);
+ if (z > 0 && z < table->n[a] && !params->supports_margins && a == H)
+ rules |= 1u << TABLE_STROKE_SOLID;
+ }
/* Calculate maximum width of the rules that are present. */
- width = 0;
- 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]);
+ int width = 0;
+ for (size_t i = 0; i < TABLE_N_STROKES; i++)
+ if (rules & (1u << i))
+ width = MAX (width, params->line_widths[rule_to_render_type (i)]);
return width;
}
/* Allocates and returns a new render_page using PARAMS and TABLE. Allocates
- space for all of the members of the new page, but the caller must initialize
- the 'cp' member itself. */
+ space for rendering a table with dimensions given in N. The caller must
+ initialize most of the members itself. */
static struct render_page *
-render_page_allocate (const struct render_params *params,
- struct table *table)
+render_page_allocate__ (const struct render_params *params,
+ struct table *table, int n[TABLE_N_AXES])
{
- struct render_page *page;
- int i;
-
- page = xmalloc (sizeof *page);
+ struct render_page *page = xmalloc (sizeof *page);
page->params = params;
page->table = table;
page->ref_cnt = 1;
- page->n[H] = table->n[H];
- page->n[V] = table->n[V];
- page->h[H][0] = table->h[H][0];
- page->h[H][1] = table->h[H][1];
- page->h[V][0] = table->h[V][0];
- page->h[V][1] = table->h[V][1];
-
- for (i = 0; i < TABLE_N_AXES; i++)
+ page->n[H] = n[H];
+ page->n[V] = n[V];
+
+ for (int i = 0; i < TABLE_N_AXES; i++)
{
- page->cp[i] = xmalloc ((2 * page->n[i] + 2) * sizeof *page->cp[i]);
- page->join_crossing[i] = xzalloc ((page->n[i] + 1) * sizeof *page->join_crossing[i]);
+ page->cp[i] = xmalloc ((2 * n[i] + 2) * sizeof *page->cp[i]);
+ page->join_crossing[i] = xzalloc ((n[i] + 1)
+ * sizeof *page->join_crossing[i]);
}
hmap_init (&page->overflows);
return page;
}
+/* Allocates and returns a new render_page using PARAMS and TABLE. Allocates
+ space for all of the members of the new page, but the caller must initialize
+ the 'cp' member itself. */
+static struct render_page *
+render_page_allocate (const struct render_params *params, struct table *table)
+{
+ struct render_page *page = render_page_allocate__ (params, table, table->n);
+ for (enum table_axis a = 0; a < TABLE_N_AXES; a++)
+ {
+ page->h[a][0] = table->h[a][0];
+ page->h[a][1] = table->h[a][1];
+ page->r[a][0] = table->h[a][0];
+ page->r[a][1] = table->n[a] - table->h[a][1];
+ }
+ return page;
+}
+
/* Allocates and returns a new render_page for PARAMS and TABLE, initializing
cp[H] in the new page from ROWS and RULES. The caller must still initialize
cp[V]. */
const struct render_row *rows_max,
int w_min, int w_max, const int *rules)
{
- /* This implementation uses floating-point types and operators, but all the
- values involved are integers. For integers smaller than 53 bits, this
- should not lose any precision, and it should degrade gracefully for larger
- values. */
const int n = table->n[H];
- const double avail = params->size[H] - w_min;
- const double wanted = w_max - w_min;
- struct render_page *page;
- double w;
- int *cph;
- int x;
+ const long long int avail = params->size[H] - w_min;
+ const long long int wanted = w_max - w_min;
assert (wanted > 0);
- page = render_page_allocate (params, table);
+ struct render_page *page = render_page_allocate (params, table);
- cph = page->cp[H];
+ int *cph = page->cp[H];
*cph = 0;
- w = (int) wanted / 2;
- for (x = 0; x < n; x++)
+ long long int w = wanted / 2;
+ for (int x = 0; x < n; x++)
{
- int extra;
-
w += avail * (rows_max[x].width - rows_min[x].width);
- extra = w / wanted;
+ int extra = w / wanted;
w -= extra * wanted;
cph[1] = cph[0] + rules[x];
assert (page->cp[H][n * 2 + 1] == params->size[H]);
return page;
}
-
\f
static void
set_join_crossings (struct render_page *page, enum table_axis axis,
const struct table_cell *cell, int *rules)
{
- int z;
-
- for (z = cell->d[axis][0] + 1; z <= cell->d[axis][1] - 1; z++)
+ for (int z = cell->d[axis][0] + 1; z <= cell->d[axis][1] - 1; z++)
page->join_crossing[axis][z] = rules[z];
}
+/* Maps a contiguous range of cells from a page to the underlying table along
+ the horizpntal or vertical dimension. */
+struct map
+ {
+ int p0; /* First ordinate in the page. */
+ int t0; /* First ordinate in the table. */
+ int n; /* Number of ordinates in page and table. */
+ };
+
+/* Initializes M to a mapping from PAGE to PAGE->table along axis A. The
+ mapping includes ordinate Z (in PAGE). */
+static void
+get_map (const struct render_page *page, enum table_axis a, int z,
+ struct map *m)
+{
+ if (z < page->h[a][0])
+ {
+ m->p0 = 0;
+ m->t0 = 0;
+ m->n = page->h[a][0];
+ }
+ else if (z < page->n[a] - page->h[a][1])
+ {
+ m->p0 = page->h[a][0];
+ m->t0 = page->r[a][0];
+ m->n = page->r[a][1] - page->r[a][0];
+ }
+ else
+ {
+ m->p0 = page->n[a] - page->h[a][1];
+ m->t0 = page->table->n[a] - page->table->h[a][1];
+ m->n = page->h[a][1];
+ }
+}
+
+/* Initializes CELL with the contents of the table cell at column X and row Y
+ within PAGE. When CELL is no longer needed, the caller is responsible for
+ freeing it by calling table_cell_free(CELL).
+
+ The caller must ensure that CELL is destroyed before TABLE is unref'ed.
+
+ This is equivalent to table_get_cell(), except X and Y are in terms of the
+ page's rows and columns rather than the underlying table's. */
+static void
+render_get_cell (const struct render_page *page, int x, int y,
+ struct table_cell *cell)
+{
+ int d[TABLE_N_AXES] = { [H] = x, [V] = y };
+ struct map map[TABLE_N_AXES];
+
+ for (enum table_axis a = 0; a < TABLE_N_AXES; a++)
+ {
+ struct map *m = &map[a];
+ get_map (page, a, d[a], m);
+ d[a] += m->t0 - m->p0;
+ }
+ table_get_cell (page->table, d[H], d[V], cell);
+
+ for (enum table_axis a = 0; a < TABLE_N_AXES; a++)
+ {
+ struct map *m = &map[a];
+
+ for (int i = 0; i < 2; i++)
+ cell->d[a][i] -= m->t0 - m->p0;
+ cell->d[a][0] = MAX (cell->d[a][0], m->p0);
+ cell->d[a][1] = MIN (cell->d[a][1], m->p0 + m->n);
+ }
+}
+
/* Creates and returns a new render_page for rendering TABLE on a device
described by PARAMS.
The new render_page will be suitable for rendering on a device whose page
size is PARAMS->size, but the caller is responsible for actually breaking it
up to fit on such a device, using the render_break abstraction. */
-struct render_page *
-render_page_create (const struct render_params *params,
- const struct table *table_)
+static struct render_page *
+render_page_create (const struct render_params *params, struct table *table,
+ int min_width)
{
- struct render_page *page;
- struct table *table;
enum { MIN, MAX };
- struct render_row *columns[2];
- struct render_row *rows;
- int table_widths[2];
- int *rules[TABLE_N_AXES];
- int nr, nc;
- int x, y;
- int i;
- enum table_axis axis;
- table = table_ref (table_);
- nc = table_nc (table);
- nr = table_nr (table);
+ int nc = table_nc (table);
+ int nr = table_nr (table);
/* Figure out rule widths. */
- for (axis = 0; axis < TABLE_N_AXES; axis++)
+ int *rules[TABLE_N_AXES];
+ for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
{
int n = table->n[axis] + 1;
- int z;
rules[axis] = xnmalloc (n, sizeof *rules);
- for (z = 0; z < n; z++)
+ for (int z = 0; z < n; z++)
rules[axis][z] = measure_rule (params, table, axis, z);
}
/* Calculate minimum and maximum widths of cells that do not
span multiple columns. */
- for (i = 0; i < 2; i++)
+ struct render_row *columns[2];
+ for (int i = 0; i < 2; i++)
columns[i] = xzalloc (nc * sizeof *columns[i]);
- for (y = 0; y < nr; y++)
- for (x = 0; x < nc; )
+ for (int y = 0; y < nr; y++)
+ for (int x = 0; x < nc;)
{
struct table_cell cell;
table_get_cell (table, x, y, &cell);
- if (y == cell.d[V][0] && table_cell_colspan (&cell) == 1)
+ if (y == cell.d[V][0])
{
- int w[2];
- int i;
-
- params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]);
- for (i = 0; i < 2; i++)
- if (columns[i][x].unspanned < w[i])
- columns[i][x].unspanned = w[i];
+ if (table_cell_colspan (&cell) == 1)
+ {
+ int w[2];
+ params->ops->measure_cell_width (params->aux, &cell,
+ &w[MIN], &w[MAX]);
+ for (int i = 0; i < 2; i++)
+ if (columns[i][x].unspanned < w[i])
+ columns[i][x].unspanned = w[i];
+ }
}
x = cell.d[H][1];
- table_cell_free (&cell);
}
/* Distribute widths of spanned columns. */
- for (i = 0; i < 2; i++)
- for (x = 0; x < nc; x++)
+ for (int i = 0; i < 2; i++)
+ for (int x = 0; x < nc; x++)
columns[i][x].width = columns[i][x].unspanned;
- for (y = 0; y < nr; y++)
- for (x = 0; x < nc; )
+ for (int y = 0; y < nr; y++)
+ for (int x = 0; x < nc;)
{
struct table_cell cell;
{
int w[2];
- params->measure_cell_width (params->aux, &cell, &w[MIN], &w[MAX]);
- for (i = 0; i < 2; i++)
+ params->ops->measure_cell_width (params->aux, &cell,
+ &w[MIN], &w[MAX]);
+ for (int i = 0; i < 2; i++)
distribute_spanned_width (w[i], &columns[i][cell.d[H][0]],
rules[H], table_cell_colspan (&cell));
}
x = cell.d[H][1];
- table_cell_free (&cell);
}
+ if (min_width > 0)
+ for (int i = 0; i < 2; i++)
+ distribute_spanned_width (min_width, &columns[i][0], rules[H], nc);
+
+ /* In pathological cases, spans can cause the minimum width of a column to
+ exceed the maximum width. This bollixes our interpolation algorithm
+ later, so fix it up. */
+ for (int i = 0; i < nc; i++)
+ if (columns[MIN][i].width > columns[MAX][i].width)
+ columns[MAX][i].width = columns[MIN][i].width;
/* Decide final column widths. */
- for (i = 0; i < 2; i++)
+ int table_widths[2];
+ for (int i = 0; i < 2; i++)
table_widths[i] = calculate_table_width (table_nc (table),
columns[i], rules[H]);
+
+ struct render_page *page;
if (table_widths[MAX] <= params->size[H])
{
/* Fits even with maximum widths. Use them. */
}
/* Calculate heights of cells that do not span multiple rows. */
- rows = xzalloc (nr * sizeof *rows);
- for (y = 0; y < nr; y++)
- {
- for (x = 0; x < nc; )
- {
- struct render_row *r = &rows[y];
- struct table_cell cell;
+ struct render_row *rows = xzalloc (nr * sizeof *rows);
+ for (int y = 0; y < nr; y++)
+ for (int x = 0; x < nc;)
+ {
+ struct render_row *r = &rows[y];
+ struct table_cell cell;
- table_get_cell (table, x, y, &cell);
- if (y == cell.d[V][0])
- {
- if (table_cell_rowspan (&cell) == 1)
- {
- int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
- int h = params->measure_cell_height (params->aux, &cell, w);
- if (h > r->unspanned)
- r->unspanned = r->width = h;
- }
- else
- set_join_crossings (page, V, &cell, rules[V]);
-
- if (table_cell_colspan (&cell) > 1)
- set_join_crossings (page, H, &cell, rules[H]);
- }
- x = cell.d[H][1];
- table_cell_free (&cell);
- }
- }
- for (i = 0; i < 2; i++)
+ render_get_cell (page, x, y, &cell);
+ if (y == cell.d[V][0])
+ {
+ if (table_cell_rowspan (&cell) == 1)
+ {
+ int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
+ int h = params->ops->measure_cell_height (params->aux,
+ &cell, w);
+ if (h > r->unspanned)
+ r->unspanned = r->width = h;
+ }
+ else
+ set_join_crossings (page, V, &cell, rules[V]);
+
+ if (table_cell_colspan (&cell) > 1)
+ set_join_crossings (page, H, &cell, rules[H]);
+ }
+ x = cell.d[H][1];
+ }
+ for (int i = 0; i < 2; i++)
free (columns[i]);
/* Distribute heights of spanned rows. */
- for (y = 0; y < nr; y++)
- for (x = 0; x < nc; )
+ for (int y = 0; y < nr; y++)
+ for (int x = 0; x < nc;)
{
struct table_cell cell;
- table_get_cell (table, x, y, &cell);
+ render_get_cell (page, x, y, &cell);
if (y == cell.d[V][0] && table_cell_rowspan (&cell) > 1)
{
int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
- int h = params->measure_cell_height (params->aux, &cell, w);
+ int h = params->ops->measure_cell_height (params->aux, &cell, w);
distribute_spanned_width (h, &rows[cell.d[V][0]], rules[V],
table_cell_rowspan (&cell));
}
x = cell.d[H][1];
- table_cell_free (&cell);
}
/* Decide final row heights. */
free (rows);
/* Measure headers. If they are "too big", get rid of them. */
- for (axis = 0; axis < TABLE_N_AXES; axis++)
+ for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
{
int hw = headers_width (page, axis);
if (hw * 2 >= page->params->size[axis]
|| hw + max_cell_width (page, axis) > page->params->size[axis])
{
- page->table = table_unshare (page->table);
- page->table->h[axis][0] = page->table->h[axis][1] = 0;
page->h[axis][0] = page->h[axis][1] = 0;
+ page->r[axis][0] = 0;
+ page->r[axis][1] = page->n[axis];
}
}
/* Decreases PAGE's reference count and destroys PAGE if this causes the
reference count to fall to zero. */
-void
+static void
render_page_unref (struct render_page *page)
{
if (page != NULL && --page->ref_cnt == 0)
{
- int i;
struct render_overflow *overflow, *next;
-
HMAP_FOR_EACH_SAFE (overflow, next, struct render_overflow, node,
&page->overflows)
free (overflow);
hmap_destroy (&page->overflows);
table_unref (page->table);
-
- for (i = 0; i < TABLE_N_AXES; ++i)
+
+ for (int i = 0; i < TABLE_N_AXES; ++i)
{
free (page->join_crossing[i]);
free (page->cp[i]);
/* Returns the size of PAGE along AXIS. (This might be larger than the page
size specified in the parameters passed to render_page_create(). Use a
render_break to break up a render_page into page-sized chunks.) */
-int
+static int
render_page_get_size (const struct render_page *page, enum table_axis axis)
{
return page->cp[axis][page->n[axis] * 2 + 1];
}
+
+static int
+render_page_get_best_breakpoint (const struct render_page *page, int height)
+{
+ /* 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 (int 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 inline enum render_line_style
+/* This is like table_get_rule() except:
+
+ - D is in terms of the page's rows and column rather than the underlying
+ table's.
+
+ - The result is in the form of a render_line_style. */
+static enum render_line_style
get_rule (const struct render_page *page, enum table_axis axis,
- const int d[TABLE_N_AXES])
+ const int d_[TABLE_N_AXES], struct cell_color *color)
{
- return rule_to_render_type (table_get_rule (page->table,
- axis, d[H] / 2, d[V] / 2));
+ int d[TABLE_N_AXES] = { d_[0] / 2, d_[1] / 2 };
+ int d2 = -1;
+
+ enum table_axis a = axis;
+ if (d[a] < page->h[a][0])
+ /* Nothing to do */;
+ else if (d[a] <= page->n[a] - page->h[a][1])
+ {
+ if (page->h[a][0] && d[a] == page->h[a][0])
+ d2 = page->h[a][0];
+ else if (page->h[a][1] && d[a] == page->n[a] - page->h[a][1])
+ d2 = page->table->n[a] - page->h[a][1];
+ d[a] += page->r[a][0] - page->h[a][0];
+ }
+ else
+ d[a] += ((page->table->n[a] - page->table->h[a][1])
+ - (page->n[a] - page->h[a][1]));
+
+ enum table_axis b = !axis;
+ struct map m;
+ get_map (page, b, d[b], &m);
+ d[b] += m.t0 - m.p0;
+
+ int r = table_get_rule (page->table, axis, d[H], d[V], color);
+ if (d2 >= 0)
+ {
+ d[a] = d2;
+ int r2 = table_get_rule (page->table, axis, d[H], d[V], color);
+ r = table_stroke_combine (r, r2);
+ }
+ return rule_to_render_type (r);
}
static bool
return !(z & 1);
}
+bool
+render_direction_rtl (void)
+{
+ /* TRANSLATORS: Do not translate this string. If the script of your language
+ reads from right to left (eg Persian, Arabic, Hebrew etc), then replace
+ this string with "output-direction-rtl". Otherwise either leave it
+ untranslated or copy it verbatim. */
+ const char *dir = _("output-direction-ltr");
+ if (0 == strcmp ("output-direction-rtl", dir))
+ return true;
+
+ if (0 != strcmp ("output-direction-ltr", dir))
+ fprintf (stderr, "This localisation has been incorrectly translated. "
+ "Complain to the translator.\n");
+
+ return false;
+}
+
static void
-render_rule (const struct render_page *page, const int d[TABLE_N_AXES])
+render_rule (const struct render_page *page, const int ofs[TABLE_N_AXES],
+ const int d[TABLE_N_AXES])
{
enum render_line_style styles[TABLE_N_AXES][2];
- enum table_axis a;
+ struct cell_color colors[TABLE_N_AXES][2];
- for (a = 0; a < TABLE_N_AXES; a++)
+ for (enum table_axis a = 0; a < TABLE_N_AXES; a++)
{
enum table_axis b = !a;
e[H] = d[H];
e[V] = d[V];
e[b]--;
- styles[a][0] = get_rule (page, a, e);
+ styles[a][0] = get_rule (page, a, e, &colors[a][0]);
}
- if (d[b] / 2 < page->table->n[b])
- styles[a][1] = get_rule (page, a, d);
+ if (d[b] / 2 < page->n[b])
+ styles[a][1] = get_rule (page, a, d, &colors[a][1]);
}
else
- styles[a][0] = styles[a][1] = get_rule (page, a, d);
+ {
+ styles[a][0] = styles[a][1] = get_rule (page, a, d, &colors[a][0]);
+ colors[a][1] = colors[a][0];
+ }
}
if (styles[H][0] != RENDER_LINE_NONE || styles[H][1] != RENDER_LINE_NONE
{
int bb[TABLE_N_AXES][2];
- bb[H][0] = page->cp[H][d[H]];
- bb[H][1] = page->cp[H][d[H] + 1];
- bb[V][0] = page->cp[V][d[V]];
- bb[V][1] = page->cp[V][d[V] + 1];
- page->params->draw_line (page->params->aux, bb, styles);
+ bb[H][0] = ofs[H] + page->cp[H][d[H]];
+ bb[H][1] = ofs[H] + page->cp[H][d[H] + 1];
+ if (page->params->rtl)
+ {
+ int temp = bb[H][0];
+ bb[H][0] = render_page_get_size (page, H) - bb[H][1];
+ bb[H][1] = render_page_get_size (page, H) - temp;
+ }
+ bb[V][0] = ofs[V] + page->cp[V][d[V]];
+ bb[V][1] = ofs[V] + page->cp[V][d[V] + 1];
+ page->params->ops->draw_line (page->params->aux, bb, styles, colors);
}
}
static void
-render_cell (const struct render_page *page, const struct table_cell *cell)
+render_cell (const struct render_page *page, const int ofs[TABLE_N_AXES],
+ const struct table_cell *cell)
{
- const struct render_overflow *of;
int bb[TABLE_N_AXES][2];
int clip[TABLE_N_AXES][2];
- bb[H][0] = clip[H][0] = page->cp[H][cell->d[H][0] * 2 + 1];
- bb[H][1] = clip[H][1] = page->cp[H][cell->d[H][1] * 2];
- bb[V][0] = clip[V][0] = page->cp[V][cell->d[V][0] * 2 + 1];
- bb[V][1] = clip[V][1] = page->cp[V][cell->d[V][1] * 2];
-
- of = find_overflow (page, cell->d[H][0], cell->d[V][0]);
- if (of)
+ bb[H][0] = clip[H][0] = ofs[H] + page->cp[H][cell->d[H][0] * 2 + 1];
+ bb[H][1] = clip[H][1] = ofs[H] + page->cp[H][cell->d[H][1] * 2];
+ if (page->params->rtl)
{
- enum table_axis axis;
+ int temp = bb[H][0];
+ bb[H][0] = clip[H][0] = render_page_get_size (page, H) - bb[H][1];
+ bb[H][1] = clip[H][1] = render_page_get_size (page, H) - temp;
+ }
+ bb[V][0] = clip[V][0] = ofs[V] + page->cp[V][cell->d[V][0] * 2 + 1];
+ bb[V][1] = clip[V][1] = ofs[V] + page->cp[V][cell->d[V][1] * 2];
- for (axis = 0; axis < TABLE_N_AXES; axis++)
+ enum table_valign valign = cell->style->cell_style.valign;
+ int valign_offset = 0;
+ if (valign != TABLE_VALIGN_TOP)
+ {
+ int height = page->params->ops->measure_cell_height (
+ page->params->aux, cell, bb[H][1] - bb[H][0]);
+ int extra = bb[V][1] - bb[V][0] - height;
+ if (extra > 0)
{
- if (of->overflow[axis][0])
- {
- bb[axis][0] -= of->overflow[axis][0];
- if (cell->d[axis][0] == 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])
- clip[axis][1] = page->cp[axis][cell->d[axis][1] * 2 + 1];
- }
+ if (valign == TABLE_VALIGN_CENTER)
+ extra /= 2;
+ valign_offset += extra;
}
}
- page->params->draw_cell (page->params->aux, cell, bb, clip);
+ const struct render_overflow *of = find_overflow (
+ page, cell->d[H][0], cell->d[V][0]);
+ if (of)
+ for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
+ {
+ if (of->overflow[axis][0])
+ {
+ bb[axis][0] -= of->overflow[axis][0];
+ if (cell->d[axis][0] == 0 && !page->is_edge_cutoff[axis][0])
+ clip[axis][0] = ofs[axis] + 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]
+ && !page->is_edge_cutoff[axis][1])
+ clip[axis][1] = ofs[axis] + page->cp[axis][cell->d[axis][1] * 2
+ + 1];
+ }
+ }
+
+ int spill[TABLE_N_AXES][2];
+ for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
+ {
+ spill[axis][0] = rule_width (page, axis, cell->d[axis][0]) / 2;
+ spill[axis][1] = rule_width (page, axis, cell->d[axis][1]) / 2;
+ }
+
+ int color_idx = (cell->d[V][0] < page->h[V][0]
+ || page->n[V] - (cell->d[V][0] + 1) < page->h[V][1]
+ ? 0
+ : (cell->d[V][0] - page->h[V][0]) & 1);
+ page->params->ops->draw_cell (page->params->aux, cell, color_idx,
+ bb, valign_offset, spill, clip);
}
/* Draws the cells of PAGE indicated in BB. */
static void
render_page_draw_cells (const struct render_page *page,
- int bb[TABLE_N_AXES][2])
+ int ofs[TABLE_N_AXES], int bb[TABLE_N_AXES][2])
{
- int x, y;
+ for (int y = bb[V][0]; y < bb[V][1]; y++)
+ for (int x = bb[H][0]; x < bb[H][1];)
+ if (!is_rule (x) && !is_rule (y))
+ {
+ struct table_cell cell;
+
+ render_get_cell (page, x / 2, y / 2, &cell);
+ if (y / 2 == bb[V][0] / 2 || y / 2 == cell.d[V][0])
+ render_cell (page, ofs, &cell);
+ x = rule_ofs (cell.d[H][1]);
+ }
+ else
+ x++;
- for (y = bb[V][0]; y < bb[V][1]; y++)
- for (x = bb[H][0]; x < bb[H][1]; )
+ for (int y = bb[V][0]; y < bb[V][1]; y++)
+ for (int x = bb[H][0]; x < bb[H][1]; x++)
if (is_rule (x) || is_rule (y))
{
int d[TABLE_N_AXES];
d[H] = x;
d[V] = y;
- render_rule (page, d);
- x++;
- }
- else
- {
- 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])
- render_cell (page, &cell);
- x = rule_ofs (cell.d[H][1]);
- table_cell_free (&cell);
+ render_rule (page, ofs, d);
}
}
/* Renders PAGE, by calling the 'draw_line' and 'draw_cell' functions from the
render_params provided to render_page_create(). */
-void
-render_page_draw (const struct render_page *page)
+static void
+render_page_draw (const struct render_page *page, int ofs[TABLE_N_AXES])
{
int bb[TABLE_N_AXES][2];
bb[V][0] = 0;
bb[V][1] = page->n[V] * 2 + 1;
- render_page_draw_cells (page, bb);
+ render_page_draw_cells (page, ofs, bb);
}
/* Returns the greatest value i, 0 <= i < n, such that cp[i] <= x0. */
static int
get_clip_min_extent (int x0, const int cp[], int n)
{
- int low, high, best;
-
- low = 0;
- high = n;
- best = 0;
+ int low = 0;
+ int high = n;
+ int best = 0;
while (low < high)
{
int middle = low + (high - low) / 2;
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)
{
- int low, high, best;
-
- low = 0;
- high = n;
- best = n;
+ int low = 0;
+ int high = n;
+ int best = n;
while (low < high)
{
int middle = low + (high - low) / 2;
low = middle + 1;
}
+ while (best > 0 && cp[best - 1] == cp[best])
+ best--;
+
return best;
}
/* Renders the cells of PAGE that intersect (X,Y)-(X+W,Y+H), by calling the
'draw_line' and 'draw_cell' functions from the render_params provided to
render_page_create(). */
-void
+static void
render_page_draw_region (const struct render_page *page,
- int x, int y, int w, int h)
+ int ofs[TABLE_N_AXES], int clip[TABLE_N_AXES][2])
{
int bb[TABLE_N_AXES][2];
- bb[H][0] = get_clip_min_extent (x, page->cp[H], page->n[H] * 2 + 1);
- bb[H][1] = get_clip_max_extent (x + w, page->cp[H], page->n[H] * 2 + 1);
- bb[V][0] = get_clip_min_extent (y, page->cp[V], page->n[V] * 2 + 1);
- bb[V][1] = get_clip_max_extent (y + h, page->cp[V], page->n[V] * 2 + 1);
+ bb[H][0] = get_clip_min_extent (clip[H][0], page->cp[H], page->n[H] * 2 + 1);
+ bb[H][1] = get_clip_max_extent (clip[H][1], page->cp[H], page->n[H] * 2 + 1);
+ bb[V][0] = get_clip_min_extent (clip[V][0], page->cp[V], page->n[V] * 2 + 1);
+ bb[V][1] = get_clip_max_extent (clip[V][1], page->cp[V], page->n[V] * 2 + 1);
- render_page_draw_cells (page, bb);
+ render_page_draw_cells (page, ofs, bb);
}
-\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 *,
int z1, int p1);
/* Initializes render_break B for breaking PAGE along AXIS.
-
- Ownership of PAGE is transferred to B. The caller must use
- render_page_ref() if it needs to keep a copy of PAGE. */
-void
+ Takes ownership of PAGE. */
+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)
/* 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.
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;
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;
- }
+ int pixel = 0;
+ int z;
+ 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->ops->adjust_break)
+ for (int x = 0; x < page->n[H];)
+ {
+ struct table_cell cell;
+
+ render_get_cell (page, x, z, &cell);
+ int w = joined_width (page, H, cell.d[H][0], cell.d[H][1]);
+ int better_pixel = page->params->ops->adjust_break (
+ page->params->aux, &cell, w, pixel);
+ x = cell.d[H][1];
+
+ 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;
+ }
+ }
- subpage = render_page_select (page, axis, b->cell, b->pixel,
- pixel ? cell + 1 : cell,
- pixel ? cell_width (page, axis, cell) - pixel
+ if (z == b->z && !pixel)
+ return NULL;
+
+ 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;
}
{
const struct render_page *page = b->page;
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. */
+ int 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;
}
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
+ {
+ 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);
+
+ for (size_t i = 0; i < n_footnotes; i++)
+ {
+ table_text_format (t, 0, i, 0, "%s. %s", f[i]->marker, f[i]->content);
+ 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] = table_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] = table_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;
}
\f
/* render_page_select() and helpers. */
const struct table_cell *);
/* Creates and returns a new render_page whose contents are a subregion of
- PAGE's contents. The new render_page includes cells Z0 through Z1 along
- AXIS, plus any headers on AXIS.
+ PAGE's contents. The new render_page includes cells Z0 through Z1
+ (exclusive) along AXIS, plus any headers on AXIS.
If P0 is nonzero, then it is a number of pixels to exclude from the left or
top (according to AXIS) of cell Z0. Similarly, P1 is a number of pixels to
render_page_select (const struct render_page *page, enum table_axis axis,
int z0, int p0, int z1, int p1)
{
- struct render_page_selection s;
enum table_axis a = axis;
enum table_axis b = !a;
- struct render_page *subpage;
- struct render_overflow *ro;
- int *dcp, *scp;
- int *jc;
- int z;
-
/* Optimize case where all of PAGE is selected by just incrementing the
reference count. */
}
/* Allocate subpage. */
- subpage = render_page_allocate (page->params,
- table_select_slice (
- table_ref (page->table),
- a, z0, z1, true));
+ int trim[2] = { z0 - page->h[a][0], (page->n[a] - page->h[a][1]) - z1 };
+ int n[TABLE_N_AXES] = { [H] = page->n[H], [V] = page->n[V] };
+ n[a] -= trim[0] + trim[1];
+ struct render_page *subpage = render_page_allocate__ (
+ page->params, table_ref (page->table), n);
+ for (enum table_axis k = 0; k < TABLE_N_AXES; k++)
+ {
+ subpage->h[k][0] = page->h[k][0];
+ subpage->h[k][1] = page->h[k][1];
+ subpage->r[k][0] = page->r[k][0];
+ subpage->r[k][1] = page->r[k][1];
+ }
+ subpage->r[a][0] += trim[0];
+ subpage->r[a][1] -= trim[1];
/* An edge is cut off if it was cut off in PAGE or if we're trimming pixels
off that side of the page and there are no headers. */
subpage->is_edge_cutoff[b][1] = page->is_edge_cutoff[b][1];
/* Select join crossings from PAGE into subpage. */
- jc = subpage->join_crossing[a];
- for (z = 0; z < page->h[a][0]; z++)
+ int *jc = subpage->join_crossing[a];
+ for (int z = 0; z < page->h[a][0]; z++)
*jc++ = page->join_crossing[a][z];
- for (z = z0; z <= z1; z++)
+ for (int z = z0; z <= z1; z++)
*jc++ = page->join_crossing[a][z];
- for (z = page->n[a] - page->h[a][1]; z < page->n[a]; z++)
+ for (int z = page->n[a] - page->h[a][1]; z < page->n[a]; z++)
*jc++ = page->join_crossing[a][z];
assert (jc == &subpage->join_crossing[a][subpage->n[a] + 1]);
(subpage->n[b] + 1) * sizeof **subpage->join_crossing);
/* Select widths from PAGE into subpage. */
- scp = page->cp[a];
- dcp = subpage->cp[a];
+ int *scp = page->cp[a];
+ int *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]);
- for (z = cell_ofs (z0); z <= cell_ofs (z1 - 1); z++, dcp++)
+ for (int z = 0; z <= rule_ofs (subpage->h[a][0]); z++, dcp++)
+ {
+ int w = !z && subpage->is_edge_cutoff[a][0] ? 0 : scp[z + 1] - scp[z];
+ dcp[1] = dcp[0] + w;
+ }
+ for (int z = cell_ofs (z0); z <= cell_ofs (z1 - 1); z++, dcp++)
{
dcp[1] = dcp[0] + (scp[z + 1] - scp[z]);
if (z == cell_ofs (z0))
if (z == cell_ofs (z1 - 1))
dcp[1] -= p1;
}
- for (z = rule_ofs_r (page, a, subpage->h[a][1]);
+ for (int 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++)
+ for (int z = 0; z < page->n[b] * 2 + 2; z++)
subpage->cp[b][z] = page->cp[b][z];
/* Add new overflows. */
- s.page = page;
- s.a = a;
- s.b = b;
- s.z0 = z0;
- s.z1 = z1;
- s.p0 = p0;
- s.p1 = p1;
- s.subpage = subpage;
-
- for (z = 0; z < page->n[b]; z++)
- {
- struct table_cell cell;
- int d[TABLE_N_AXES];
+ struct render_page_selection s = {
+ .page = page,
+ .a = a,
+ .b = b,
+ .z0 = z0,
+ .z1 = z1,
+ .p0 = p0,
+ .p1 = p1,
+ .subpage = subpage,
+ };
- 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);
- }
+ if (!page->h[a][0] || z0 > page->h[a][0] || p0)
+ for (int z = 0; z < page->n[b];)
+ {
+ int d[TABLE_N_AXES];
+ d[a] = z0;
+ d[b] = z;
- for (z = 0; z < page->n[b]; z++)
- {
- struct table_cell cell;
- int d[TABLE_N_AXES];
+ struct table_cell cell;
+ render_get_cell (page, d[H], d[V], &cell);
+ bool overflow0 = p0 || cell.d[a][0] < z0;
+ bool overflow1 = cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1);
+ if (overflow0 || overflow1)
+ {
+ struct render_overflow *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];
+ }
- /* 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 (int z = 0; z < page->n[b];)
+ {
+ int d[TABLE_N_AXES];
+ d[a] = z1 - 1;
+ d[b] = z;
+
+ struct table_cell cell;
+ render_get_cell (page, d[H], d[V], &cell);
+ if ((cell.d[a][1] > z1 || (cell.d[a][1] == z1 && p1))
+ && find_overflow_for_cell (&s, &cell) == NULL)
+ {
+ struct render_overflow *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];
+ }
/* Copy overflows from PAGE into subpage. */
+ struct render_overflow *ro;
HMAP_FOR_EACH (ro, struct render_overflow, node, &page->overflows)
{
struct table_cell cell;
if (cell.d[a][1] > z0 && cell.d[a][0] < z1
&& find_overflow_for_cell (&s, &cell) == NULL)
insert_overflow (&s, &cell);
- table_cell_free (&cell);
}
return subpage;
insert_overflow (struct render_page_selection *s,
const struct table_cell *cell)
{
- const struct render_overflow *old;
- struct render_overflow *of;
-
- of = xzalloc (sizeof *of);
+ struct render_overflow *of = xzalloc (sizeof *of);
cell_to_subpage (s, cell, of->d);
hmap_insert (&s->subpage->overflows, &of->node,
- hash_overflow (of->d[H], of->d[V]));
+ hash_cell (of->d[H], of->d[V]));
- old = find_overflow (s->page, cell->d[H][0], cell->d[V][0]);
+ const struct render_overflow *old
+ = find_overflow (s->page, cell->d[H][0], cell->d[V][0]);
if (old != NULL)
memcpy (of->overflow, old->overflow, sizeof of->overflow);