pspp-output: New command get-table-look.
[pspp] / src / output / spv / spv-table-look.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2017, 2018, 2020 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-table-look.h"
20
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <libxml/xmlreader.h>
24 #include <libxml/xmlwriter.h>
25 #include <string.h>
26
27 #include "output/spv/structure-xml-parser.h"
28
29 #include "gl/read-file.h"
30 #include "gl/xalloc.h"
31
32 #include "gettext.h"
33 #define _(msgid) gettext (msgid)
34
35 static struct cell_color
36 optional_color (int color, struct cell_color default_color)
37 {
38   return (color >= 0
39           ? (struct cell_color) CELL_COLOR (color >> 16, color >> 8, color)
40           : default_color);
41 }
42
43 static int
44 optional_length (const char *s, int default_length)
45 {
46   /* There is usually a "pt" suffix.  We ignore it. */
47   int length;
48   return s && sscanf (s, "%d", &length) == 1 ? length : default_length;
49 }
50
51 static int
52 optional_px (double inches, int default_px)
53 {
54   return inches != DBL_MAX ? inches * 96.0 : default_px;
55 }
56
57 static int
58 optional_int (int x, int default_value)
59 {
60   return x != INT_MIN ? x : default_value;
61 }
62
63 static int
64 optional_pt (double inches, int default_pt)
65 {
66   return inches != DBL_MAX ? inches * 72.0 + .5 : default_pt;
67 }
68
69 static const char *pivot_area_names[PIVOT_N_AREAS] = {
70   [PIVOT_AREA_TITLE] = "title",
71   [PIVOT_AREA_CAPTION] = "caption",
72   [PIVOT_AREA_FOOTER] = "footnotes",
73   [PIVOT_AREA_CORNER] = "cornerLabels",
74   [PIVOT_AREA_COLUMN_LABELS] = "columnLabels",
75   [PIVOT_AREA_ROW_LABELS] = "rowLabels",
76   [PIVOT_AREA_DATA] = "data",
77   [PIVOT_AREA_LAYERS] = "layers",
78 };
79
80 static enum pivot_area
81 pivot_area_from_name (const char *name)
82 {
83   enum pivot_area area;
84   for (area = 0; area < PIVOT_N_AREAS; area++)
85     if (!strcmp (name, pivot_area_names[area]))
86       break;
87   return area;
88 }
89
90 static const char *pivot_border_names[PIVOT_N_BORDERS] = {
91   [PIVOT_BORDER_TITLE] = "titleLayerSeparator",
92   [PIVOT_BORDER_OUTER_LEFT] = "leftOuterFrame",
93   [PIVOT_BORDER_OUTER_TOP] = "topOuterFrame",
94   [PIVOT_BORDER_OUTER_RIGHT] = "rightOuterFrame",
95   [PIVOT_BORDER_OUTER_BOTTOM] = "bottomOuterFrame",
96   [PIVOT_BORDER_INNER_LEFT] = "leftInnerFrame",
97   [PIVOT_BORDER_INNER_TOP] = "topInnerFrame",
98   [PIVOT_BORDER_INNER_RIGHT] = "rightInnerFrame",
99   [PIVOT_BORDER_INNER_BOTTOM] = "bottomInnerFrame",
100   [PIVOT_BORDER_DATA_LEFT] = "dataAreaLeft",
101   [PIVOT_BORDER_DATA_TOP] = "dataAreaTop",
102   [PIVOT_BORDER_DIM_ROW_HORZ] = "horizontalDimensionBorderRows",
103   [PIVOT_BORDER_DIM_ROW_VERT] = "verticalDimensionBorderRows",
104   [PIVOT_BORDER_DIM_COL_HORZ] = "horizontalDimensionBorderColumns",
105   [PIVOT_BORDER_DIM_COL_VERT] = "verticalDimensionBorderColumns",
106   [PIVOT_BORDER_CAT_ROW_HORZ] = "horizontalCategoryBorderRows",
107   [PIVOT_BORDER_CAT_ROW_VERT] = "verticalCategoryBorderRows",
108   [PIVOT_BORDER_CAT_COL_HORZ] = "horizontalCategoryBorderColumns",
109   [PIVOT_BORDER_CAT_COL_VERT] = "verticalCategoryBorderColumns",
110 };
111
112 static enum pivot_border
113 pivot_border_from_name (const char *name)
114 {
115   enum pivot_border border;
116   for (border = 0; border < PIVOT_N_BORDERS; border++)
117     if (!strcmp (name, pivot_border_names[border]))
118       break;
119   return border;
120 }
121
122 char * WARN_UNUSED_RESULT
123 spv_table_look_decode (const struct spvsx_table_properties *in,
124                        struct spv_table_look **outp)
125 {
126   struct spv_table_look *out = xzalloc (sizeof *out);
127   char *error = NULL;
128
129   out->name = in->name ? xstrdup (in->name) : NULL;
130
131   const struct spvsx_general_properties *g = in->general_properties;
132   out->omit_empty = g->hide_empty_rows != 0;
133   out->width_ranges[TABLE_HORZ][0] = optional_pt (g->minimum_column_width, -1);
134   out->width_ranges[TABLE_HORZ][1] = optional_pt (g->maximum_column_width, -1);
135   out->width_ranges[TABLE_VERT][0] = optional_pt (g->minimum_row_width, -1);
136   out->width_ranges[TABLE_VERT][1] = optional_pt (g->maximum_row_width, -1);
137   out->row_labels_in_corner
138     = g->row_dimension_labels != SPVSX_ROW_DIMENSION_LABELS_NESTED;
139
140   const struct spvsx_footnote_properties *f = in->footnote_properties;
141   out->footnote_marker_superscripts
142     = (f->marker_position != SPVSX_MARKER_POSITION_SUBSCRIPT);
143   out->show_numeric_markers
144     = (f->number_format == SPVSX_NUMBER_FORMAT_NUMERIC);
145
146   for (int i = 0; i < PIVOT_N_AREAS; i++)
147     area_style_copy (NULL, &out->areas[i], pivot_area_get_default_style (i));
148
149   const struct spvsx_cell_format_properties *cfp = in->cell_format_properties;
150   for (size_t i = 0; i < cfp->n_cell_style; i++)
151     {
152       const struct spvsx_cell_style *c = cfp->cell_style[i];
153       const char *name = CHAR_CAST (const char *, c->node_.raw->name);
154       enum pivot_area area = pivot_area_from_name (name);
155       if (area == PIVOT_N_AREAS)
156         {
157           error = xasprintf ("unknown area \"%s\" in cellFormatProperties",
158                              name);
159           goto error;
160         }
161
162       struct area_style *a = &out->areas[area];
163       const struct spvsx_style *s = c->style;
164       if (s->font_weight)
165         a->font_style.bold = s->font_weight == SPVSX_FONT_WEIGHT_BOLD;
166       if (s->font_style)
167         a->font_style.italic = s->font_style == SPVSX_FONT_STYLE_ITALIC;
168       if (s->font_underline)
169         a->font_style.underline
170           = s->font_underline == SPVSX_FONT_UNDERLINE_UNDERLINE;
171       if (s->color >= 0)
172         a->font_style.fg[0] = optional_color (
173           s->color, (struct cell_color) CELL_COLOR_BLACK);
174       if (c->alternating_text_color >= 0 || s->color >= 0)
175         a->font_style.fg[1] = optional_color (c->alternating_text_color,
176                                               a->font_style.fg[0]);
177       if (s->color2 >= 0)
178         a->font_style.bg[0] = optional_color (
179           s->color2, (struct cell_color) CELL_COLOR_WHITE);
180       if (c->alternating_color >= 0 || s->color2 >= 0)
181         a->font_style.bg[1] = optional_color (c->alternating_color,
182                                               a->font_style.bg[0]);
183       if (s->font_family)
184         {
185           free (a->font_style.typeface);
186           a->font_style.typeface = xstrdup (s->font_family);
187         }
188
189       if (s->font_size)
190         a->font_style.size = optional_length (s->font_size, 0);
191
192       if (s->text_alignment)
193         a->cell_style.halign
194           = (s->text_alignment == SPVSX_TEXT_ALIGNMENT_LEFT
195              ? TABLE_HALIGN_LEFT
196              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_RIGHT
197              ? TABLE_HALIGN_RIGHT
198              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_CENTER
199              ? TABLE_HALIGN_CENTER
200              : s->text_alignment == SPVSX_TEXT_ALIGNMENT_DECIMAL
201              ? TABLE_HALIGN_DECIMAL
202              : TABLE_HALIGN_MIXED);
203       if (s->label_location_vertical)
204         a->cell_style.valign
205           = (s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_NEGATIVE
206              ? TABLE_VALIGN_BOTTOM
207              : s->label_location_vertical == SPVSX_LABEL_LOCATION_VERTICAL_POSITIVE
208              ? TABLE_VALIGN_TOP
209              : TABLE_VALIGN_CENTER);
210
211       if (s->decimal_offset != DBL_MAX)
212         a->cell_style.decimal_offset = optional_px (s->decimal_offset, 0);
213
214       if (s->margin_left != DBL_MAX)
215         a->cell_style.margin[TABLE_HORZ][0] = optional_px (s->margin_left, 8);
216       if (s->margin_right != DBL_MAX)
217         a->cell_style.margin[TABLE_HORZ][1] = optional_px (s->margin_right,
218                                                            11);
219       if (s->margin_top != DBL_MAX)
220         a->cell_style.margin[TABLE_VERT][0] = optional_px (s->margin_top, 1);
221       if (s->margin_bottom != DBL_MAX)
222         a->cell_style.margin[TABLE_VERT][1] = optional_px (s->margin_bottom,
223                                                            1);
224     }
225
226   for (int i = 0; i < PIVOT_N_BORDERS; i++)
227     pivot_border_get_default_style (i, &out->borders[i]);
228
229   const struct spvsx_border_properties *bp = in->border_properties;
230   for (size_t i = 0; i < bp->n_border_style; i++)
231     {
232       const struct spvsx_border_style *bin = bp->border_style[i];
233       const char *name = CHAR_CAST (const char *, bin->node_.raw->name);
234       enum pivot_border border = pivot_border_from_name (name);
235       if (border == PIVOT_N_BORDERS)
236         {
237           error = xasprintf ("unknown border \"%s\" parsing borderProperties",
238                              name);
239           goto error;
240         }
241
242       struct table_border_style *bout = &out->borders[border];
243       bout->stroke
244         = (bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_NONE
245            ? TABLE_STROKE_NONE
246            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DASHED
247            ? TABLE_STROKE_DASHED
248            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THICK
249            ? TABLE_STROKE_THICK
250            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_THIN
251            ? TABLE_STROKE_THIN
252            : bin->border_style_type == SPVSX_BORDER_STYLE_TYPE_DOUBLE
253            ? TABLE_STROKE_DOUBLE
254            : TABLE_STROKE_SOLID);
255       bout->color = optional_color (bin->color,
256                                     (struct cell_color) CELL_COLOR_BLACK);
257     }
258
259   const struct spvsx_printing_properties *pp = in->printing_properties;
260   out->print_all_layers = pp->print_all_layers > 0;
261   out->paginate_layers = pp->print_each_layer_on_separate_page > 0;
262   out->shrink_to_width = pp->rescale_wide_table_to_fit_page > 0;
263   out->shrink_to_length = pp->rescale_long_table_to_fit_page > 0;
264   out->top_continuation = pp->continuation_text_at_top > 0;
265   out->bottom_continuation = pp->continuation_text_at_bottom > 0;
266   out->continuation = xstrdup (pp->continuation_text
267                                ? pp->continuation_text : "(cont.)");
268   out->n_orphan_lines = optional_int (pp->window_orphan_lines, 2);
269
270   *outp = out;
271   return NULL;
272
273 error:
274   spv_table_look_destroy (out);
275   *outp = NULL;
276   return error;
277 }
278
279 char * WARN_UNUSED_RESULT
280 spv_table_look_read (const char *filename, struct spv_table_look **outp)
281 {
282   *outp = NULL;
283
284   size_t length;
285   char *file = read_file (filename, 0, &length);
286   if (!file)
287     return xasprintf ("%s: failed to read file (%s)",
288                       filename, strerror (errno));
289
290   xmlDoc *doc = xmlReadMemory (file, length, NULL, NULL, XML_PARSE_NOBLANKS);
291   free (file);
292   if (!doc)
293     return xasprintf ("%s: failed to parse XML", filename);
294
295   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
296   struct spvsx_table_properties *tp;
297   spvsx_parse_table_properties (&ctx, xmlDocGetRootElement (doc), &tp);
298   char *error = spvxml_context_finish (&ctx, &tp->node_);
299
300   if (!error)
301     error = spv_table_look_decode (tp, outp);
302
303   spvsx_free_table_properties (tp);
304   xmlFreeDoc (doc);
305
306   return error;
307 }
308
309 static void
310 write_attr (xmlTextWriter *xml, const char *name, const char *value)
311 {
312   xmlTextWriterWriteAttribute (xml,
313                                CHAR_CAST (xmlChar *, name),
314                                CHAR_CAST (xmlChar *, value));
315 }
316
317 static void PRINTF_FORMAT (3, 4)
318 write_attr_format (xmlTextWriter *xml, const char *name,
319                    const char *format, ...)
320 {
321   va_list args;
322   va_start (args, format);
323   char *value = xvasprintf (format, args);
324   va_end (args);
325
326   write_attr (xml, name, value);
327   free (value);
328 }
329
330 static void
331 write_attr_color (xmlTextWriter *xml, const char *name,
332                   const struct cell_color *color)
333 {
334   write_attr_format (xml, name, "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
335                      color->r, color->g, color->b);
336 }
337
338 static void
339 write_attr_dimension (xmlTextWriter *xml, const char *name, int px)
340 {
341   int pt = px / 96.0 * 72.0;
342   write_attr_format (xml, name, "%dpt", pt);
343 }
344
345 static void
346 write_attr_bool (xmlTextWriter *xml, const char *name, bool b)
347 {
348   write_attr (xml, name, b ? "true" : "false");
349 }
350
351 static void
352 start_elem (xmlTextWriter *xml, const char *name)
353 {
354   xmlTextWriterStartElement (xml, CHAR_CAST (xmlChar *, name));
355 }
356
357 static void
358 end_elem (xmlTextWriter *xml)
359 {
360   xmlTextWriterEndElement (xml);
361 }
362
363 char * WARN_UNUSED_RESULT
364 spv_table_look_write (const char *filename, const struct spv_table_look *look)
365 {
366   FILE *file = fopen (filename, "w");
367   if (!file)
368     return xasprintf (_("%s: create failed (%s)"), filename, strerror (errno));
369
370   xmlTextWriter *xml = xmlNewTextWriter (xmlOutputBufferCreateFile (
371                                            file, NULL));
372   if (!xml)
373     {
374       fclose (file);
375       return xasprintf (_("%s: failed to start writing XML"), filename);
376     }
377
378   xmlTextWriterSetIndent (xml, 1);
379   xmlTextWriterSetIndentString (xml, CHAR_CAST (xmlChar *, "    "));
380
381   xmlTextWriterStartDocument (xml, NULL, "UTF-8", NULL);
382   start_elem (xml, "tableProperties");
383   if (look->name)
384     write_attr (xml, "name", look->name);
385   write_attr (xml, "xmlns", "http://www.ibm.com/software/analytics/spss/xml/table-looks");
386   write_attr (xml, "xmlns:vizml", "http://www.ibm.com/software/analytics/spss/xml/visualization");
387   write_attr (xml, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
388   write_attr (xml, "xsi:schemaLocation", "http://www.ibm.com/software/analytics/spss/xml/table-looks http://www.ibm.com/software/analytics/spss/xml/table-looks/table-looks-1.4.xsd");
389
390   start_elem (xml, "generalProperties");
391   write_attr_bool (xml, "hideEmptyRows", look->omit_empty);
392   const int (*wr)[2] = look->width_ranges;
393   write_attr_format (xml, "maximumColumnWidth", "%d", wr[TABLE_HORZ][1]);
394   write_attr_format (xml, "maximumRowWidth", "%d", wr[TABLE_VERT][1]);
395   write_attr_format (xml, "minimumColumnWidth", "%d", wr[TABLE_HORZ][0]);
396   write_attr_format (xml, "minimumRowWidth", "%d", wr[TABLE_VERT][0]);
397   write_attr (xml, "rowDimensionLabels",
398               look->row_labels_in_corner ? "inCorner" : "nested");
399   end_elem (xml);
400
401   start_elem (xml, "footnoteProperties");
402   write_attr (xml, "markerPosition",
403               look->footnote_marker_superscripts ? "superscript" : "subscript");
404   write_attr (xml, "numberFormat",
405               look->show_numeric_markers ? "numeric" : "alphabetic");
406   end_elem (xml);
407
408   start_elem (xml, "cellFormatProperties");
409   for (enum pivot_area a = 0; a < PIVOT_N_AREAS; a++)
410     {
411       const struct area_style *area = &look->areas[a];
412       const struct font_style *font = &area->font_style;
413       const struct cell_style *cell = &area->cell_style;
414
415       start_elem (xml, pivot_area_names[a]);
416       if (a == PIVOT_AREA_DATA
417           && (!cell_color_equal (&font->fg[0], &font->fg[1])
418               || !cell_color_equal (&font->bg[0], &font->bg[1])))
419         {
420           write_attr_color (xml, "alternatingColor", &font->bg[1]);
421           write_attr_color (xml, "alternatingTextColor", &font->fg[1]);
422         }
423
424       start_elem (xml, "vizml:style");
425       write_attr_color (xml, "color", &font->fg[0]);
426       write_attr_color (xml, "color2", &font->bg[0]);
427       write_attr (xml, "font-family", font->typeface);
428       write_attr_format (xml, "font-size", "%dpt", font->size);
429       write_attr (xml, "font-weight", font->bold ? "bold" : "regular");
430       write_attr (xml, "font-underline",
431                   font->underline ? "underline" : "none");
432       write_attr (xml, "labelLocationVertical",
433                   cell->valign == TABLE_VALIGN_BOTTOM ? "negative"
434                   : cell->valign == TABLE_VALIGN_TOP ? "positive"
435                   : "center");
436       write_attr_dimension (xml, "margin-bottom", cell->margin[TABLE_VERT][1]);
437       write_attr_dimension (xml, "margin-left", cell->margin[TABLE_HORZ][0]);
438       write_attr_dimension (xml, "margin-right", cell->margin[TABLE_HORZ][1]);
439       write_attr_dimension (xml, "margin-top", cell->margin[TABLE_VERT][0]);
440       write_attr (xml, "textAlignment",
441                   cell->halign == TABLE_HALIGN_LEFT ? "left"
442                   : cell->halign == TABLE_HALIGN_RIGHT ? "right"
443                   : cell->halign == TABLE_HALIGN_CENTER ? "center"
444                   : cell->halign == TABLE_HALIGN_DECIMAL ? "decimal"
445                   : "mixed");
446       if (cell->halign == TABLE_HALIGN_DECIMAL)
447         write_attr_dimension (xml, "decimal-offset", cell->decimal_offset);
448       end_elem (xml);
449
450       end_elem (xml);
451     }
452   end_elem (xml);
453
454   start_elem (xml, "borderProperties");
455   for (enum pivot_border b = 0; b < PIVOT_N_BORDERS; b++)
456     {
457       const struct table_border_style *border = &look->borders[b];
458
459       start_elem (xml, pivot_border_names[b]);
460
461       static const char *table_stroke_names[TABLE_N_STROKES] =
462         {
463           [TABLE_STROKE_NONE] = "none",
464           [TABLE_STROKE_SOLID] = "solid",
465           [TABLE_STROKE_DASHED] = "dashed",
466           [TABLE_STROKE_THICK] = "thick",
467           [TABLE_STROKE_THIN] = "thin",
468           [TABLE_STROKE_DOUBLE] = "double",
469         };
470       write_attr (xml, "borderStyleType", table_stroke_names[border->stroke]);
471       write_attr_color (xml, "color", &border->color);
472       end_elem (xml);
473     }
474   end_elem (xml);
475
476   start_elem (xml, "printingProperties");
477   write_attr_bool (xml, "printAllLayers", look->print_all_layers);
478   write_attr_bool (xml, "rescaleLongTableToFitPage", look->shrink_to_length);
479   write_attr_bool (xml, "rescaleWideTableToFitPage", look->shrink_to_width);
480   write_attr_format (xml, "windowOrphanLines", "%zu", look->n_orphan_lines);
481   if (look->continuation && look->continuation[0]
482       && (look->top_continuation || look->bottom_continuation))
483     {
484       write_attr_format (xml, "continuationText", look->continuation);
485       write_attr_bool (xml, "continuationTextAtTop", look->top_continuation);
486       write_attr_bool (xml, "continuationTextAtBottom",
487                        look->bottom_continuation);
488     }
489   end_elem (xml);
490
491   xmlTextWriterEndDocument (xml);
492
493   xmlFreeTextWriter (xml);
494
495   fflush (file);
496   bool ok = !ferror (file);
497   if (fclose (file) == EOF)
498     ok = false;
499
500   if (!ok)
501     return xasprintf (_("%s: error writing file (%s)"),
502                       filename, strerror (errno));
503
504   return NULL;
505 }
506
507 void
508 spv_table_look_destroy (struct spv_table_look *look)
509 {
510   if (look)
511     {
512       free (look->name);
513       for (size_t i = 0; i < PIVOT_N_AREAS; i++)
514         area_style_uninit (&look->areas[i]);
515       free (look->continuation);
516       free (look);
517     }
518 }
519
520 void
521 spv_table_look_install (const struct spv_table_look *look,
522                         struct pivot_table *table)
523 {
524   free (table->table_look);
525   if (look->name)
526     table->table_look = xstrdup (look->name);
527
528   table->omit_empty = look->omit_empty;
529
530   for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
531     for (int i = 0; i < 2; i++)
532       if (look->width_ranges[axis][i] > 0)
533         table->sizing[axis].range[i] = look->width_ranges[axis][i];
534   table->row_labels_in_corner = look->row_labels_in_corner;
535
536   table->footnote_marker_superscripts = look->footnote_marker_superscripts;
537   table->show_numeric_markers = look->show_numeric_markers;
538
539   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
540     {
541       area_style_uninit (&table->areas[i]);
542       area_style_copy (NULL, &table->areas[i], &look->areas[i]);
543     }
544   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
545     table->borders[i] = look->borders[i];
546
547   table->print_all_layers = look->print_all_layers;
548   table->paginate_layers = look->paginate_layers;
549   table->shrink_to_fit[TABLE_HORZ] = look->shrink_to_width;
550   table->shrink_to_fit[TABLE_VERT] = look->shrink_to_length;
551   table->top_continuation = look->top_continuation;
552   table->bottom_continuation = look->bottom_continuation;
553   table->continuation = xstrdup (look->continuation);
554   table->n_orphan_lines = look->n_orphan_lines;
555 }
556
557 struct spv_table_look *
558 spv_table_look_get (const struct pivot_table *table)
559 {
560   struct spv_table_look *look = xzalloc (sizeof *look);
561
562   look->name = table->table_look ? xstrdup (table->table_look) : NULL;
563
564   look->omit_empty = table->omit_empty;
565
566   for (enum table_axis axis = 0; axis < TABLE_N_AXES; axis++)
567     for (int i = 0; i < 2; i++)
568       look->width_ranges[axis][i] = table->sizing[axis].range[i];
569   look->row_labels_in_corner = table->row_labels_in_corner;
570
571   look->footnote_marker_superscripts = table->footnote_marker_superscripts;
572   look->show_numeric_markers = table->show_numeric_markers;
573
574   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
575     area_style_copy (NULL, &look->areas[i], &table->areas[i]);
576   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
577     look->borders[i] = table->borders[i];
578
579   look->print_all_layers = table->print_all_layers;
580   look->paginate_layers = table->paginate_layers;
581   look->shrink_to_width = table->shrink_to_fit[TABLE_HORZ];
582   look->shrink_to_length = table->shrink_to_fit[TABLE_VERT];
583   look->top_continuation = table->top_continuation;
584   look->bottom_continuation = table->bottom_continuation;
585   look->continuation = xstrdup (table->continuation);
586   look->n_orphan_lines = table->n_orphan_lines;
587
588   return look;
589 }