Implement SET LEADZERO.
[pspp] / src / output / spv / spv-writer.c
index b8a4768fc00e300818b772e183cc7e2fab963960..3d56429d6604c5ca6b90426b457fdffa4416b0ae 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "output/spv/spv-writer.h"
 
+#include <cairo.h>
 #include <inttypes.h>
 #include <libxml/xmlwriter.h>
 #include <math.h>
 #include "libpspp/temp-file.h"
 #include "libpspp/version.h"
 #include "libpspp/zip-writer.h"
-#include "output/page-setup-item.h"
+#include "output/cairo-chart.h"
+#include "output/driver.h"
+#include "output/output-item.h"
+#include "output/page-setup.h"
 #include "output/pivot-table.h"
-#include "output/text-item.h"
 
 #include "gl/xalloc.h"
 #include "gl/xvasprintf.h"
@@ -58,6 +61,8 @@ struct spv_writer
     bool need_page_break;
   };
 
+static void spv_writer_close_heading (struct spv_writer *);
+
 char * WARN_UNUSED_RESULT
 spv_writer_open (const char *filename, struct spv_writer **writerp)
 {
@@ -93,14 +98,6 @@ spv_writer_close (struct spv_writer *w)
   return error;
 }
 
-void
-spv_writer_set_page_setup (struct spv_writer *w,
-                           const struct page_setup *page_setup)
-{
-  page_setup_destroy (w->page_setup);
-  w->page_setup = page_setup_clone (page_setup);
-}
-
 static void
 write_attr (struct spv_writer *w, const char *name, const char *value)
 {
@@ -197,8 +194,9 @@ spv_writer_open_file (struct spv_writer *w)
 
   time_t t = time (NULL);
   struct tm *tm = gmtime (&t);
-  char *tm_s = asctime (tm);
-  write_attr (w, "creation-date-time", tm_s);
+  char tm_s[128];
+  if (strftime (tm_s, sizeof tm_s, "%x %X", tm))
+    write_attr (w, "creation-date-time", tm_s);
 
   write_attr (w, "creator", version);
 
@@ -224,9 +222,8 @@ spv_writer_open_file (struct spv_writer *w)
   return true;
 }
 
