output: Use Cairo and Pango to draw charts, instead of libplot.
[pspp-builds.git] / src / output / cairo.c
index a60b5ebd04aeddfefbf8b892f761ed889790fd09..b8fc486e89e66e1fe218e9543e31eb43b4afe3d9 100644 (file)
 
 #include <config.h>
 
+#include <output/cairo.h>
+
 #include <libpspp/assertion.h>
 #include <libpspp/start-date.h>
 #include <libpspp/version.h>
 #include <output/afm.h>
-#include <output/chart.h>
+#include <output/chart-provider.h>
 #include <output/manager.h>
 #include <output/output.h>
 
@@ -55,9 +57,9 @@
    top-margin=0.5in
    bottom-margin=0.5in
 
-   prop-font=Times-Roman
-   emph-font=Times-Italic
-   fixed-font=Courier
+   prop-font=serif
+   emph-font=serif italic
+   fixed-font=monospace
    font-size=10000
 
    line-gutter=1pt
    line-width=0.5pt
  */
 
-/* Measurements.  We use the same scale as Pango, for simplicity. */
+/* Measurements as we present to the rest of PSPP. */
 #define XR_POINT PANGO_SCALE
 #define XR_INCH (XR_POINT * 72)
 
+/* Conversions to and from points. */
+static double
+xr_to_pt (int x)
+{
+  return x / (double) XR_POINT;
+}
+
+static int
+pt_to_xr (double x)
+{
+  return x * XR_POINT + 0.5;
+}
+
 /* Output types. */
 enum xr_output_type
   {
@@ -89,29 +104,36 @@ struct xr_font
 /* Cairo output driver extension record. */
 struct xr_driver_ext
   {
-    char *file_name;            /* Output file name. */
-    enum xr_output_type file_type; /* Type of output file. */
     cairo_t *cairo;
+    struct xr_font fonts[OUTP_FONT_CNT];
 
     bool draw_headers;          /* Draw headers at top of page? */
     int page_number;           /* Current page number. */
 
+    int line_gutter;           /* Space around lines. */
+    int line_space;            /* Space between lines. */
+    int line_width;            /* Width of lines. */
+  };
+
+struct xr_driver_options
+  {
+    struct outp_driver *driver;
+
+    char *file_name;            /* Output file name. */
+    enum xr_output_type file_type; /* Type of output file. */
+
+
     bool portrait;              /* Portrait mode? */
+
     int paper_width;            /* Width of paper before dropping margins. */
     int paper_length;           /* Length of paper before dropping margins. */
     int left_margin;           /* Left margin in XR units. */
     int right_margin;          /* Right margin in XR units. */
     int top_margin;            /* Top margin in XR units. */
     int bottom_margin;         /* Bottom margin in XR units. */
-
-    int line_gutter;           /* Space around lines. */
-    int line_space;            /* Space between lines. */
-    int line_width;            /* Width of lines. */
-
-    struct xr_font fonts[OUTP_FONT_CNT];
   };
 
-static bool handle_option (struct outp_driver *this, const char *key,
+static bool handle_option (void *options, const char *key,
                            const struct string *val);
 static void draw_headers (struct outp_driver *this);
 
@@ -121,32 +143,18 @@ static int text_width (struct outp_driver *, const char *, enum outp_font);
 \f
 /* Driver initialization. */
 
-static bool
-xr_open_driver (struct outp_driver *this, struct substring options)
+static struct outp_driver *
+xr_allocate (const char *name, int types)
 {
-  cairo_surface_t *surface;
-  cairo_status_t status;
+  struct outp_driver *this;
   struct xr_driver_ext *x;
-  double width_pt, length_pt;
   size_t i;
 
+  this = outp_allocate_driver (&cairo_class, name, types);
   this->width = this->length = 0;
   this->font_height = XR_POINT * 10;
-
-  this->ext = x = xmalloc (sizeof *x);
-  x->file_name = xstrdup ("pspp.pdf");
-  x->file_type = XR_PDF;
-  x->draw_headers = true;
-  x->page_number = 0;
-  x->portrait = true;
-  outp_get_paper_size ("", &x->paper_width, &x->paper_length);
-  x->left_margin = XR_INCH / 2;
-  x->right_margin = XR_INCH / 2;
-  x->top_margin = XR_INCH / 2;
-  x->bottom_margin = XR_INCH / 2;
-  x->line_gutter = XR_POINT;
-  x->line_space = XR_POINT;
-  x->line_width = XR_POINT / 2;
+  this->ext = x = xzalloc (sizeof *x);
+  x->cairo = NULL;
   x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
   x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
   x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
@@ -157,32 +165,109 @@ xr_open_driver (struct outp_driver *this, struct substring options)
       font->metrics = NULL;
       font->layout = NULL;
     }
+  x->draw_headers = true;
+  x->page_number = 0;
+  x->line_gutter = XR_POINT;
+  x->line_space = XR_POINT;
+  x->line_width = XR_POINT / 2;
 
-  outp_parse_options (options, handle_option, this);
+  return this;
+}
 
-  if (x->portrait)
+static bool
+xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
+{
+  struct xr_driver_ext *x = this->ext;
+  int i;
+
+  x->cairo = cairo;
+
+  cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
+
+  for (i = 0; i < OUTP_FONT_CNT; i++)
+    if (!load_font (this, &x->fonts[i]))
+      return false;
+
+  this->fixed_width = text_width (this, "0", OUTP_FIXED);
+  this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
+
+  this->horiz_line_width[OUTP_L_NONE] = 0;
+  this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
+  this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
+                                           + 2 * x->line_width);
+  memcpy (this->vert_line_width, this->horiz_line_width,
+          sizeof this->vert_line_width);
+
+  return true;
+}
+
+struct outp_driver *
+xr_create_driver (cairo_t *cairo)
+{
+  struct outp_driver *this;
+
+  this = xr_allocate ("cairo", 0);
+  this->width = INT_MAX / 8;
+  this->length = INT_MAX / 8;
+  if (!xr_set_cairo (this, cairo))
     {
-      this->width = x->paper_width;
-      this->length = x->paper_length;
+      this->class->close_driver (this);
+      outp_free_driver (this);
+      return NULL;
+    }
+  return this;
+}
+
+static bool
+xr_open_driver (const char *name, int types, struct substring option_string)
+{
+  struct outp_driver *this;
+  struct xr_driver_ext *x;
+  struct xr_driver_options options;
+  cairo_surface_t *surface;
+  cairo_status_t status;
+  double width_pt, length_pt;
+
+  this = xr_allocate (name, types);
+  x = this->ext;
+
+  options.driver = this;
+  options.file_name = xstrdup ("pspp.pdf");
+  options.file_type = XR_PDF;
+  options.portrait = true;
+  outp_get_paper_size ("", &options.paper_width, &options.paper_length);
+  options.left_margin = XR_INCH / 2;
+  options.right_margin = XR_INCH / 2;
+  options.top_margin = XR_INCH / 2;
+  options.bottom_margin = XR_INCH / 2;
+
+  outp_parse_options (this->name, option_string, handle_option, &options);
+
+  width_pt = options.paper_width / 1000.0;
+  length_pt = options.paper_length / 1000.0;
+  if (options.portrait)
+    {
+      this->width = pt_to_xr (width_pt);
+      this->length = pt_to_xr (length_pt);
     }
   else
     {
-      this->width = x->paper_length;
-      this->length = x->paper_width;
+      this->width = pt_to_xr (width_pt);
+      this->length = pt_to_xr (length_pt);
     }
   if (x->draw_headers)
-    x->top_margin += 3 * this->font_height;
-  this->width -= x->left_margin + x->right_margin;
-  this->length -= x->top_margin + x->bottom_margin;
-
-  width_pt = x->paper_width / (double) XR_POINT;
-  length_pt = x->paper_length / (double) XR_POINT;
-  if (x->file_type == XR_PDF)
-    surface = cairo_pdf_surface_create (x->file_name, width_pt, length_pt);
-  else if (x->file_type == XR_PS)
-    surface = cairo_ps_surface_create (x->file_name, width_pt, length_pt);
-  else if (x->file_type == XR_SVG)
-    surface = cairo_svg_surface_create (x->file_name, width_pt, length_pt);
+    options.top_margin += 3 * this->font_height;
+  this->width -= options.left_margin + options.right_margin;
+  this->length -= options.top_margin + options.bottom_margin;
+
+  if (options.file_type == XR_PDF)
+    surface = cairo_pdf_surface_create (options.file_name,
+                                        width_pt, length_pt);
+  else if (options.file_type == XR_PS)
+    surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
+  else if (options.file_type == XR_SVG)
+    surface = cairo_svg_surface_create (options.file_name,
+                                        width_pt, length_pt);
   else
     NOT_REACHED ();
 
@@ -190,7 +275,7 @@ xr_open_driver (struct outp_driver *this, struct substring options)
   if (status != CAIRO_STATUS_SUCCESS)
     {
       error (0, 0, _("opening output file \"%s\": %s"),
-             x->file_name, cairo_status_to_string (status));
+             options.file_name, cairo_status_to_string (status));
       cairo_surface_destroy (surface);
       goto error;
     }
