From c0b02c779f26fe801dbc83f1650294df03e0522a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 6 Feb 2023 08:17:28 -0800 Subject: [PATCH] output: Correctly define and properly implement column width ranges. 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. --- doc/dev/spv-file-format.texi | 41 +++++++++++--- src/language/commands/ctables.c | 2 +- src/output/cairo-fsm.c | 1 + src/output/pivot-table.c | 10 ++-- src/output/pivot-table.h | 12 ++++- src/output/render.c | 83 +++++++++++++++++++++++++---- src/output/render.h | 5 ++ src/output/spv/light-binary.grammar | 4 +- src/output/spv/spv-legacy-decoder.c | 4 +- src/output/spv/spv-light-decoder.c | 8 +-- src/output/spv/spv-table-look.c | 35 ++++++------ src/output/spv/spv-writer.c | 8 +-- src/output/spv/tlo.grammar | 4 +- 13 files changed, 158 insertions(+), 59 deletions(-) diff --git a/doc/dev/spv-file-format.texi b/doc/dev/spv-file-format.texi index 0cf15f94d9..523eb556e9 100644 --- a/doc/dev/spv-file-format.texi +++ b/doc/dev/spv-file-format.texi @@ -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, diff --git a/src/language/commands/ctables.c b/src/language/commands/ctables.c index 3f238a7f10..d25832b7e6 100644 --- a/src/language/commands/ctables.c +++ b/src/language/commands/ctables.c @@ -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]; diff --git a/src/output/cairo-fsm.c b/src/output/cairo-fsm.c index 51f48efb75..5d9b60a443 100644 --- a/src/output/cairo-fsm.c +++ b/src/output/cairo-fsm.c @@ -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, diff --git a/src/output/pivot-table.c b/src/output/pivot-table.c index 81a8b31925..661813f28b 100644 --- a/src/output/pivot-table.c +++ b/src/output/pivot-table.c @@ -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); diff --git a/src/output/pivot-table.h b/src/output/pivot-table.h index d45f3138c3..145e356cf3 100644 --- a/src/output/pivot-table.h +++ b/src/output/pivot-table.h @@ -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; diff --git a/src/output/render.c b/src/output/render.c index 76f5017fa3..c8766266f2 100644 --- a/src/output/render.c +++ b/src/output/render.c @@ -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 diff --git a/src/output/render.h b/src/output/render.h index 89a7c4fe5c..e52eb148af 100644 --- a/src/output/render.h +++ b/src/output/render.h @@ -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.) */ diff --git a/src/output/spv/light-binary.grammar b/src/output/spv/light-binary.grammar index 74d5315758..105e1dbca2 100644 --- a/src/output/spv/light-binary.grammar +++ b/src/output/spv/light-binary.grammar @@ -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 => diff --git a/src/output/spv/spv-legacy-decoder.c b/src/output/spv/spv-legacy-decoder.c index 84397a04ab..904a6815f1 100644 --- a/src/output/spv/spv-legacy-decoder.c +++ b/src/output/spv/spv-legacy-decoder.c @@ -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; } } diff --git a/src/output/spv/spv-light-decoder.c b/src/output/spv/spv-light-decoder.c index 7bc3b1fb27..5d7856df65 100644 --- a/src/output/spv/spv-light-decoder.c +++ b/src/output/spv/spv-light-decoder.c @@ -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, diff --git a/src/output/spv/spv-table-look.c b/src/output/spv/spv-table-look.c index 49266d899a..6b3c6d2345 100644 --- a/src/output/spv/spv-table-look.c +++ b/src/output/spv/spv-table-look.c @@ -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); diff --git a/src/output/spv/spv-writer.c b/src/output/spv/spv-writer.c index ec72e55a79..24f3b02b54 100644 --- a/src/output/spv/spv-writer.c +++ b/src/output/spv/spv-writer.c @@ -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. */ diff --git a/src/output/spv/tlo.grammar b/src/output/spv/tlo.grammar index 2cad8a17c1..0b34958785 100644 --- a/src/output/spv/tlo.grammar +++ b/src/output/spv/tlo.grammar @@ -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] -- 2.30.2