-void
-spv_writer_open_heading (struct spv_writer *w, const char *command_id,
-                         const char *label)
+static void
+spv_writer_open_heading (struct spv_writer *w, const struct output_item *item)
 {
   if (!w->heading)
     {
@@ -236,12 +233,15 @@ spv_writer_open_heading (struct spv_writer *w, const char *command_id,
 
   w->heading_depth++;
   start_elem (w, "heading");
-  write_attr (w, "commandName", command_id);
+  if (item->command_name)
+    write_attr (w, "commandName", item->command_name);
+  if (!item->show)
+    write_attr (w, "visibility", "collapsed");
   /* XXX locale */
   /* XXX olang */
 
   start_elem (w, "label");
-  write_text (w, label);
+  write_text (w, output_item_get_label (item));
   end_elem (w);
 }
 
@@ -263,7 +263,7 @@ spv_writer_close_file (struct spv_writer *w, const char *infix)
   w->heading = NULL;
 }
 
-void
+static void
 spv_writer_close_heading (struct spv_writer *w)
 {
   const char *infix = "";
@@ -279,54 +279,92 @@ spv_writer_close_heading (struct spv_writer *w)
 }
 
 static void
-start_container (struct spv_writer *w)
+open_container (struct spv_writer *w, const struct output_item *item,
+                const char *inner_elem)
 {
   start_elem (w, "container");
-  write_attr (w, "visibility", "visible");
+  write_attr (w, "visibility", item->show ? "visible" : "hidden");
   if (w->need_page_break)
     {
       write_attr (w, "page-break-before", "always");
       w->need_page_break = false;
     }
+
+  start_elem (w, "label");
+  write_text (w, output_item_get_label (item));
+  end_elem (w);
+
+  start_elem (w, inner_elem);
+  if (item->command_name)
+    write_attr (w, "commandName", item->command_name);
 }
 
-void
-spv_writer_put_text (struct spv_writer *w, const struct text_item *text,
-                     const char *command_id)
+static void
+close_container (struct spv_writer *w)
+{
+  end_elem (w);
+  end_elem (w);
+}
+
+static void
+spv_writer_put_text (struct spv_writer *w, struct output_item *item)
 {
   bool initial_depth = w->heading_depth;
   if (!initial_depth)
     spv_writer_open_file (w);
 
-  start_container (w);
+  open_container (w, item, "vtx:text");
+  write_attr (w, "type",
+              (item->text.subtype == TEXT_ITEM_TITLE ? "title"
+               : item->text.subtype == TEXT_ITEM_PAGE_TITLE ? "page-title"
+               : "log"));
 
-  start_elem (w, "label");
-  write_text (w, (text->type == TEXT_ITEM_TITLE ? "Title"
-                  : text->type == TEXT_ITEM_PAGE_TITLE ? "Page Title"
-                  : "Log"));
+  start_elem (w, "html");
+  char *s = text_item_get_plain_text (item);
+  write_text (w, s);
+  free (s);
   end_elem (w);
 
-  start_elem (w, "vtx:text");
-  write_attr (w, "type", (text->type == TEXT_ITEM_TITLE ? "title"
-                          : text->type == TEXT_ITEM_PAGE_TITLE ? "page-title"
-                          : "log"));
-  if (command_id)
-    write_attr (w, "commandName", command_id);
-
-  start_elem (w, "html");
-  write_text (w, text->text);   /* XXX */
-  end_elem (w); /* html */
-  end_elem (w); /* vtx:text */
-  end_elem (w); /* container */
+  close_container (w);
 
   if (!initial_depth)
     spv_writer_close_file (w, "");
+
+  output_item_unref (item);
 }
 
-void
-spv_writer_eject_page (struct spv_writer *w)
+static cairo_status_t
+write_to_zip (void *zw_, const unsigned char *data, unsigned int length)
 {
-  w->need_page_break = true;
+  struct zip_writer *zw = zw_;
+
+  zip_writer_add_write (zw, data, length);
+  return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+spv_writer_put_image (struct spv_writer *w, const struct output_item *item,
+                      cairo_surface_t *image)
+{
+  bool initial_depth = w->heading_depth;
+  if (!initial_depth)
+    spv_writer_open_file (w);
+
+  char *uri = xasprintf ("%010d_Imagegeneric.png", ++w->n_tables);
+
+  open_container (w, item, "object");
+  write_attr (w, "type", "unknown");
+  write_attr (w, "uri", uri);
+  close_container (w);
+
+  if (!initial_depth)
+    spv_writer_close_file (w, "");
+
+  zip_writer_add_start (w->zw, uri);
+  cairo_surface_write_to_png_stream (image, write_to_zip, w->zw);
+  zip_writer_add_finish (w->zw);
+
+  free (uri);
 }
 \f
 #define H TABLE_HORZ
@@ -520,20 +558,25 @@ static void
 put_value_mod (struct buf *buf, const struct pivot_value *value,
                const char *template)
 {
-  if (value->n_footnotes || value->n_subscripts
-      || template || value->font_style || value->cell_style)
+  if ((value->ex
+       && (value->ex->n_footnotes
+           || value->ex->n_subscripts
+           || value->ex->font_style
+           || value->ex->cell_style))
+      || template)
     {
+      const struct pivot_value_ex *ex = pivot_value_ex (value);
       put_byte (buf, 0x31);
 
       /* Footnotes. */
-      put_u32 (buf, value->n_footnotes);
-      for (size_t i = 0; i < value->n_footnotes; i++)
-        put_u16 (buf, value->footnote_indexes[i]);
+      put_u32 (buf, ex->n_footnotes);
+      for (size_t i = 0; i < ex->n_footnotes; i++)
+        put_u16 (buf, ex->footnote_indexes[i]);
 
       /* Subscripts. */
-      put_u32 (buf, value->n_subscripts);
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        put_string (buf, value->subscripts[i]);
+      put_u32 (buf, ex->n_subscripts);
+      for (size_t i = 0; i < ex->n_subscripts; i++)
+        put_string (buf, ex->subscripts[i]);
 
       /* Template and style. */
       uint32_t v3_start = start_count (buf);
@@ -547,7 +590,7 @@ put_value_mod (struct buf *buf, const struct pivot_value *value,
           put_string (buf, template);
         }
       end_count_u32 (buf, template_string_start);
-      put_style_pair (buf, value->font_style, value->cell_style);
+      put_style_pair (buf, ex->font_style, ex->cell_style);
       end_count_u32 (buf, v3_start);
     }
   else
@@ -555,9 +598,10 @@ put_value_mod (struct buf *buf, const struct pivot_value *value,
 }
 
 static void
-put_format (struct buf *buf, const struct fmt_spec *f)
+put_format (struct buf *buf, const struct fmt_spec *f, bool honor_small)
 {
-  put_u32 (buf, (fmt_to_io (f->type) << 16) | (f->w << 8) | f->d);
+  int type = f->type == FMT_F && honor_small ? 40 : fmt_to_io (f->type);
+  put_u32 (buf, (type << 16) | (f->w << 8) | f->d);
 }
 
 static int
@@ -585,7 +629,7 @@ put_value (struct buf *buf, const struct pivot_value *value)
         {
           put_byte (buf, 2);
           put_value_mod (buf, value, NULL);
-          put_format (buf, &value->numeric.format);
+          put_format (buf, &value->numeric.format, value->numeric.honor_small);
           put_double (buf, value->numeric.x);
           put_string (buf, value->numeric.var_name);
           put_string (buf, value->numeric.value_label);
@@ -595,7 +639,7 @@ put_value (struct buf *buf, const struct pivot_value *value)
         {
           put_byte (buf, 1);
           put_value_mod (buf, value, NULL);
-          put_format (buf, &value->numeric.format);
+          put_format (buf, &value->numeric.format, value->numeric.honor_small);
           put_double (buf, value->numeric.x);
         }
       break;
@@ -603,8 +647,12 @@ put_value (struct buf *buf, const struct pivot_value *value)
     case PIVOT_VALUE_STRING:
       put_byte (buf, 4);
       put_value_mod (buf, value, NULL);
-      put_format (buf,
-                  &(struct fmt_spec) { FMT_A, strlen (value->string.s), 0 });
+      size_t len = strlen (value->string.s);
+      if (value->string.hex)
+        put_format (buf, &(struct fmt_spec) { .type = FMT_AHEX, .w = len * 2 },
+                    false);
+      else
+        put_format (buf, &(struct fmt_spec) { .type = FMT_A, .w = len }, false);
       put_string (buf, value->string.value_label);
       put_string (buf, value->string.var_name);
       put_show_values (buf, value->string.show);
@@ -687,7 +735,7 @@ put_category (struct buf *buf, const struct pivot_category *c)
   else
     {
       put_bytes (buf, "\0\0\1", 3);
-      put_u32 (buf, 0);
+      put_u32 (buf, 0);         /* x23 */
       put_u32 (buf, -1);
       put_u32 (buf, c->n_subs);
       for (size_t i = 0; i < c->n_subs; i++)
@@ -700,7 +748,7 @@ put_y0 (struct buf *buf, const struct pivot_table *table)
 {
   put_u32 (buf, table->settings.epoch);
   put_byte (buf, table->settings.decimal);
-  put_byte (buf, table->grouping);
+  put_byte (buf, ',');
 }
 
 static void
@@ -720,17 +768,17 @@ put_custom_currency (struct buf *buf, const struct pivot_table *table)
 static void
 put_x1 (struct buf *buf, const struct pivot_table *table)
 {
-  put_byte (buf, 0);
+  put_byte (buf, 0);            /* x14 */
   put_byte (buf, table->show_title ? 1 : 10);
-  put_byte (buf, 0);
-  put_byte (buf, 0);
+  put_byte (buf, 0);            /* x16 */
+  put_byte (buf, 0);            /* lang */
   put_show_values (buf, table->show_variables);
   put_show_values (buf, table->show_values);
-  put_u32 (buf, -1);
-  put_u32 (buf, -1);
+  put_u32 (buf, -1);            /* x18 */
+  put_u32 (buf, -1);            /* x19 */
   for (int i = 0; i < 17; i++)
     put_byte (buf, 0);
-  put_bool (buf, false);
+  put_bool (buf, false);        /* x20 */
   put_byte (buf, table->show_caption);
 }
 
@@ -744,16 +792,38 @@ put_x2 (struct buf *buf)
 }
 
 static void
-put_x3 (struct buf *buf, const struct pivot_table *table)
+put_y1 (struct buf *buf, const struct pivot_table *table)
 {
-  put_bytes (buf, "\1\0\4\0\0\0", 6);
   put_string (buf, table->command_c);
   put_string (buf, table->command_local);
   put_string (buf, table->language);
   put_string (buf, "UTF-8");    /* XXX */
   put_string (buf, table->locale);
-  put_bytes (buf, "\0\0\1\1", 4);
+  put_bool (buf, false);        /* x10 */
+  put_bool (buf, table->settings.include_leading_zero);
+  put_bool (buf, true);         /* x12 */
+  put_bool (buf, true);         /* x13 */
   put_y0 (buf, table);
+}
+
+static void
+put_y2 (struct buf *buf, const struct pivot_table *table)
+{
+  put_custom_currency (buf, table);
+  put_byte (buf, '.');
+  put_bool (buf, 0);
+}
+
+static void
+put_x3 (struct buf *buf, const struct pivot_table *table)
+{
+  put_byte (buf, 1);
+  put_byte (buf, 0);
+  put_byte (buf, 4);            /* x21 */
+  put_byte (buf, 0);
+  put_byte (buf, 0);
+  put_byte (buf, 0);
+  put_y1 (buf, table);
   put_double (buf, table->small);
   put_byte (buf, 1);
   put_string (buf, table->dataset);
@@ -761,11 +831,22 @@ put_x3 (struct buf *buf, const struct pivot_table *table)
   put_u32 (buf, 0);
   put_u32 (buf, table->date);
   put_u32 (buf, 0);
+  put_y2 (buf, table);
+}
 
-  /* Y2. */
-  put_custom_currency (buf, table);
-  put_byte (buf, '.');
-  put_bool (buf, 0);
+static uint32_t
+encode_current_layer (const struct pivot_table *table)
+{
+  uint32_t current_layer = 0;
+
+  const struct pivot_axis *axis = &table->axes[PIVOT_AXIS_LAYER];
+  for (size_t i = axis->n_dimensions - 1; i < axis->n_dimensions; i--)
+    {
+      const struct pivot_dimension *d = axis->dimensions[i];
+      current_layer = current_layer * d->n_leaves + table->current_layer[i];
+    }
+
+  return current_layer;
 }
 
 static void
@@ -798,9 +879,10 @@ put_light_table (struct buf *buf, uint64_t table_id,
   put_u32 (buf, table->n_footnotes);
   for (size_t i = 0; i < table->n_footnotes; i++)
     {
-      put_value (buf, table->footnotes[i]->content);
-      put_optional_value (buf, table->footnotes[i]->marker);
-      put_u32 (buf, 0);
+      const struct pivot_footnote *f = table->footnotes[i];
+      put_value (buf, f->content);
+      put_optional_value (buf, f->marker);
+      put_u32 (buf, f->show ? 1 : -1);
     }
 
   /* Areas. */
@@ -883,7 +965,7 @@ put_light_table (struct buf *buf, uint64_t table_id,
   uint32_t ts_start = start_count (buf);
   put_be32 (buf, 1);
   put_be32 (buf, 4);
-  put_be32 (buf, 0);            /* XXX current_layer */
+  put_be32 (buf, encode_current_layer (table));
   put_bool (buf, table->look->omit_empty);
   put_bool (buf, table->look->row_labels_in_corner);
   put_bool (buf, !table->look->show_numeric_markers);
@@ -939,9 +1021,9 @@ put_light_table (struct buf *buf, uint64_t table_id,
     {
       const struct pivot_dimension *d = table->dimensions[i];
       put_value (buf, d->root->name);
-      put_byte (buf, 0);
+      put_byte (buf, 0);        /* x1 */
       put_byte (buf, x2[i]);
-      put_u32 (buf, 2);
+      put_u32 (buf, 2);         /* x3 */
       put_bool (buf, !d->root->show_label);
       put_bool (buf, d->hide_all_labels);
       put_bool (buf, 1);
@@ -978,36 +1060,24 @@ put_light_table (struct buf *buf, uint64_t table_id,
     }
 }
 
-void
-spv_writer_put_table (struct spv_writer *w, const struct pivot_table *table)
+static void
+spv_writer_put_table (struct spv_writer *w, const struct output_item *item)
 {
-  struct pivot_table *table_rw = CONST_CAST (struct pivot_table *, table);
-  if (!table_rw->subtype)
-    table_rw->subtype = pivot_value_new_user_text ("unknown", -1);
-
   int table_id = ++w->n_tables;
 
   bool initial_depth = w->heading_depth;
   if (!initial_depth)
     spv_writer_open_file (w);
 
-  start_container (w);
-
-  char *title = pivot_value_to_string (table->title, table);
-  char *subtype = pivot_value_to_string (table->subtype, table);
-  
-  start_elem (w, "label");
-  write_text (w, title);
-  end_elem (w);
+  open_container (w, item, "vtb:table");
 
-  start_elem (w, "vtb:table");
-  write_attr (w, "commandName", table->command_c);
   write_attr (w, "type", "table"); /* XXX */
-  write_attr (w, "subType", subtype);
   write_attr_format (w, "tableId", "%d", table_id);
-
+  char *subtype = (item->table->subtype
+                   ? pivot_value_to_string (item->table->subtype, item->table)
+                   : xstrdup ("unknown"));
+  write_attr (w, "subType", subtype);
   free (subtype);
-  free (title);
 
   start_elem (w, "vtb:tableStructure");
   start_elem (w, "vtb:dataPath");
@@ -1015,16 +1085,71 @@ spv_writer_put_table (struct spv_writer *w, const struct pivot_table *table)
   write_text (w, data_path);
   end_elem (w); /* vtb:dataPath */
   end_elem (w); /* vtb:tableStructure */
-  end_elem (w); /* vtb:table */
-  end_elem (w); /* container */
+
+  close_container (w);
 
   if (!initial_depth)
     spv_writer_close_file (w, "");
 
   struct buf buf = { NULL, 0, 0 };
-  put_light_table (&buf, table_id, table);
+  put_light_table (&buf, table_id, item->table);
   zip_writer_add_memory (w->zw, data_path, buf.data, buf.len);
   free (buf.data);
 
   free (data_path);
 }
+\f
+void
+spv_writer_write (struct spv_writer *w, const struct output_item *item)
+{
+  switch (item->type)
+    {
+    case OUTPUT_ITEM_CHART:
+      {
+        cairo_surface_t *surface = xr_draw_image_chart (
+          item->chart,
+          &(struct cell_color) CELL_COLOR_BLACK,
+          &(struct cell_color) CELL_COLOR_WHITE);
+        if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS)
+          spv_writer_put_image (w, item, surface);
+        cairo_surface_destroy (surface);
+      }
+      break;
+
+    case OUTPUT_ITEM_GROUP:
+      spv_writer_open_heading (w, item);
+      for (size_t i = 0; i < item->group.n_children; i++)
+        spv_writer_write (w, item->group.children[i]);
+      spv_writer_close_heading (w);
+      break;
+
+    case OUTPUT_ITEM_IMAGE:
+      spv_writer_put_image (w, item, item->image);
+      break;
+
+    case OUTPUT_ITEM_MESSAGE:
+      spv_writer_put_text (
+        w, message_item_to_text_item (output_item_ref (item)));
+      break;
+
+    case OUTPUT_ITEM_PAGE_BREAK:
+      w->need_page_break = true;
+      break;
+
+    case OUTPUT_ITEM_TABLE:
+      spv_writer_put_table (w, item);
+      break;
+
+    case OUTPUT_ITEM_TEXT:
+      spv_writer_put_text (w, output_item_ref (item));
+      break;
+    }
+}
+
+void
+spv_writer_set_page_setup (struct spv_writer *w,
+                           const struct page_setup *ps)
+{
+  page_setup_destroy (w->page_setup);
+  w->page_setup = page_setup_clone (ps);
+}