#include "libpspp/assertion.h"
#include "libpspp/cast.h"
#include "libpspp/message.h"
+#include "libpspp/pool.h"
#include "libpspp/start-date.h"
#include "libpspp/str.h"
#include "libpspp/string-map.h"
#include <pango/pangocairo.h>
#include <stdlib.h>
+#include "gl/c-ctype.h"
#include "gl/c-strcase.h"
#include "gl/intprops.h"
#include "gl/minmax.h"
#define H TABLE_HORZ
#define V TABLE_VERT
-/* The unit used for internal measurements is inch/(72 * XR_POINT). */
+/* The unit used for internal measurements is inch/(72 * XR_POINT).
+ (Thus, XR_POINT units represent one point.) */
#define XR_POINT PANGO_SCALE
/* Conversions to and from points. */
return x * (PANGO_SCALE * 72 / 96);
}
+/* Dimensions for drawing lines in tables. */
+#define XR_LINE_WIDTH (XR_POINT / 2) /* Width of an ordinary line. */
+#define XR_LINE_SPACE XR_POINT /* Space between double lines. */
+
/* Output types. */
enum xr_output_type
{
static void xr_driver_run_fsm (struct xr_driver *);
static void xr_draw_line (void *, int bb[TABLE_N_AXES][2],
- enum render_line_style styles[TABLE_N_AXES][2]);
+ enum render_line_style styles[TABLE_N_AXES][2],
+ struct cell_color colors[TABLE_N_AXES][2]);
static void xr_measure_cell_width (void *, const struct table_cell *,
int *min, int *max);
static int xr_measure_cell_height (void *, const struct table_cell *,
int width);
-static void xr_draw_cell (void *, const struct table_cell *,
+static void xr_draw_cell (void *, const struct table_cell *, int color_idx,
int bb[TABLE_N_AXES][2],
int spill[TABLE_N_AXES][2],
int clip[TABLE_N_AXES][2]);
xr->fonts[XR_FONT_EMPHASIS].desc = parse_font_option (
d, o, "emph-font", "sans serif", font_size, false, true);
- xr->line_space = XR_POINT;
- xr->line_width = XR_POINT / 2;
xr->page_number = 0;
parse_color (d, o, "background-color", "#FFFFFFFFFFFF", &xr->bg);
xr->cairo = cairo;
- cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
+ cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
xr->char_width = 0;
xr->char_height = 0;
xr->params->font_size[H] = xr->char_width;
xr->params->font_size[V] = xr->char_height;
- int lw = xr->line_width;
- int ls = xr->line_space;
+ int lw = XR_LINE_WIDTH;
+ int ls = XR_LINE_SPACE;
for (i = 0; i < TABLE_N_AXES; i++)
{
xr->params->line_widths[i][RENDER_LINE_NONE] = 0;
int *width, int *height, int *brk);
static void
-dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style)
+dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
+ const struct cell_color *color)
{
cairo_new_path (xr->cairo);
+ cairo_set_source_rgb (xr->cairo,
+ color->r / 255.0, color->g / 255.0, color->b / 255.0);
cairo_set_line_width (
xr->cairo,
- xr_to_pt (style == RENDER_LINE_THICK ? xr->line_width * 2
- : style == RENDER_LINE_THIN ? xr->line_width / 2
- : xr->line_width));
+ xr_to_pt (style == RENDER_LINE_THICK ? XR_LINE_WIDTH * 2
+ : style == RENDER_LINE_THIN ? XR_LINE_WIDTH / 2
+ : XR_LINE_WIDTH));
cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
cairo_stroke (xr->cairo);
dump_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
{
cairo_new_path (xr->cairo);
- cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
+ cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
cairo_move_to (xr->cairo, xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y));
cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y0 + xr->y));
cairo_line_to (xr->cairo, xr_to_pt (x1 + xr->x), xr_to_pt (y1 + xr->y));
fill_rectangle (struct xr_driver *xr, int x0, int y0, int x1, int y1)
{
cairo_new_path (xr->cairo);
- cairo_set_line_width (xr->cairo, xr_to_pt (xr->line_width));
+ cairo_set_line_width (xr->cairo, xr_to_pt (XR_LINE_WIDTH));
cairo_rectangle (xr->cairo,
xr_to_pt (x0 + xr->x), xr_to_pt (y0 + xr->y),
xr_to_pt (x1 - x0), xr_to_pt (y1 - y0));
static void
horz_line (struct xr_driver *xr, int x0, int x1, int x2, int x3, int y,
enum render_line_style left, enum render_line_style right,
+ const struct cell_color *left_color,
+ const struct cell_color *right_color,
bool shorten)
{
- if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten)
- dump_line (xr, x0, y, x3, y, left);
+ if (left != RENDER_LINE_NONE && right != RENDER_LINE_NONE && !shorten
+ && cell_color_equal (left_color, right_color))
+ dump_line (xr, x0, y, x3, y, left, left_color);
else
{
if (left != RENDER_LINE_NONE)
- dump_line (xr, x0, y, shorten ? x1 : x2, y, left);
+ dump_line (xr, x0, y, shorten ? x1 : x2, y, left, left_color);
if (right != RENDER_LINE_NONE)
- dump_line (xr, shorten ? x2 : x1, y, x3, y, right);
+ dump_line (xr, shorten ? x2 : x1, y, x3, y, right, right_color);
}
}
static void
vert_line (struct xr_driver *xr, int y0, int y1, int y2, int y3, int x,
enum render_line_style top, enum render_line_style bottom,
+ const struct cell_color *top_color,
+ const struct cell_color *bottom_color,
bool shorten)
{
- if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten)
- dump_line (xr, x, y0, x, y3, top);
+ if (top != RENDER_LINE_NONE && bottom != RENDER_LINE_NONE && !shorten
+ && cell_color_equal (top_color, bottom_color))
+ dump_line (xr, x, y0, x, y3, top, top_color);
else
{
if (top != RENDER_LINE_NONE)
- dump_line (xr, x, y0, x, shorten ? y1 : y2, top);
+ dump_line (xr, x, y0, x, shorten ? y1 : y2, top, top_color);
if (bottom != RENDER_LINE_NONE)
- dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom);
+ dump_line (xr, x, shorten ? y2 : y1, x, y3, bottom, bottom_color);
}
}
static void
xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
- enum render_line_style styles[TABLE_N_AXES][2])
+ enum render_line_style styles[TABLE_N_AXES][2],
+ struct cell_color colors[TABLE_N_AXES][2])
{
const int x0 = bb[H][0];
const int y0 = bb[V][0];
const int y3 = bb[V][1];
const int top = styles[H][0];
const int bottom = styles[H][1];
- const int start_of_line = render_direction_rtl() ? styles[V][1]: styles[V][0];
- const int end_of_line = render_direction_rtl() ? styles[V][0]: styles[V][1];
+
+ int start_side = render_direction_rtl();
+ int end_side = !start_side;
+ const int start_of_line = styles[V][start_side];
+ const int end_of_line = styles[V][end_side];
+ const struct cell_color *top_color = &colors[H][0];
+ const struct cell_color *bottom_color = &colors[H][1];
+ const struct cell_color *start_color = &colors[V][start_side];
+ const struct cell_color *end_color = &colors[V][end_side];
/* The algorithm here is somewhat subtle, to allow it to handle
all the kinds of intersections that we need.
struct xr_driver *xr = xr_;
/* Offset from center of each line in a pair of double lines. */
- int double_line_ofs = (xr->line_space + xr->line_width) / 2;
+ int double_line_ofs = (XR_LINE_SPACE + XR_LINE_WIDTH) / 2;
/* Are the lines along each axis single or double?
(It doesn't make sense to have different kinds of line on the
int y2 = yc + vert_line_ofs;
if (!double_horz)
- horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line, shorten_yc_line);
+ horz_line (xr, x0, x1, x2, x3, yc, start_of_line, end_of_line,
+ start_color, end_color, shorten_yc_line);
else
{
- horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line, shorten_y1_lines);
- horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line, shorten_y2_lines);
+ horz_line (xr, x0, x1, x2, x3, y1, start_of_line, end_of_line,
+ start_color, end_color, shorten_y1_lines);
+ horz_line (xr, x0, x1, x2, x3, y2, start_of_line, end_of_line,
+ start_color, end_color, shorten_y2_lines);
}
if (!double_vert)
- vert_line (xr, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
+ vert_line (xr, y0, y1, y2, y3, xc, top, bottom, top_color, bottom_color,
+ shorten_xc_line);
else
{
- vert_line (xr, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
- vert_line (xr, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
+ vert_line (xr, y0, y1, y2, y3, x1, top, bottom, top_color, bottom_color,
+ shorten_x1_lines);
+ vert_line (xr, y0, y1, y2, y3, x2, top, bottom, top_color, bottom_color,
+ shorten_x2_lines);
}
}
static void xr_clip (struct xr_driver *, int clip[TABLE_N_AXES][2]);
static void
-xr_draw_cell (void *xr_, const struct table_cell *cell,
+xr_draw_cell (void *xr_, const struct table_cell *cell, int color_idx,
int bb[TABLE_N_AXES][2],
int spill[TABLE_N_AXES][2],
int clip[TABLE_N_AXES][2])
}
xr_clip (xr, bg_clip);
cairo_set_source_rgb (xr->cairo,
- cell->style->bg.r / 255.,
- cell->style->bg.g / 255.,
- cell->style->bg.b / 255.);
+ cell->style->bg[color_idx].r / 255.,
+ cell->style->bg[color_idx].g / 255.,
+ cell->style->bg[color_idx].b / 255.);
fill_rectangle (xr,
bb[H][0] - spill[H][0],
bb[V][0] - spill[V][0],
cairo_save (xr->cairo);
cairo_set_source_rgb (xr->cairo,
- cell->style->fg.r / 255.,
- cell->style->fg.g / 255.,
- cell->style->fg.b / 255.);
+ cell->style->fg[color_idx].r / 255.,
+ cell->style->fg[color_idx].g / 255.,
+ cell->style->fg[color_idx].b / 255.);
for (int axis = 0; axis < TABLE_N_AXES; axis++)
{
int *widthp, int *brk)
{
unsigned int options = contents->options;
- size_t length;
int w, h;
struct xr_font *font = (options & TAB_FIX ? &xr->fonts[XR_FONT_FIXED]
else
footnote_adjustment = px_to_xr (style->margin[H][1]);
- length = strlen (contents->text);
- if (footnote_adjustment)
+ struct string tmp = DS_EMPTY_INITIALIZER;
+ const char *text = contents->text;
+
+ /* Deal with an oddity of the Unicode line-breaking algorithm (or perhaps in
+ Pango's implementation of it): it will break after a period or a comma
+ that precedes a digit, e.g. in ".000" it will break after the period.
+ This code looks for such a situation and inserts a U+2060 WORD JOINER
+ to prevent the break.
+
+ This isn't necessary when the decimal point is between two digits
+ (e.g. "0.000" won't be broken) or when the display width is not limited so
+ that word wrapping won't happen.
+
+ It isn't necessary to look for more than one period or comma, as would
+ happen with grouping like 1,234,567.89 or 1.234.567,89 because if groups
+ are present then there will always be a digit on both sides of every
+ period and comma. */
+ if (bb[H][1] != INT_MAX)
{
- PangoAttrList *attrs;
- struct string s;
+ const char *decimal = text + strcspn (text, ".,");
+ if (decimal[0]
+ && c_isdigit (decimal[1])
+ && (decimal == text || !c_isdigit (decimal[-1])))
+ {
+ ds_extend (&tmp, strlen (text) + 16);
+ ds_put_substring (&tmp, ss_buffer (text, decimal - text + 1));
+ ds_put_unichar (&tmp, 0x2060 /* U+2060 WORD JOINER */);
+ ds_put_cstr (&tmp, decimal + 1);
+ }
+ }
+ if (footnote_adjustment)
+ {
bb[H][1] += footnote_adjustment;
- ds_init_empty (&s);
- ds_extend (&s, length + contents->n_footnotes * 10);
- ds_put_cstr (&s, contents->text);
- cell_contents_format_footnote_markers (contents, &s);
- pango_layout_set_text (font->layout, ds_cstr (&s), ds_length (&s));
- ds_destroy (&s);
+ if (ds_is_empty (&tmp))
+ {
+ ds_extend (&tmp, strlen (text) + 16);
+ ds_put_cstr (&tmp, text);
+ }
+ size_t initial_length = ds_length (&tmp);
+
+ cell_contents_format_footnote_markers (contents, &tmp);
+ pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
- attrs = pango_attr_list_new ();
+ PangoAttrList *attrs = pango_attr_list_new ();
if (style->underline)
pango_attr_list_insert (attrs, pango_attr_underline_new (
PANGO_UNDERLINE_SINGLE));
- add_attr_with_start (attrs, pango_attr_rise_new (7000), length);
+ add_attr_with_start (attrs, pango_attr_rise_new (7000), initial_length);
add_attr_with_start (
- attrs, pango_attr_font_desc_new (font->desc), length);
+ attrs, pango_attr_font_desc_new (font->desc), initial_length);
pango_layout_set_attributes (font->layout, attrs);
pango_attr_list_unref (attrs);
}
else
{
- pango_layout_set_text (font->layout, contents->text, -1);
+ const char *content = ds_is_empty (&tmp) ? text : ds_cstr (&tmp);
+ pango_layout_set_text (font->layout, content, -1);
if (style->underline)
{
pango_attr_list_unref (attrs);
}
}
+ ds_destroy (&tmp);
pango_layout_set_alignment (
font->layout,
if (0)
{
if (best && !xr->nest)
- {
- cairo_save (xr->cairo);
- cairo_set_source_rgb (xr->cairo, 0, 1, 0);
- dump_line (xr, -xr->left_margin, best,
- xr->width + xr->right_margin, best,
- RENDER_LINE_SINGLE);
- cairo_restore (xr->cairo);
- }
+ dump_line (xr, -xr->left_margin, best,
+ xr->width + xr->right_margin, best,
+ RENDER_LINE_SINGLE, &CELL_COLOR (0, 255, 0));
}
}
else if (is_message_item (item))
{
const struct message_item *message_item = to_message_item (item);
- const struct msg *msg = message_item_get_msg (message_item);
- char *s = msg_to_string (msg, NULL);
+ char *s = msg_to_string (message_item_get_msg (message_item));
r = xr_rendering_create_text (xr, s, cr);
free (s);
}
}
\f
static struct xr_render_fsm *
-xr_create_text_renderer (struct xr_driver *xr, const char *text)
+xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
{
- struct table_item *table_item;
- struct xr_render_fsm *fsm;
-
- table_item = table_item_create (table_from_string (TAB_LEFT, text),
- NULL, NULL);
- fsm = xr_render_table (xr, table_item);
+ struct tab_table *tab = tab_create (1, 1);
+
+ struct cell_style *style = pool_alloc (tab->container, sizeof *style);
+ *style = (struct cell_style) CELL_STYLE_INITIALIZER;
+ if (item->font)
+ style->font = pool_strdup (tab->container, item->font);
+ style->font_size = item->font_size;
+ style->bold = item->bold;
+ style->italic = item->italic;
+ style->underline = item->underline;
+ tab->styles[0] = style;
+
+ tab_text (tab, 0, 0, TAB_LEFT, text_item_get_text (item));
+ struct table_item *table_item = table_item_create (&tab->table, NULL, NULL);
+ struct xr_render_fsm *fsm = xr_render_table (xr, table_item);
table_item_unref (table_item);
return fsm;
xr_render_text (struct xr_driver *xr, const struct text_item *text_item)
{
enum text_item_type type = text_item_get_type (text_item);
- const char *text = text_item_get_text (text_item);
switch (type)
{
- case TEXT_ITEM_TITLE:
- free (xr->title);
- xr->title = xstrdup (text);
- break;
-
- case TEXT_ITEM_SUBTITLE:
- free (xr->subtitle);
- xr->subtitle = xstrdup (text);
- break;
-
- case TEXT_ITEM_COMMAND_CLOSE:
+ case TEXT_ITEM_PAGE_TITLE:
break;
case TEXT_ITEM_BLANK_LINE:
break;
default:
- return xr_create_text_renderer (xr, text);
+ return xr_create_text_renderer (xr, text_item);
}
return NULL;
xr_render_message (struct xr_driver *xr,
const struct message_item *message_item)
{
- const struct msg *msg = message_item_get_msg (message_item);
- struct xr_render_fsm *fsm;
- char *s;
-
- s = msg_to_string (msg, message_item->command_name);
- fsm = xr_create_text_renderer (xr, s);
+ char *s = msg_to_string (message_item_get_msg (message_item));
+ struct text_item *item = text_item_create (TEXT_ITEM_PARAGRAPH, s);
free (s);
+ struct xr_render_fsm *fsm = xr_create_text_renderer (xr, item);
+ text_item_unref (item);
return fsm;
}