Add support for PNG images in .spv files.
[pspp] / src / output / spv / spv-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2019 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/spv/spv-writer.h"
20
21 #include <inttypes.h>
22 #include <libxml/xmlwriter.h>
23 #include <math.h>
24 #include <stdlib.h>
25 #include <time.h>
26
27 #include "libpspp/array.h"
28 #include "libpspp/assertion.h"
29 #include "libpspp/cast.h"
30 #include "libpspp/float-format.h"
31 #include "libpspp/integer-format.h"
32 #include "libpspp/temp-file.h"
33 #include "libpspp/version.h"
34 #include "libpspp/zip-writer.h"
35 #include "output/page-setup-item.h"
36 #include "output/pivot-table.h"
37 #include "output/text-item.h"
38
39 #include "gl/xalloc.h"
40 #include "gl/xvasprintf.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44 #define N_(msgid) (msgid)
45
46 struct spv_writer
47   {
48     struct zip_writer *zw;
49
50     FILE *heading;
51     int heading_depth;
52     xmlTextWriter *xml;
53
54     int n_tables;
55
56     int n_headings;
57     struct page_setup *page_setup;
58     bool need_page_break;
59   };
60
61 char * WARN_UNUSED_RESULT
62 spv_writer_open (const char *filename, struct spv_writer **writerp)
63 {
64   *writerp = NULL;
65
66   struct zip_writer *zw = zip_writer_create (filename);
67   if (!zw)
68     return xasprintf (_("%s: create failed"), filename);
69
70   struct spv_writer *w = xmalloc (sizeof *w);
71   *w = (struct spv_writer) { .zw = zw };
72   *writerp = w;
73   return NULL;
74 }
75
76 char * WARN_UNUSED_RESULT
77 spv_writer_close (struct spv_writer *w)
78 {
79   if (!w)
80     return NULL;
81
82   zip_writer_add_string (w->zw, "META-INF/MANIFEST.MF", "allowPivoting=true");
83
84   while (w->heading_depth)
85     spv_writer_close_heading (w);
86
87   char *error = NULL;
88   if (!zip_writer_close (w->zw))
89     error = xstrdup (_("I/O error writing SPV file"));
90
91   page_setup_destroy (w->page_setup);
92   free (w);
93   return error;
94 }
95
96 void
97 spv_writer_set_page_setup (struct spv_writer *w,
98                            const struct page_setup *page_setup)
99 {
100   page_setup_destroy (w->page_setup);
101   w->page_setup = page_setup_clone (page_setup);
102 }
103
104 static void
105 write_attr (struct spv_writer *w, const char *name, const char *value)
106 {
107   xmlTextWriterWriteAttribute (w->xml,
108                                CHAR_CAST (xmlChar *, name),
109                                CHAR_CAST (xmlChar *, value));
110 }
111
112 static void PRINTF_FORMAT (3, 4)
113 write_attr_format (struct spv_writer *w, const char *name,
114                    const char *format, ...)
115 {
116   va_list args;
117   va_start (args, format);
118   char *value = xvasprintf (format, args);
119   va_end (args);
120
121   write_attr (w, name, value);
122   free (value);
123 }
124
125 static void
126 start_elem (struct spv_writer *w, const char *name)
127 {
128   xmlTextWriterStartElement (w->xml, CHAR_CAST (xmlChar *, name));
129 }
130
131 static void
132 end_elem (struct spv_writer *w)
133 {
134   xmlTextWriterEndElement (w->xml);
135 }
136
137 static void
138 write_text (struct spv_writer *w, const char *text)
139 {
140   xmlTextWriterWriteString (w->xml, CHAR_CAST (xmlChar *, text));
141 }
142
143 static void
144 write_page_heading (struct spv_writer *w, const struct page_heading *h,
145                     const char *name)
146 {
147   start_elem (w, name);
148   if (h->n)
149     {
150       start_elem (w, "pageParagraph");
151       for (size_t i = 0; i < h->n; i++)
152         {
153           start_elem (w, "text");
154           write_attr (w, "type", "title");
155           write_text (w, h->paragraphs[i].markup); /* XXX */
156           end_elem (w);
157         }
158       end_elem (w);
159     }
160   end_elem (w);
161 }
162
163 static void
164 write_page_setup (struct spv_writer *w, const struct page_setup *ps)
165 {
166   start_elem (w, "pageSetup");
167   write_attr_format (w, "initial-page-number", "%d", ps->initial_page_number);
168   write_attr (w, "chart-size",
169               (ps->chart_size == PAGE_CHART_AS_IS ? "as-is"
170                : ps->chart_size == PAGE_CHART_FULL_HEIGHT ? "full-height"
171                : ps->chart_size == PAGE_CHART_HALF_HEIGHT ? "half-height"
172                : "quarter-height"));
173   write_attr_format (w, "margin-left", "%.2fin", ps->margins[TABLE_HORZ][0]);
174   write_attr_format (w, "margin-right", "%.2fin", ps->margins[TABLE_HORZ][1]);
175   write_attr_format (w, "margin-top", "%.2fin", ps->margins[TABLE_VERT][0]);
176   write_attr_format (w, "margin-bottom", "%.2fin", ps->margins[TABLE_VERT][1]);
177   write_attr_format (w, "paper-height", "%.2fin", ps->paper[TABLE_VERT]);
178   write_attr_format (w, "paper-width", "%.2fin", ps->paper[TABLE_HORZ]);
179   write_attr (w, "reference-orientation",
180               ps->orientation == PAGE_PORTRAIT ? "portrait" : "landscape");
181   write_attr_format (w, "space-after", "%.1fpt", ps->object_spacing * 72.0);
182   write_page_heading (w, &ps->headings[0], "pageHeader");
183   write_page_heading (w, &ps->headings[1], "pageFooter");
184   end_elem (w);
185 }
186
187 static bool
188 spv_writer_open_file (struct spv_writer *w)
189 {
190   w->heading = create_temp_file ();
191   if (!w->heading)
192     return false;
193
194   w->xml = xmlNewTextWriter (xmlOutputBufferCreateFile (w->heading, NULL));
195   xmlTextWriterStartDocument (w->xml, NULL, "UTF-8", NULL);
196   start_elem (w, "heading");
197
198   time_t t = time (NULL);
199   struct tm *tm = gmtime (&t);
200   char *tm_s = asctime (tm);
201   write_attr (w, "creation-date-time", tm_s);
202
203   write_attr (w, "creator", version);
204
205   write_attr (w, "creator-version", "21");
206
207   write_attr (w, "xmlns", "http://xml.spss.com/spss/viewer/viewer-tree");
208   write_attr (w, "xmlns:vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
209   write_attr (w, "xmlns:vtx", "http://xml.spss.com/spss/viewer/viewer-text");
210   write_attr (w, "xmlns:vtb", "http://xml.spss.com/spss/viewer/viewer-table");
211
212   start_elem (w, "label");
213   write_text (w, _("Output"));
214   end_elem (w);
215
216   if (w->page_setup)
217     {
218       write_page_setup (w, w->page_setup);
219
220       page_setup_destroy (w->page_setup);
221       w->page_setup = NULL;
222     }
223
224   return true;
225 }
226
227 void
228 spv_writer_open_heading (struct spv_writer *w, const char *command_id,
229                          const char *label)
230 {
231   if (!w->heading)
232     {
233       if (!spv_writer_open_file (w))
234         return;
235     }
236
237   w->heading_depth++;
238   start_elem (w, "heading");
239   write_attr (w, "commandName", command_id);
240   /* XXX locale */
241   /* XXX olang */
242
243   start_elem (w, "label");
244   write_text (w, label);
245   end_elem (w);
246 }
247
248 static void
249 spv_writer_close_file (struct spv_writer *w, const char *infix)
250 {
251   if (!w->heading)
252     return;
253
254   end_elem (w);
255   xmlTextWriterEndDocument (w->xml);
256   xmlFreeTextWriter (w->xml);
257
258   char *member_name = xasprintf ("outputViewer%010d%s.xml",
259                                  w->n_headings++, infix);
260   zip_writer_add (w->zw, w->heading, member_name);
261   free (member_name);
262
263   w->heading = NULL;
264 }
265
266 void
267 spv_writer_close_heading (struct spv_writer *w)
268 {
269   const char *infix = "";
270   if (w->heading_depth)
271     {
272       infix = "_heading";
273       end_elem (w);
274       w->heading_depth--;
275     }
276
277   if (!w->heading_depth)
278     spv_writer_close_file (w, infix);
279 }
280
281 static void
282 start_container (struct spv_writer *w)
283 {
284   start_elem (w, "container");
285   write_attr (w, "visibility", "visible");
286   if (w->need_page_break)
287     {
288       write_attr (w, "page-break-before", "always");
289       w->need_page_break = false;
290     }
291 }
292
293 void
294 spv_writer_put_text (struct spv_writer *w, const struct text_item *text,
295                      const char *command_id)
296 {
297   bool initial_depth = w->heading_depth;
298   if (!initial_depth)
299     spv_writer_open_file (w);
300
301   start_container (w);
302
303   start_elem (w, "label");
304   write_text (w, (text->type == TEXT_ITEM_TITLE ? "Title"
305                   : text->type == TEXT_ITEM_PAGE_TITLE ? "Page Title"
306                   : "Log"));
307   end_elem (w);
308
309   start_elem (w, "vtx:text");
310   write_attr (w, "type", (text->type == TEXT_ITEM_TITLE ? "title"
311                           : text->type == TEXT_ITEM_PAGE_TITLE ? "page-title"
312                           : "log"));
313   if (command_id)
314     write_attr (w, "commandName", command_id);
315
316   start_elem (w, "html");
317   write_text (w, text->text);   /* XXX */
318   end_elem (w); /* html */
319   end_elem (w); /* vtx:text */
320   end_elem (w); /* container */
321
322   if (!initial_depth)
323     spv_writer_close_file (w, "");
324 }
325
326 #ifdef HAVE_CAIRO
327 static cairo_status_t
328 write_to_zip (void *zw_, const unsigned char *data, unsigned int length)
329 {
330   struct zip_writer *zw = zw_;
331
332   zip_writer_add_write (zw, data, length);
333   return CAIRO_STATUS_SUCCESS;
334 }
335
336 void
337 spv_writer_put_image (struct spv_writer *w, cairo_surface_t *image)
338 {
339   bool initial_depth = w->heading_depth;
340   if (!initial_depth)
341     spv_writer_open_file (w);
342
343   char *uri = xasprintf ("%010d_Imagegeneric.png", ++w->n_tables);
344
345   start_container (w);
346
347   start_elem (w, "label");
348   write_text (w, "Image");
349   end_elem (w);
350
351   start_elem (w, "object");
352   write_attr (w, "type", "unknown");
353   write_attr (w, "uri", uri);
354   end_elem (w); /* object */
355   end_elem (w); /* container */
356
357   if (!initial_depth)
358     spv_writer_close_file (w, "");
359
360   zip_writer_add_start (w->zw, uri);
361   cairo_surface_write_to_png_stream (image, write_to_zip, w->zw);
362   zip_writer_add_finish (w->zw);
363
364   free (uri);
365 }
366 #endif
367
368 void
369 spv_writer_eject_page (struct spv_writer *w)
370 {
371   w->need_page_break = true;
372 }
373 \f
374 #define H TABLE_HORZ
375 #define V TABLE_VERT
376
377 struct buf
378   {
379     uint8_t *data;
380     size_t len;
381     size_t allocated;
382   };
383
384 static uint8_t *
385 put_uninit (struct buf *b, size_t n)
386 {
387   while (b->allocated - b->len < n)
388     b->data = x2nrealloc (b->data, &b->allocated, sizeof b->data);
389   uint8_t *p = &b->data[b->len];
390   b->len += n;
391   return p;
392 }
393
394 static void
395 put_byte (struct buf *b, uint8_t byte)
396 {
397   *put_uninit (b, 1) = byte;
398 }
399
400 static void
401 put_bool (struct buf *b, bool boolean)
402 {
403   put_byte (b, boolean);
404 }
405
406 static void
407 put_bytes (struct buf *b, const char *bytes, size_t n)
408 {
409   memcpy (put_uninit (b, n), bytes, n);
410 }
411
412 static void
413 put_u16 (struct buf *b, uint16_t x)
414 {
415   put_uint16 (native_to_le16 (x), put_uninit (b, sizeof x));
416 }
417
418 static void
419 put_u32 (struct buf *b, uint32_t x)
420 {
421   put_uint32 (native_to_le32 (x), put_uninit (b, sizeof x));
422 }
423
424 static void
425 put_u64 (struct buf *b, uint64_t x)
426 {
427   put_uint64 (native_to_le64 (x), put_uninit (b, sizeof x));
428 }
429
430 static void
431 put_be32 (struct buf *b, uint32_t x)
432 {
433   put_uint32 (native_to_be32 (x), put_uninit (b, sizeof x));
434 }
435
436 static void
437 put_double (struct buf *b, double x)
438 {
439   float_convert (FLOAT_NATIVE_DOUBLE, &x,
440                  FLOAT_IEEE_DOUBLE_LE, put_uninit (b, 8));
441 }
442
443 static void
444 put_float (struct buf *b, float x)
445 {
446   float_convert (FLOAT_NATIVE_FLOAT, &x,
447                  FLOAT_IEEE_SINGLE_LE, put_uninit (b, 4));
448 }
449
450 static void
451 put_string (struct buf *b, const char *s_)
452 {
453   const char *s = s_ ? s_ : "";
454   size_t len = strlen (s);
455   put_u32 (b, len);
456   memcpy (put_uninit (b, len), s, len);
457 }
458
459 static void
460 put_bestring (struct buf *b, const char *s_)
461 {
462   const char *s = s_ ? s_ : "";
463   size_t len = strlen (s);
464   put_be32 (b, len);
465   memcpy (put_uninit (b, len), s, len);
466 }
467
468 static size_t
469 start_count (struct buf *b)
470 {
471   put_u32 (b, 0);
472   return b->len;
473 }
474
475 static void
476 end_count_u32 (struct buf *b, size_t start)
477 {
478   put_uint32 (native_to_le32 (b->len - start), &b->data[start - 4]);
479 }
480
481 static void
482 end_count_be32 (struct buf *b, size_t start)
483 {
484   put_uint32 (native_to_be32 (b->len - start), &b->data[start - 4]);
485 }
486
487 static void
488 put_color (struct buf *buf, const struct cell_color *color)
489 {
490   char *s = xasprintf ("#%02"PRIx8"%02"PRIx8"%02"PRIx8,
491                        color->r, color->g, color->b);
492   put_string (buf, s);
493   free (s);
494 }
495
496 static void
497 put_font_style (struct buf *buf, const struct font_style *font_style)
498 {
499   put_bool (buf, font_style->bold);
500   put_bool (buf, font_style->italic);
501   put_bool (buf, font_style->underline);
502   put_bool (buf, 1);
503   put_color (buf, &font_style->fg[0]);
504   put_color (buf, &font_style->bg[0]);
505   put_string (buf, font_style->typeface ? font_style->typeface : "SansSerif");
506   put_byte (buf, ceil (font_style->size * 1.33));
507 }
508
509 static void
510 put_halign (struct buf *buf, enum table_halign halign,
511             uint32_t mixed, uint32_t decimal)
512 {
513   put_u32 (buf, (halign == TABLE_HALIGN_RIGHT ? 4
514                  : halign == TABLE_HALIGN_LEFT ? 2
515                  : halign == TABLE_HALIGN_CENTER ? 0
516                  : halign == TABLE_HALIGN_MIXED ? mixed
517                  : decimal));
518 }
519
520 static void
521 put_valign (struct buf *buf, enum table_valign valign)
522 {
523   put_u32 (buf, (valign == TABLE_VALIGN_TOP ? 1
524                  : valign == TABLE_VALIGN_CENTER ? 0
525                  : 3));
526 }
527
528 static void
529 put_cell_style (struct buf *buf, const struct cell_style *cell_style)
530 {
531   put_halign (buf, cell_style->halign, 0xffffffad, 6);
532   put_valign (buf, cell_style->valign);
533   put_double (buf, cell_style->decimal_offset);
534   put_u16 (buf, cell_style->margin[H][0]);
535   put_u16 (buf, cell_style->margin[H][1]);
536   put_u16 (buf, cell_style->margin[V][0]);
537   put_u16 (buf, cell_style->margin[V][1]);
538 }
539
540 static void UNUSED
541 put_style_pair (struct buf *buf, const struct font_style *font_style,
542                 const struct cell_style *cell_style)
543 {
544   if (font_style)
545     {
546       put_byte (buf, 0x31);
547       put_font_style (buf, font_style);
548     }
549   else
550     put_byte (buf, 0x58);
551
552   if (cell_style)
553     {
554       put_byte (buf, 0x31);
555       put_cell_style (buf, cell_style);
556     }
557   else
558     put_byte (buf, 0x58);
559 }
560
561 static void
562 put_value_mod (struct buf *buf, const struct pivot_value *value,
563                const char *template)
564 {
565   if (value->n_footnotes || value->n_subscripts
566       || template || value->font_style || value->cell_style)
567     {
568       put_byte (buf, 0x31);
569
570       /* Footnotes. */
571       put_u32 (buf, value->n_footnotes);
572       for (size_t i = 0; i < value->n_footnotes; i++)
573         put_u16 (buf, value->footnote_indexes[i]);
574
575       /* Subscripts. */
576       put_u32 (buf, value->n_subscripts);
577       for (size_t i = 0; i < value->n_subscripts; i++)
578         put_string (buf, value->subscripts[i]);
579
580       /* Template and style. */
581       uint32_t v3_start = start_count (buf);
582       uint32_t template_string_start = start_count (buf);
583       if (template)
584         {
585           uint32_t inner_start = start_count (buf);
586           end_count_u32 (buf, inner_start);
587
588           put_byte (buf, 0x31);
589           put_string (buf, template);
590         }
591       end_count_u32 (buf, template_string_start);
592       put_style_pair (buf, value->font_style, value->cell_style);
593       end_count_u32 (buf, v3_start);
594     }
595   else
596     put_byte (buf, 0x58);
597 }
598
599 static void
600 put_format (struct buf *buf, const struct fmt_spec *f, bool honor_small)
601 {
602   int type = f->type == FMT_F && honor_small ? 40 : fmt_to_io (f->type);
603   put_u32 (buf, (type << 16) | (f->w << 8) | f->d);
604 }
605
606 static int
607 show_values_to_spvlb (enum settings_value_show show)
608 {
609   return (show == SETTINGS_VALUE_SHOW_DEFAULT ? 0
610           : show == SETTINGS_VALUE_SHOW_VALUE ? 1
611           : show == SETTINGS_VALUE_SHOW_LABEL ? 2
612           : 3);
613 }
614
615 static void
616 put_show_values (struct buf *buf, enum settings_value_show show)
617 {
618   put_byte (buf, show_values_to_spvlb (show));
619 }
620
621 static void
622 put_value (struct buf *buf, const struct pivot_value *value)
623 {
624   switch (value->type)
625     {
626     case PIVOT_VALUE_NUMERIC:
627       if (value->numeric.var_name || value->numeric.value_label)
628         {
629           put_byte (buf, 2);
630           put_value_mod (buf, value, NULL);
631           put_format (buf, &value->numeric.format, value->numeric.honor_small);
632           put_double (buf, value->numeric.x);
633           put_string (buf, value->numeric.var_name);
634           put_string (buf, value->numeric.value_label);
635           put_show_values (buf, value->numeric.show);
636         }
637       else
638         {
639           put_byte (buf, 1);
640           put_value_mod (buf, value, NULL);
641           put_format (buf, &value->numeric.format, value->numeric.honor_small);
642           put_double (buf, value->numeric.x);
643         }
644       break;
645
646     case PIVOT_VALUE_STRING:
647       put_byte (buf, 4);
648       put_value_mod (buf, value, NULL);
649       size_t len = strlen (value->string.s);
650       if (value->string.hex)
651         put_format (buf, &(struct fmt_spec) { FMT_AHEX, len * 2, 0 }, false);
652       else
653         put_format (buf, &(struct fmt_spec) { FMT_A, len, 0 }, false);
654       put_string (buf, value->string.value_label);
655       put_string (buf, value->string.var_name);
656       put_show_values (buf, value->string.show);
657       put_string (buf, value->string.s);
658       break;
659
660     case PIVOT_VALUE_VARIABLE:
661       put_byte (buf, 5);
662       put_value_mod (buf, value, NULL);
663       put_string (buf, value->variable.var_name);
664       put_string (buf, value->variable.var_label);
665       put_show_values (buf, value->variable.show);
666       break;
667
668     case PIVOT_VALUE_TEXT:
669       put_byte (buf, 3);
670       put_string (buf, value->text.local);
671       put_value_mod (buf, value, NULL);
672       put_string (buf, value->text.id);
673       put_string (buf, value->text.c);
674       put_byte (buf, 1);        /* XXX user-provided */
675       break;
676
677     case PIVOT_VALUE_TEMPLATE:
678       put_byte (buf, 0);
679       put_value_mod (buf, value, value->template.id);
680       put_string (buf, value->template.local);
681       put_u32 (buf, value->template.n_args);
682       for (size_t i = 0; i < value->template.n_args; i++)
683         {
684           const struct pivot_argument *arg = &value->template.args[i];
685           assert (arg->n >= 1);
686           if (arg->n > 1)
687             {
688               put_u32 (buf, arg->n);
689               put_u32 (buf, 0);
690               for (size_t j = 0; j < arg->n; j++)
691                 {
692                   if (j > 0)
693                     put_bytes (buf, "\0\0\0\0", 4);
694                   put_value (buf, arg->values[j]);
695                 }
696             }
697           else
698             {
699               put_u32 (buf, 0);
700               put_value (buf, arg->values[0]);
701             }
702         }
703       break;
704
705     default:
706       NOT_REACHED ();
707     }
708 }
709
710 static void
711 put_optional_value (struct buf *buf, const struct pivot_value *value)
712 {
713   if (value)
714     {
715       put_byte (buf, 0x31);
716       put_value (buf, value);
717     }
718   else
719     put_byte (buf, 0x58);
720 }
721
722 static void
723 put_category (struct buf *buf, const struct pivot_category *c)
724 {
725   put_value (buf, c->name);
726   if (pivot_category_is_leaf (c))
727     {
728       put_bytes (buf, "\0\0\0", 3);
729       put_u32 (buf, 2);
730       put_u32 (buf, c->data_index);
731       put_u32 (buf, 0);
732     }
733   else
734     {
735       put_bytes (buf, "\0\0\1", 3);
736       put_u32 (buf, 0);         /* x23 */
737       put_u32 (buf, -1);
738       put_u32 (buf, c->n_subs);
739       for (size_t i = 0; i < c->n_subs; i++)
740         put_category (buf, c->subs[i]);
741     }
742 }
743
744 static void
745 put_y0 (struct buf *buf, const struct pivot_table *table)
746 {
747   put_u32 (buf, table->settings.epoch);
748   put_byte (buf, table->settings.decimal);
749   put_byte (buf, ',');
750 }
751
752 static void
753 put_custom_currency (struct buf *buf, const struct pivot_table *table)
754 {
755   put_u32 (buf, 5);
756   for (int i = 0; i < 5; i++)
757     {
758       enum fmt_type types[5] = { FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE };
759       char *cc = fmt_number_style_to_string (fmt_settings_get_style (
760                                                &table->settings, types[i]));
761       put_string (buf, cc);
762       free (cc);
763     }
764 }
765
766 static void
767 put_x1 (struct buf *buf, const struct pivot_table *table)
768 {
769   put_byte (buf, 0);            /* x14 */
770   put_byte (buf, table->show_title ? 1 : 10);
771   put_byte (buf, 0);            /* x16 */
772   put_byte (buf, 0);            /* lang */
773   put_show_values (buf, table->show_variables);
774   put_show_values (buf, table->show_values);
775   put_u32 (buf, -1);            /* x18 */
776   put_u32 (buf, -1);            /* x19 */
777   for (int i = 0; i < 17; i++)
778     put_byte (buf, 0);
779   put_bool (buf, false);        /* x20 */
780   put_byte (buf, table->show_caption);
781 }
782
783 static void
784 put_x2 (struct buf *buf)
785 {
786   put_u32 (buf, 0);             /* n-row-heights */
787   put_u32 (buf, 0);             /* n-style-map */
788   put_u32 (buf, 0);             /* n-styles */
789   put_u32 (buf, 0);
790 }
791
792 static void
793 put_y1 (struct buf *buf, const struct pivot_table *table)
794 {
795   put_string (buf, table->command_c);
796   put_string (buf, table->command_local);
797   put_string (buf, table->language);
798   put_string (buf, "UTF-8");    /* XXX */
799   put_string (buf, table->locale);
800   put_bytes (buf, "\0\0\1\1", 4);
801   put_y0 (buf, table);
802 }
803
804 static void
805 put_y2 (struct buf *buf, const struct pivot_table *table)
806 {
807   put_custom_currency (buf, table);
808   put_byte (buf, '.');
809   put_bool (buf, 0);
810 }
811
812 static void
813 put_x3 (struct buf *buf, const struct pivot_table *table)
814 {
815   put_byte (buf, 1);
816   put_byte (buf, 0);
817   put_byte (buf, 4);            /* x21 */
818   put_byte (buf, 0);
819   put_byte (buf, 0);
820   put_byte (buf, 0);
821   put_y1 (buf, table);
822   put_double (buf, table->small);
823   put_byte (buf, 1);
824   put_string (buf, table->dataset);
825   put_string (buf, table->datafile);
826   put_u32 (buf, 0);
827   put_u32 (buf, table->date);
828   put_u32 (buf, 0);
829   put_y2 (buf, table);
830 }
831
832 static void
833 put_light_table (struct buf *buf, uint64_t table_id,
834                  const struct pivot_table *table)
835 {
836   /* Header. */
837   put_bytes (buf, "\1\0", 2);
838   put_u32 (buf, 3);
839   put_bool (buf, true);
840   put_bool (buf, false);
841   put_bool (buf, table->rotate_inner_column_labels);
842   put_bool (buf, table->rotate_outer_row_labels);
843   put_bool (buf, true);
844   put_u32 (buf, 0x15);
845   put_u32 (buf, table->look->width_ranges[H][0]);
846   put_u32 (buf, table->look->width_ranges[H][1]);
847   put_u32 (buf, table->look->width_ranges[V][0]);
848   put_u32 (buf, table->look->width_ranges[V][1]);
849   put_u64 (buf, table_id);
850
851   /* Titles. */
852   put_value (buf, table->title);
853   put_value (buf, table->subtype);
854   put_optional_value (buf, table->title);
855   put_optional_value (buf, table->corner_text);
856   put_optional_value (buf, table->caption);
857
858   /* Footnotes. */
859   put_u32 (buf, table->n_footnotes);
860   for (size_t i = 0; i < table->n_footnotes; i++)
861     {
862       put_value (buf, table->footnotes[i]->content);
863       put_optional_value (buf, table->footnotes[i]->marker);
864       put_u32 (buf, 0);
865     }
866
867   /* Areas. */
868   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
869     {
870       const struct table_area_style *a = &table->look->areas[i];
871       put_byte (buf, i + 1);
872       put_byte (buf, 0x31);
873       put_string (buf, (a->font_style.typeface
874                         ? a->font_style.typeface
875                         : "SansSerif"));
876       put_float (buf, ceil (a->font_style.size * 1.33));
877       put_u32 (buf, ((a->font_style.bold ? 1 : 0)
878                      | (a->font_style.italic ? 2 : 0)));
879       put_bool (buf, a->font_style.underline);
880       put_halign (buf, a->cell_style.halign, 64173, 61453);
881       put_valign (buf, a->cell_style.valign);
882
883       put_color (buf, &a->font_style.fg[0]);
884       put_color (buf, &a->font_style.bg[0]);
885
886       bool alt
887         = (!cell_color_equal (&a->font_style.fg[0], &a->font_style.fg[1])
888            || !cell_color_equal (&a->font_style.bg[0], &a->font_style.bg[1]));
889       put_bool (buf, alt);
890       if (alt)
891         {
892           put_color (buf, &a->font_style.fg[1]);
893           put_color (buf, &a->font_style.bg[1]);
894         }
895       else
896         {
897           put_string (buf, "");
898           put_string (buf, "");
899         }
900
901       put_u32 (buf, a->cell_style.margin[H][0]);
902       put_u32 (buf, a->cell_style.margin[H][1]);
903       put_u32 (buf, a->cell_style.margin[V][0]);
904       put_u32 (buf, a->cell_style.margin[V][1]);
905     }
906
907   /* Borders. */
908   uint32_t borders_start = start_count (buf);
909   put_be32 (buf, 1);
910   put_be32 (buf, PIVOT_N_BORDERS);
911   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
912     {
913       const struct table_border_style *b = &table->look->borders[i];
914       put_be32 (buf, i);
915       put_be32 (buf, (b->stroke == TABLE_STROKE_NONE ? 0
916                       : b->stroke == TABLE_STROKE_SOLID ? 1
917                       : b->stroke == TABLE_STROKE_DASHED ? 2
918                       : b->stroke == TABLE_STROKE_THICK ? 3
919                       : b->stroke == TABLE_STROKE_THIN ? 4
920                       : 5));
921       put_be32 (buf, ((b->color.alpha << 24)
922                       | (b->color.r << 16)
923                       | (b->color.g << 8)
924                       | b->color.b));
925     }
926   put_bool (buf, table->show_grid_lines);
927   put_bytes (buf, "\0\0\0", 3);
928   end_count_u32 (buf, borders_start);
929
930   /* Print Settings. */
931   uint32_t ps_start = start_count (buf);
932   put_be32 (buf, 1);
933   put_bool (buf, table->look->print_all_layers);
934   put_bool (buf, table->look->paginate_layers);
935   put_bool (buf, table->look->shrink_to_fit[H]);
936   put_bool (buf, table->look->shrink_to_fit[V]);
937   put_bool (buf, table->look->top_continuation);
938   put_bool (buf, table->look->bottom_continuation);
939   put_be32 (buf, table->look->n_orphan_lines);
940   put_bestring (buf, table->look->continuation);
941   end_count_u32 (buf, ps_start);
942
943   /* Table Settings. */
944   uint32_t ts_start = start_count (buf);
945   put_be32 (buf, 1);
946   put_be32 (buf, 4);
947   put_be32 (buf, 0);            /* XXX current_layer */
948   put_bool (buf, table->look->omit_empty);
949   put_bool (buf, table->look->row_labels_in_corner);
950   put_bool (buf, !table->look->show_numeric_markers);
951   put_bool (buf, table->look->footnote_marker_superscripts);
952   put_byte (buf, 0);
953   uint32_t keep_start = start_count (buf);
954   put_be32 (buf, 0);            /* n-row-breaks */
955   put_be32 (buf, 0);            /* n-column-breaks */
956   put_be32 (buf, 0);            /* n-row-keeps */
957   put_be32 (buf, 0);            /* n-column-keeps */
958   put_be32 (buf, 0);            /* n-row-point-keeps */
959   put_be32 (buf, 0);            /* n-column-point-keeps */
960   end_count_be32 (buf, keep_start);
961   put_bestring (buf, table->notes);
962   put_bestring (buf, table->look->name);
963   for (size_t i = 0; i < 82; i++)
964     put_byte (buf, 0);
965   end_count_u32 (buf, ts_start);
966
967   /* Formats. */
968   put_u32 (buf, 0);             /* n-widths */
969   put_string (buf, "en_US.ISO_8859-1:1987"); /* XXX */
970   put_u32 (buf, 0);                /* XXX current-layer */
971   put_bool (buf, 0);
972   put_bool (buf, 0);
973   put_bool (buf, 1);
974   put_y0 (buf, table);
975   put_custom_currency (buf, table);
976   uint32_t formats_start = start_count (buf);
977   uint32_t x1_start = start_count (buf);
978   put_x1 (buf, table);
979   uint32_t x2_start = start_count (buf);
980   put_x2 (buf);
981   end_count_u32 (buf, x2_start);
982   end_count_u32 (buf, x1_start);
983   uint32_t x3_start = start_count (buf);
984   put_x3 (buf, table);
985   end_count_u32 (buf, x3_start);
986   end_count_u32 (buf, formats_start);
987
988   /* Dimensions. */
989   put_u32 (buf, table->n_dimensions);
990   int *x2 = xnmalloc (table->n_dimensions, sizeof *x2);
991   for (size_t i = 0; i < table->axes[PIVOT_AXIS_LAYER].n_dimensions; i++)
992     x2[i] = 2;
993   for (size_t i = 0; i < table->axes[PIVOT_AXIS_ROW].n_dimensions; i++)
994     x2[i + table->axes[PIVOT_AXIS_LAYER].n_dimensions] = 0;
995   for (size_t i = 0; i < table->axes[PIVOT_AXIS_COLUMN].n_dimensions; i++)
996     x2[i
997        + table->axes[PIVOT_AXIS_LAYER].n_dimensions
998        + table->axes[PIVOT_AXIS_ROW].n_dimensions] = 1;
999   for (size_t i = 0; i < table->n_dimensions; i++)
1000     {
1001       const struct pivot_dimension *d = table->dimensions[i];
1002       put_value (buf, d->root->name);
1003       put_byte (buf, 0);        /* x1 */
1004       put_byte (buf, x2[i]);
1005       put_u32 (buf, 2);         /* x3 */
1006       put_bool (buf, !d->root->show_label);
1007       put_bool (buf, d->hide_all_labels);
1008       put_bool (buf, 1);
1009       put_u32 (buf, i);
1010
1011       put_u32 (buf, d->root->n_subs);
1012       for (size_t j = 0; j < d->root->n_subs; j++)
1013         put_category (buf, d->root->subs[j]);
1014     }
1015   free (x2);
1016
1017   /* Axes. */
1018   put_u32 (buf, table->axes[PIVOT_AXIS_LAYER].n_dimensions);
1019   put_u32 (buf, table->axes[PIVOT_AXIS_ROW].n_dimensions);
1020   put_u32 (buf, table->axes[PIVOT_AXIS_COLUMN].n_dimensions);
1021   for (size_t i = 0; i < table->axes[PIVOT_AXIS_LAYER].n_dimensions; i++)
1022     put_u32 (buf, table->axes[PIVOT_AXIS_LAYER].dimensions[i]->top_index);
1023   for (size_t i = 0; i < table->axes[PIVOT_AXIS_ROW].n_dimensions; i++)
1024     put_u32 (buf, table->axes[PIVOT_AXIS_ROW].dimensions[i]->top_index);
1025   for (size_t i = 0; i < table->axes[PIVOT_AXIS_COLUMN].n_dimensions; i++)
1026     put_u32 (buf, table->axes[PIVOT_AXIS_COLUMN].dimensions[i]->top_index);
1027
1028   /* Cells. */
1029   put_u32 (buf, hmap_count (&table->cells));
1030   const struct pivot_cell *cell;
1031   HMAP_FOR_EACH (cell, struct pivot_cell, hmap_node, &table->cells)
1032     {
1033       uint64_t index = 0;
1034       for (size_t j = 0; j < table->n_dimensions; j++)
1035         index = (table->dimensions[j]->n_leaves * index) + cell->idx[j];
1036       put_u64 (buf, index);
1037
1038       put_value (buf, cell->value);
1039     }
1040 }
1041
1042 void
1043 spv_writer_put_table (struct spv_writer *w, const struct pivot_table *table)
1044 {
1045   struct pivot_table *table_rw = CONST_CAST (struct pivot_table *, table);
1046   if (!table_rw->subtype)
1047     table_rw->subtype = pivot_value_new_user_text ("unknown", -1);
1048
1049   int table_id = ++w->n_tables;
1050
1051   bool initial_depth = w->heading_depth;
1052   if (!initial_depth)
1053     spv_writer_open_file (w);
1054
1055   start_container (w);
1056
1057   char *title = pivot_value_to_string (table->title, table);
1058   char *subtype = pivot_value_to_string (table->subtype, table);
1059   
1060   start_elem (w, "label");
1061   write_text (w, title);
1062   end_elem (w);
1063
1064   start_elem (w, "vtb:table");
1065   write_attr (w, "commandName", table->command_c);
1066   write_attr (w, "type", "table"); /* XXX */
1067   write_attr (w, "subType", subtype);
1068   write_attr_format (w, "tableId", "%d", table_id);
1069
1070   free (subtype);
1071   free (title);
1072
1073   start_elem (w, "vtb:tableStructure");
1074   start_elem (w, "vtb:dataPath");
1075   char *data_path = xasprintf ("%010d_lightTableData.bin", table_id);
1076   write_text (w, data_path);
1077   end_elem (w); /* vtb:dataPath */
1078   end_elem (w); /* vtb:tableStructure */
1079   end_elem (w); /* vtb:table */
1080   end_elem (w); /* container */
1081
1082   if (!initial_depth)
1083     spv_writer_close_file (w, "");
1084
1085   struct buf buf = { NULL, 0, 0 };
1086   put_light_table (&buf, table_id, table);
1087   zip_writer_add_memory (w->zw, data_path, buf.data, buf.len);
1088   free (buf.data);
1089
1090   free (data_path);
1091 }