@@ -198,13 +283,9 @@ xr_open_driver (struct outp_driver *this, struct substring options)
   x->cairo = cairo_create (surface);
   cairo_surface_destroy (surface);
 
-  cairo_scale (x->cairo, 1.0 / PANGO_SCALE, 1.0 / PANGO_SCALE);
-  cairo_translate (x->cairo, x->left_margin, x->top_margin);
-  cairo_set_line_width (x->cairo, x->line_width);
-
-  for (i = 0; i < OUTP_FONT_CNT; i++)
-    if (!load_font (this, &x->fonts[i]))
-      goto error;
+  cairo_translate (x->cairo,
+                   xr_to_pt (options.left_margin),
+                   xr_to_pt (options.top_margin));
 
   if (this->length / this->font_height < 15)
     {
@@ -216,20 +297,17 @@ xr_open_driver (struct outp_driver *this, struct substring options)
       goto error;
     }
 
-  this->fixed_width = text_width (this, "0", OUTP_FIXED);
-  this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
-
-  this->horiz_line_width[OUTP_L_NONE] = 0;
-  this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
-  this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
-                                           + 2 * x->line_width);
-  memcpy (this->vert_line_width, this->horiz_line_width,
-          sizeof this->vert_line_width);
+  if (!xr_set_cairo (this, x->cairo))
+    goto error;
 
