output: Cache the script ltr versus rtl direction.
[pspp] / src / output / cairo.c
index 49d8f0bc885a684d426e15236cb3794d94f1d1f1..61944624a7ee088f040349e591ada53edd4468ba 100644 (file)
@@ -39,6 +39,7 @@
 #include "output/charts/scree.h"
 #include "output/charts/scatterplot.h"
 #include "output/driver-provider.h"
+#include "output/group-item.h"
 #include "output/message-item.h"
 #include "output/options.h"
 #include "output/render.h"
@@ -58,6 +59,7 @@
 #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"
@@ -70,7 +72,8 @@
 #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. */
@@ -87,6 +90,10 @@ px_to_xr (int x)
   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
   {
@@ -172,7 +179,7 @@ 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]);
@@ -309,8 +316,6 @@ apply_options (struct xr_driver *xr, struct string_map *o)
   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);
@@ -373,7 +378,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
 
   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;
@@ -405,8 +410,8 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       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;
@@ -420,6 +425,7 @@ xr_set_cairo (struct xr_driver *xr, cairo_t *cairo)
       for (i = 0; i < TABLE_N_AXES; i++)
         xr->params->min_break[i] = xr->min_break[i];
       xr->params->supports_margins = true;
+      xr->params->rtl = render_direction_rtl ();
     }
 
   cairo_set_source_rgb (xr->cairo, xr->fg.red, xr->fg.green, xr->fg.blue);
@@ -667,9 +673,9 @@ dump_line (struct xr_driver *xr, int x0, int y0, int x1, int y1, int style,
                         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);
@@ -679,7 +685,7 @@ static void UNUSED
 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));
@@ -692,7 +698,7 @@ static void
 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));
@@ -798,7 +804,7 @@ xr_draw_line (void *xr_, int bb[TABLE_N_AXES][2],
   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
@@ -919,7 +925,7 @@ xr_measure_cell_height (void *xr_, const struct table_cell *cell, int width)
 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])
@@ -941,9 +947,9 @@ xr_draw_cell (void *xr_, const struct table_cell *cell,
     }
   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],
@@ -953,9 +959,9 @@ xr_draw_cell (void *xr_, const struct table_cell *cell,
 
   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++)
     {
@@ -1022,7 +1028,6 @@ xr_layout_cell_text (struct xr_driver *xr,
                      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]
@@ -1067,34 +1072,65 @@ xr_layout_cell_text (struct xr_driver *xr,
   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);
 
-      attrs = pango_attr_list_new ();
+      cell_contents_format_footnote_markers (contents, &tmp);
+      pango_layout_set_text (font->layout, ds_cstr (&tmp), ds_length (&tmp));
+
+      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)
         {
@@ -1105,6 +1141,7 @@ xr_layout_cell_text (struct xr_driver *xr,
           pango_attr_list_unref (attrs);
         }
     }
+  ds_destroy (&tmp);
 
   pango_layout_set_alignment (
     font->layout,
@@ -1354,8 +1391,7 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
   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);
     }
@@ -1372,6 +1408,9 @@ xr_rendering_create (struct xr_driver *xr, const struct output_item *item,
       r = xzalloc (sizeof *r);
       r->item = output_item_ref (item);
     }
+  else if (is_group_open_item (item))
+    r = xr_rendering_create_text (xr, to_group_open_item (item)->command_name,
+                                  cr);
 
   return r;
 }
@@ -1641,10 +1680,7 @@ xr_create_text_renderer (struct xr_driver *xr, const struct text_item *item)
   struct cell_style *style = pool_alloc (tab->container, sizeof *style);
   *style = (struct cell_style) CELL_STYLE_INITIALIZER;
   if (item->font)
-    {
-      puts (item->font);
-      style->font = pool_strdup (tab->container, item->font);
-    }
+    style->font = pool_strdup (tab->container, item->font);
   style->font_size = item->font_size;
   style->bold = item->bold;
   style->italic = item->italic;
@@ -1663,21 +1699,10 @@ static struct xr_render_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:
@@ -1701,8 +1726,7 @@ static struct xr_render_fsm *
 xr_render_message (struct xr_driver *xr,
                    const struct message_item *message_item)
 {
-  const struct msg *msg = message_item_get_msg (message_item);
-  char *s = msg_to_string (msg, message_item->command_name);
+  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);