output: Correctly define and properly implement column width ranges.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 6 Feb 2023 16:17:28 +0000 (08:17 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 10 Feb 2023 19:07:05 +0000 (11:07 -0800)
I previously had the idea that SPV files had ranges for both column
widths and row heights, which didn't really make sense, since if
you fix the range of one of those you have to be flexible about the
other.  However, these settings actually control just column widths,
in two sets of columns: the columns for row headings and the columns
for column headings.  This implements those properly.

13 files changed:
doc/dev/spv-file-format.texi
src/language/commands/ctables.c
src/output/cairo-fsm.c
src/output/pivot-table.c
src/output/pivot-table.h
src/output/render.c
src/output/render.h
src/output/spv/light-binary.grammar
src/output/spv/spv-legacy-decoder.c
src/output/spv/spv-light-decoder.c
src/output/spv/spv-table-look.c
src/output/spv/spv-writer.c
src/output/spv/tlo.grammar

index 0cf15f94d9934d8da08f89f595b27ee285442985..523eb556e9e61538120f9cd33d7fb4974e6b7642 100644 (file)
@@ -1051,8 +1051,8 @@ Header =>
     bool[rotate-outer-row-labels]
     bool[x2]
     int32[x3]
-    int32[min-col-width] int32[max-col-width]
-    int32[min-row-width] int32[max-row-width]
+    int32[min-col-heading-width] int32[max-col-heading-width]
+    int32[min-row-heading-width] int32[max-row-heading-width]
     int64[table-id]
 @end example
 
@@ -1069,12 +1069,37 @@ If @code{rotate-outer-row-labels} is 1, then row labels farthest from
 the data are rotated 90° counterclockwise; otherwise, they are shown
 in the normal way.
 
-@code{min-col-width} is the minimum width that a column will be
-assigned automatically.  @code{max-col-width} is the maximum width
-that a column will be assigned to accommodate a long column label.
-@code{min-row-width} and @code{max-row-width} are a similar range for
-the width of row labels.  All of these measurements are in 1/96 inch
-units (called a ``device independent pixel'' unit in Windows).
+@code{min-col-heading-width}, @code{max-col-heading-width}, @code{min-row-heading-width}, and
+@code{max-row-heading-width} are measurements in 1/96 inch units (called
+``device independent pixel'' units in Windows) whose values influence
+column widths.  For the purpose of interpreting these values, a table
+is divided into the three regions shown below:
+
+@example
++------------------+-------------------------------------------------+
+|                  |                  column headings                |
+|                  +-------------------------------------------------+
+|      corner      |                                                 |
+|       and        |                                                 |
+|   row headings   |                      data                       |
+|                  |                                                 |
+|                  |                                                 |
++------------------+-------------------------------------------------+
+@end example
+
+@code{min-col-heading-width} and @code{max-col-heading-width} apply to the columns in
+the column headings region.  @code{min-col-heading-width} is the minimum width
+that any of these columns will be given automatically.  In addition,
+@code{max-col-heading-width} is the maximum width that a column will be
+assigned to accommodate a long label in the column headings cells.
+These columns will still be made wider to accommodate wide data values
+in the data region.
+
+@code{min-row-heading-width} is the minimum width that a column in the corner
+and row headings region will be given automatically.
+@code{max-col-heading-width} is the maximum width that a column in this region
+will be assigned to accomodate a long label.  This region doesn't
+include data, so data values don't affect column widths.
 
 @code{table-id} is a binary version of the @code{tableId} attribute in
 the structure member that refers to the detail member.  For example,
index 3f238a7f10c847208c1c166cb685286ab71e8bcc..d25832b7e69949921fb84f9c3cd6f49f592206a3 100644 (file)
@@ -6137,7 +6137,7 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
           for (size_t i = 0; i < 2; i++)
             if (widths[i] != SYSMIS)
               {
-                int *wr = ct->look->width_ranges[TABLE_HORZ];
+                int *wr = ct->look->col_heading_width_range;
                 wr[i] = widths[i] / units_per_inch * 96.0;
                 if (wr[0] > wr[1])
                   wr[!i] = wr[i];
index 51f48efb750d087d110930de8c14ec149ecf7eaf..5d9b60a443f3c241e60e9749d2507ee93d4704c6 100644 (file)
@@ -1053,6 +1053,7 @@ xr_fsm_create (const struct output_item *item_,
       .size = { [H] = style->size[H], [V] = style->size[V] },
       .line_widths = xr_line_widths,
       .min_break = { [H] = style->min_break[H], [V] = style->min_break[V] },
+      .px_size = px_to_xr (1),
       .supports_margins = true,
       .rtl = render_direction_rtl (),
       .printing = print,
index 81a8b319259f36d72b4ae47a957e2f795968e620..661813f28b22d0522a8fdbe5465bdf71796f95b3 100644 (file)
@@ -218,10 +218,8 @@ pivot_table_look_builtin_default (void)
 
     .omit_empty = true,
     .row_labels_in_corner = true,
-    .width_ranges = {
-      [TABLE_HORZ] = { 36, 72 },
-      [TABLE_VERT] = { 36, 120 },
-    },
+    .col_heading_width_range = { 36, 72 },
+    .row_heading_width_range = { 36, 120 },
 
     .areas = {
 #define AREA(BOLD, H, V, L, R, T, B) {                         \
@@ -2069,9 +2067,9 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
 
   indent (indentation);
   printf ("sizing:\n");
-  pivot_table_sizing_dump ("column", table->look->width_ranges[TABLE_HORZ],
+  pivot_table_sizing_dump ("column", table->look->col_heading_width_range,
                            &table->sizing[TABLE_HORZ], indentation + 1);
-  pivot_table_sizing_dump ("row", table->look->width_ranges[TABLE_VERT],
+  pivot_table_sizing_dump ("row", table->look->row_heading_width_range,
                            &table->sizing[TABLE_VERT], indentation + 1);
 
   indent (indentation);
index d45f3138c3e2eaad5c34f5bfef480a4d3433a7ff..145e356cf3ef83a0a869e25e78fef4a0be29eb80 100644 (file)
@@ -429,10 +429,18 @@ struct pivot_table_look
 
     char *name;                 /* May be null. */
 
-    /* General properties. */
+    /* General properties.
+
+       col_heading_width_range and row_heading_width_range is minimum and
+       maximum width of columns based on their column heading labels, in 1/96"
+       units.  row_heading_width_range is for columns in the row headings and
+       corner, and col_heading_width_range is for columns in the column
+       headings.
+    */
     bool omit_empty;
     bool row_labels_in_corner;
-    int width_ranges[TABLE_N_AXES][2];      /* In 1/96" units. */
+    int row_heading_width_range[2];
+    int col_heading_width_range[2];
 
     /* Footnote display settings. */
     bool show_numeric_markers;
index 76f5017fa322d3e859e99df394cc82f33d4a3863..c8766266f2af2268b8c98744c6dddd3aeb826f7b 100644 (file)
@@ -142,10 +142,40 @@ struct render_page
        so no part of the cell's content is lost (and in fact it is duplicated
        across both pages). */
     int *join_crossing[TABLE_N_AXES];
+
+    /* Minimum and maximum widths of columns based on headings.
+
+       For this purpose, a table has the following three regions:
+
+       +------------------+-------------------------------------------------+
+       |                  |                  column headings                |
+       |                  +-------------------------------------------------+
+       |      corner      |                                                 |
+       |       and        |                                                 |
+       |   row headings   |                      data                       |
+       |                  |                                                 |
+       |                  |                                                 |
+       +------------------+-------------------------------------------------+
+
+       - width_ranges[TABLE_HORZ] controls the minimum and maximum width that
+         columns in the column headings will be based on the column headings
+         themselves.  That is, these columns will have width at least
+         width_ranges[TABLE_HORZ][0] wide, and no more than
+         width_ranges[TABLE_HORZ][1] unless the data requires it.
+
+       - width_ranges[TABLE_VERT] controls the minimum and maximum width that
+         columns in the corner and row headings will be based on the corner and
+         row headings themselves.  That is, these columns will have width at
+         least width_ranges[TABLE_VERT][0] wide, and no more than
+         width_ranges[TABLE_VERT][1].  (The corner and row headings don't have
+         data in their columns so data can't affect their widths.)
+    */
+    int width_ranges[TABLE_N_AXES][2];
   };
 
 static struct render_page *render_page_create (const struct render_params *,
-                                               struct table *, int min_width);
+                                               struct table *, int min_width,
+                                               const struct pivot_table_look *);
 
 struct render_page *render_page_ref (const struct render_page *page_);
 static void render_page_unref (struct render_page *);
@@ -677,15 +707,15 @@ render_get_cell (const struct render_page *page, int x, int y,
     }
 }
 
-/* Creates and returns a new render_page for rendering TABLE on a device
-   described by PARAMS.
+/* Creates and returns a new render_page for rendering TABLE with the given
+   LOOK on a device described by PARAMS.
 
    The new render_page will be suitable for rendering on a device whose page
    size is PARAMS->size, but the caller is responsible for actually breaking it
    up to fit on such a device, using the render_break abstraction.  */
 static struct render_page *
 render_page_create (const struct render_params *params, struct table *table,
-                    int min_width)
+                    int min_width, const struct pivot_table_look *look)
 {
   enum { MIN, MAX };
 
@@ -703,6 +733,13 @@ render_page_create (const struct render_params *params, struct table *table,
         rules[axis][z] = measure_rule (params, table, axis, z);
     }
 
+  int col_heading_width_range[2];
+  int row_heading_width_range[2];
+  for (int i = 0; i < 2; i++)
+    col_heading_width_range[i] = look->col_heading_width_range[i] * params->px_size;
+  for (int i = 0; i < 2; i++)
+    row_heading_width_range[i] = look->row_heading_width_range[i] * params->px_size;
+
   /* Calculate minimum and maximum widths of cells that do not
      span multiple columns. */
   struct render_row *columns[2];
@@ -721,6 +758,29 @@ render_page_create (const struct render_params *params, struct table *table,
                 int w[2];
                 params->ops->measure_cell_width (params->aux, &cell,
                                                  &w[MIN], &w[MAX]);
+
+                if (params->px_size)
+                  {
+                    const int *wr = (x < table->h[H][0] ? row_heading_width_range
+                                     : y < table->h[V][0] ? col_heading_width_range
+                                     : NULL);
+                    if (wr)
+                      {
+                        if (w[0] < wr[0])
+                          {
+                            w[0] = wr[0];
+                            if (w[0] > w[1])
+                              w[1] = w[0];
+                          }
+                        else if (w[1] > wr[1])
+                          {
+                            w[1] = wr[1];
+                            if (w[1] < w[0])
+                              w[0] = w[1];
+                          }
+                      }
+                  }
+
                 for (int i = 0; i < 2; i++)
                   if (columns[i][x].unspanned < w[i])
                     columns[i][x].unspanned = w[i];
@@ -1488,10 +1548,11 @@ struct render_pager
 
 static void
 render_pager_add_table (struct render_pager *p, struct table *table,
-                        int min_width)
+                        int min_width, const struct pivot_table_look *look)
 {
   if (table)
-    p->pages[p->n_pages++] = render_page_create (p->params, table, min_width);
+    p->pages[p->n_pages++] = render_page_create (p->params, table, min_width,
+                                                 look);
 }
 
 static void
@@ -1518,7 +1579,7 @@ render_pager_create (const struct render_params *params,
 
   /* Figure out the width of the body of the table.  Use this to determine the
      base scale. */
-  struct render_page *body_page = render_page_create (params, body, 0);
+  struct render_page *body_page = render_page_create (params, body, 0, pt->look);
   int body_width = table_width (body_page, H);
   double scale = 1.0;
   if (body_width > params->size[H])
@@ -1540,11 +1601,11 @@ render_pager_create (const struct render_params *params,
   /* Create the pager. */
   struct render_pager *p = xmalloc (sizeof *p);
   *p = (struct render_pager) { .params = params, .scale = scale };
-  render_pager_add_table (p, title, body_width);
-  render_pager_add_table (p, layers, body_width);
+  render_pager_add_table (p, title, body_width, pt->look);
+  render_pager_add_table (p, layers, body_width, pt->look);
   p->pages[p->n_pages++] = body_page;
-  render_pager_add_table (p, caption, 0);
-  render_pager_add_table (p, footnotes, 0);
+  render_pager_add_table (p, caption, 0, pt->look);
+  render_pager_add_table (p, footnotes, 0, pt->look);
   assert (p->n_pages <= sizeof p->pages / sizeof *p->pages);
 
   /* If we're shrinking tables to fit the page length, then adjust the scale
index 89a7c4fe5c9f6851ac8b1f84ba68f7da0d075cae..e52eb148af50019d3ddfd7d26b79a5bfa5a5bc51 100644 (file)
@@ -55,6 +55,11 @@ struct render_params
     /* Width of different kinds of lines. */
     const int *line_widths;           /* RENDER_N_LINES members. */
 
+    /* 1/96" of an inch (1px) in the rendering unit.  Currently used only for
+       column width ranges (as specified in width_ranges in struct
+       pivot_table_look).  Set to 0 to disable this feature. */
+    int px_size;
+
     /* Minimum cell width or height before allowing the cell to be broken
        across two pages.  (Joined cells may always be broken at join
        points.) */
index 74d5315758e2e696a1e732706c1d9a945b2885a7..105e1dbca244a35d2723aee7a1ded4b4f2401a8a 100644 (file)
@@ -29,8 +29,8 @@ Header =>
    bool[rotate-outer-row-labels]
    bool[x2]
    int32[x3]
-   int32[min-col-width] int32[max-col-width]
-   int32[min-row-height] int32[max-row-height]
+   int32[min-col-heading-width] int32[max-col-heading-width]
+   int32[min-row-heading-width] int32[max-row-heading-width]
    int64[table-id]
 
 Titles =>
index 84397a04ab3179bc80be903689b0477efb969cbd..904a6815f1849ac5cb0baab700224f703a2a4663 100644 (file)
@@ -1793,8 +1793,8 @@ decode_spvdx_table (const struct spvdx_visualization *v, const char *subtype,
                   &min_width, &max_width, &n)
           && v->graph->cell_style->width[n] == '\0')
         {
-          table->look->width_ranges[TABLE_HORZ][0] = min_width;
-          table->look->width_ranges[TABLE_HORZ][1] = max_width;
+          table->look->col_heading_width_range[0] = min_width;
+          table->look->col_heading_width_range[1] = max_width;
         }
     }
 
index 7bc3b1fb272f469ab302920e2451cebfe2c927e3..5d7856df65b3f71f01cc437684702569e169dcfc 100644 (file)
@@ -876,10 +876,10 @@ decode_spvlb_table (const struct spvlb_table *in, struct pivot_table **outp)
     }
 
   /* Column and row display settings. */
-  out->look->width_ranges[TABLE_VERT][0] = in->header->min_row_height;
-  out->look->width_ranges[TABLE_VERT][1] = in->header->max_row_height;
-  out->look->width_ranges[TABLE_HORZ][0] = in->header->min_col_width;
-  out->look->width_ranges[TABLE_HORZ][1] = in->header->max_col_width;
+  out->look->row_heading_width_range[0] = in->header->min_row_heading_width;
+  out->look->row_heading_width_range[1] = in->header->max_row_heading_width;
+  out->look->col_heading_width_range[0] = in->header->min_col_heading_width;
+  out->look->col_heading_width_range[1] = in->header->max_col_heading_width;
 
   convert_widths (in->formats->widths, in->formats->n_widths,
                   &out->sizing[TABLE_HORZ].widths,
index 49266d899aba8c368e5c04f5435c0bb773f9492e..6b3c6d23459c34d8b09c7065a6c37a029a6fc95e 100644 (file)
@@ -134,10 +134,10 @@ spv_table_look_decode (const struct spvsx_table_properties *in,
 
   const struct spvsx_general_properties *g = in->general_properties;
   out->omit_empty = g->hide_empty_rows != 0;
-  out->width_ranges[TABLE_HORZ][0] = optional_pt (g->minimum_column_width, -1);
-  out->width_ranges[TABLE_HORZ][1] = optional_pt (g->maximum_column_width, -1);
-  out->width_ranges[TABLE_VERT][0] = optional_pt (g->minimum_row_width, -1);
-  out->width_ranges[TABLE_VERT][1] = optional_pt (g->maximum_row_width, -1);
+  out->col_heading_width_range[0] = optional_pt (g->minimum_column_width, -1);
+  out->col_heading_width_range[1] = optional_pt (g->maximum_column_width, -1);
+  out->row_heading_width_range[0] = optional_pt (g->minimum_row_width, -1);
+  out->row_heading_width_range[1] = optional_pt (g->maximum_row_width, -1);
   out->row_labels_in_corner
     = g->row_dimension_labels != SPVSX_ROW_DIMENSION_LABELS_NESTED;
 
@@ -380,17 +380,17 @@ tlo_decode (const struct tlo_table_look *in)
   out->row_labels_in_corner = !in->tl->nested_row_labels;
   if (in->v2_styles)
     {
-      out->width_ranges[TABLE_HORZ][0] = in->v2_styles->min_col_width;
-      out->width_ranges[TABLE_HORZ][1] = in->v2_styles->max_col_width;
-      out->width_ranges[TABLE_VERT][0] = in->v2_styles->min_row_height;
-      out->width_ranges[TABLE_VERT][1] = in->v2_styles->max_row_height;
+      out->col_heading_width_range[0] = in->v2_styles->min_col_heading_width;
+      out->col_heading_width_range[1] = in->v2_styles->max_col_heading_width;
+      out->row_heading_width_range[0] = in->v2_styles->min_row_heading_width;
+      out->row_heading_width_range[1] = in->v2_styles->max_row_heading_width;
     }
   else
     {
-      out->width_ranges[TABLE_HORZ][0] = 36;
-      out->width_ranges[TABLE_HORZ][1] = 72;
-      out->width_ranges[TABLE_VERT][0] = 36;
-      out->width_ranges[TABLE_VERT][1] = 120;
+      out->col_heading_width_range[0] = 36;
+      out->col_heading_width_range[1] = 72;
+      out->row_heading_width_range[0] = 36;
+      out->row_heading_width_range[1] = 120;
     }
 
   out->show_numeric_markers = flags & 0x04;
@@ -621,11 +621,12 @@ spv_table_look_write (const char *filename, const struct pivot_table_look *look)
 
   start_elem (xml, "generalProperties");
   write_attr_bool (xml, "hideEmptyRows", look->omit_empty);
-  const int (*wr)[2] = look->width_ranges;
-  write_attr_format (xml, "maximumColumnWidth", "%d", wr[TABLE_HORZ][1]);
-  write_attr_format (xml, "maximumRowWidth", "%d", wr[TABLE_VERT][1]);
-  write_attr_format (xml, "minimumColumnWidth", "%d", wr[TABLE_HORZ][0]);
-  write_attr_format (xml, "minimumRowWidth", "%d", wr[TABLE_VERT][0]);
+  const int *chwr = look->col_heading_width_range;
+  const int *rhwr = look->row_heading_width_range;
+  write_attr_format (xml, "maximumColumnWidth", "%d", chwr[1]);
+  write_attr_format (xml, "maximumRowWidth", "%d", rhwr[1]);
+  write_attr_format (xml, "minimumColumnWidth", "%d", chwr[0]);
+  write_attr_format (xml, "minimumRowWidth", "%d", rhwr[0]);
   write_attr (xml, "rowDimensionLabels",
               look->row_labels_in_corner ? "inCorner" : "nested");
   end_elem (xml);
index ec72e55a79ed658e2131c3b5649b6b2879629b98..24f3b02b545d129b84b9e250e3aca42408d45884 100644 (file)
@@ -862,10 +862,10 @@ put_light_table (struct buf *buf, uint64_t table_id,
   put_bool (buf, table->rotate_outer_row_labels);
   put_bool (buf, true);
   put_u32 (buf, 0x15);
-  put_u32 (buf, table->look->width_ranges[H][0]);
-  put_u32 (buf, table->look->width_ranges[H][1]);
-  put_u32 (buf, table->look->width_ranges[V][0]);
-  put_u32 (buf, table->look->width_ranges[V][1]);
+  put_u32 (buf, table->look->col_heading_width_range[0]);
+  put_u32 (buf, table->look->col_heading_width_range[1]);
+  put_u32 (buf, table->look->row_heading_width_range[0]);
+  put_u32 (buf, table->look->row_heading_width_range[1]);
   put_u64 (buf, table_id);
 
   /* Titles. */
index 2cad8a17c1c1d944bb069b77a22c8b1ac2445809..0b3495878585d1fd1eac51f373827e433091e868 100644 (file)
@@ -76,6 +76,6 @@ AreaStyle =>
 V2Styles =>
    Separator*11[sep3]
    byte[continuation-len] byte*[continuation-len][continuation]
-   int32[min-col-width] int32[max-col-width]
-   int32[min-row-height] int32[max-row-height]
+   int32[min-col-heading-width] int32[max-col-heading-width]
+   int32[min-row-heading-width] int32[max-row-heading-width]