+  outp_register_driver (this);
+  free (options.file_name);
   return true;
 
  error:
   this->class->close_driver (this);
+  outp_free_driver (this);
+  free (options.file_name);
   return false;
 }
 
@@ -247,12 +325,11 @@ xr_close_driver (struct outp_driver *this)
       cairo_surface_finish (cairo_get_target (x->cairo));
       status = cairo_status (x->cairo);
       if (status != CAIRO_STATUS_SUCCESS)
-        error (0, 0, _("writing output file \"%s\": %s"),
-               x->file_name, cairo_status_to_string (status));
+        error (0, 0, _("error writing output file for %s driver: %s"),
+               this->name, cairo_status_to_string (status));
       cairo_destroy (x->cairo);
     }
 
-  free (x->file_name);
   for (i = 0; i < OUTP_FONT_CNT; i++)
     free_font (&x->fonts[i]);
   free (x);
@@ -300,9 +377,10 @@ static const struct outp_option option_tab[] =
 };
 
 static bool
-handle_option (struct outp_driver *this, const char *key,
-               const struct string *val)
+handle_option (void *options_, const char *key, const struct string *val)
 {
+  struct xr_driver_options *options = options_;
+  struct outp_driver *this = options->driver;
   struct xr_driver_ext *x = this->ext;
   int subcat;
   char *value = ds_cstr (val);
@@ -315,16 +393,16 @@ handle_option (struct outp_driver *this, const char *key,
                "driver"), key);
       break;
     case output_file_arg:
-      free (x->file_name);
-      x->file_name = xstrdup (value);
+      free (options->file_name);
+      options->file_name = xstrdup (value);
       break;
     case output_type_arg:
       if (!strcmp (value, "pdf"))
-        x->file_type = XR_PDF;
+        options->file_type = XR_PDF;
       else if (!strcmp (value, "ps"))
-        x->file_type = XR_PS;
+        options->file_type = XR_PS;
       else if (!strcmp (value, "svg"))
-        x->file_type = XR_SVG;
+        options->file_type = XR_SVG;
       else
         {
           error (0, 0, _("unknown Cairo output type \"%s\""), value);
@@ -332,13 +410,14 @@ handle_option (struct outp_driver *this, const char *key,
         }
       break;
     case paper_size_arg:
-      outp_get_paper_size (value, &x->paper_width, &x->paper_length);
+      outp_get_paper_size (value,
+                           &options->paper_width, &options->paper_length);
       break;
     case orientation_arg:
       if (!strcmp (value, "portrait"))
-       x->portrait = true;
+       options->portrait = true;
       else if (!strcmp (value, "landscape"))
-       x->portrait = false;
+       options->portrait = false;
       else
        error (0, 0, _("unknown orientation `%s' (valid orientations are "
                        "`portrait' and `landscape')"), value);
@@ -365,16 +444,16 @@ handle_option (struct outp_driver *this, const char *key,
        switch (subcat)
          {
          case 0:
-           x->left_margin = dimension;
+           options->left_margin = dimension;
            break;
          case 1:
-           x->right_margin = dimension;
+           options->right_margin = dimension;
            break;
          case 2:
-           x->top_margin = dimension;
+           options->top_margin = dimension;
            break;
          case 3:
-           x->bottom_margin = dimension;
+           options->bottom_margin = dimension;
            break;
          case 4:
            this->font_height = dimension;
@@ -425,15 +504,24 @@ xr_close_page (struct outp_driver *this)
 }
 
 static void
-xr_submit (struct outp_driver *this UNUSED, struct som_entity *s)
+xr_output_chart (struct outp_driver *this, const struct chart *chart)
 {
-  switch (s->type)
-    {
-    case SOM_CHART:
-      break;
-    default:
-      NOT_REACHED ();
-    }
+  struct xr_driver_ext *x = this->ext;
+  struct chart_geometry geom;
+
+  outp_eject_page (this);
+  outp_open_page (this);
+
+  cairo_save (x->cairo);
+  cairo_translate (x->cairo, 0.0, xr_to_pt (this->length));
+  cairo_scale (x->cairo, 1.0, -1.0);
+  chart_geometry_init (x->cairo, &geom,
+                       xr_to_pt (this->width), xr_to_pt (this->length));
+  chart_draw (chart, x->cairo, &geom);
+  chart_geometry_free (x->cairo);
+  cairo_restore (x->cairo);
+
+  outp_close_page (this);
 }
 \f
 /* Draws a line from (x0,y0) to (x1,y1). */
@@ -442,8 +530,8 @@ dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
 {
   struct xr_driver_ext *x = this->ext;
   cairo_new_path (x->cairo);
-  cairo_move_to (x->cairo, x0, y0);
-  cairo_line_to (x->cairo, x1, y1);
+  cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
+  cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
   cairo_stroke (x->cairo);
 }
 
@@ -668,9 +756,9 @@ draw_headers (struct outp_driver *this)
   x1 = this->width - this->prop_em_width;
 
   /* Draw box. */
-  cairo_rectangle (ext->cairo, 0, y, this->width,
-                   2 * (this->font_height
-                        + ext->line_width + ext->line_gutter));
+  cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
+                   xr_to_pt (2 * (this->font_height
+                                  + ext->line_width + ext->line_gutter)));
   cairo_save (ext->cairo);
   cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
   cairo_fill_preserve (ext->cairo);
@@ -732,11 +820,9 @@ text (struct outp_driver *this, const struct outp_text *text, bool draw,
             }
         }
       cairo_save (ext->cairo);
-      cairo_translate (ext->cairo, text->x, text->y);
-      cairo_scale (ext->cairo, PANGO_SCALE, PANGO_SCALE);
+      cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
       pango_cairo_show_layout (ext->cairo, font->layout);
       cairo_restore (ext->cairo);
-      pango_cairo_update_layout (ext->cairo, font->layout);
     }
 
   if (width != NULL || height != NULL)
@@ -760,28 +846,9 @@ xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
 static void
 xr_text_draw (struct outp_driver *this, const struct outp_text *t)
 {
-  assert (this->page_open);
   text (this, t, true, NULL, NULL);
 }
 \f
-static void
-xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
-{
-#ifdef NO_CHARTS
-  ch->lp = NULL;
-#else
-  /* XXX libplot doesn't support Cairo yet. */
-#endif
-}
-
-static void
-xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
-{
-#ifndef NO_CHARTS
-  /* XXX libplot doesn't support Cairo yet. */
-#endif
-}
-\f
 /* Attempts to load FONT, initializing its other members based on
    its 'string' member and the information in THIS.  Returns true
    if successful, otherwise false. */
@@ -801,7 +868,6 @@ load_font (struct outp_driver *this, struct xr_font *font)
   pango_font_description_set_absolute_size (font->desc, this->font_height);
 
   font->layout = pango_cairo_create_layout (x->cairo);
-      pango_cairo_update_layout (x->cairo, font->layout);
   pango_layout_set_font_description (font->layout, font->desc);
 
   language = pango_language_get_default ();
@@ -835,12 +901,11 @@ const struct outp_class cairo_class =
   xr_close_page,
   NULL,
 
-  xr_submit,
+  xr_output_chart,
+
+  NULL,
 
   xr_line,
   xr_text_metrics,
   xr_text_draw,
-
-  xr_chart_initialise,
-  xr_chart_finalise
 };