diff --git a/table/README.md b/table/README.md index e7d3e1b..3375bd3 100644 --- a/table/README.md +++ b/table/README.md @@ -13,6 +13,7 @@ Pretty-print tables into ASCII/Unicode strings. - Limit the length of - Rows (`SetAllowedRowLength`) - Columns (`ColumnConfig.Width*`) + - Auto-size Rows (`Style().Size.WidthMin` and `Style().Size.WidthMax`) - Page results by a specified number of Lines (`SetPageSize`) - Alignment - Horizontal & Vertical - Auto (horizontal) Align (numeric columns aligned Right) diff --git a/table/render.go b/table/render.go index bb883e9..6825e15 100644 --- a/table/render.go +++ b/table/render.go @@ -185,7 +185,7 @@ func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) { // use a brand-new strings.Builder if a row length limit has been set var outLine *strings.Builder - if t.allowedRowLength > 0 { + if t.getRowWidthMax() > 0 { outLine = &strings.Builder{} } else { outLine = out @@ -227,8 +227,8 @@ func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) { func (t *Table) renderLineMergeOutputs(out *strings.Builder, outLine *strings.Builder) { outLineStr := outLine.String() - if text.RuneWidthWithoutEscSequences(outLineStr) > t.allowedRowLength { - trimLength := t.allowedRowLength - utf8.RuneCountInString(t.style.Box.UnfinishedRow) + if text.RuneWidthWithoutEscSequences(outLineStr) > t.getRowWidthMax() { + trimLength := t.getRowWidthMax() - utf8.RuneCountInString(t.style.Box.UnfinishedRow) if trimLength > 0 { out.WriteString(text.Trim(outLineStr, trimLength)) out.WriteString(t.style.Box.UnfinishedRow) @@ -385,8 +385,8 @@ func (t *Table) renderTitle(out *strings.Builder) { colors := t.style.Title.Colors colorsBorder := t.getBorderColors(renderHint{isTitleRow: true}) rowLength := t.maxRowLength - if t.allowedRowLength != 0 && t.allowedRowLength < rowLength { - rowLength = t.allowedRowLength + if t.getRowWidthMax() != 0 && t.getRowWidthMax() < rowLength { + rowLength = t.getRowWidthMax() } if t.style.Options.DrawBorder { lenBorder := rowLength - text.RuneWidthWithoutEscSequences(t.style.Box.TopLeft+t.style.Box.TopRight) diff --git a/table/render_init.go b/table/render_init.go index e9b574a..7a8436e 100644 --- a/table/render_init.go +++ b/table/render_init.go @@ -105,6 +105,8 @@ func (t *Table) initForRender() { // find the longest continuous line in each column t.initForRenderColumnLengths() + t.initForRenderMaxRowLength() + t.initForRenderPaddedColumns() // generate a separator row and calculate maximum row length t.initForRenderRowSeparator() @@ -172,6 +174,56 @@ func (t *Table) initForRenderHideColumns() { t.columnConfigMap = columnConfigMap } +func (t *Table) initForRenderMaxRowLength() { + t.maxRowLength = 0 + if t.autoIndex { + t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft) + t.maxRowLength += len(fmt.Sprint(len(t.rows))) + t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight) + if t.style.Options.SeparateColumns { + t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) + } + } + if t.style.Options.SeparateColumns { + t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) * (t.numColumns - 1) + } + for _, maxColumnLength := range t.maxColumnLengths { + maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight) + t.maxRowLength += maxColumnLength + } + if t.style.Options.DrawBorder { + t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.Left + t.style.Box.Right) + } + +} + +func (t *Table) initForRenderPaddedColumns() { + minWidth := t.getRowWidthMin() + if minWidth == 0 || t.maxRowLength >= minWidth { + return + } + + paddingSize := minWidth - t.maxRowLength + for paddingSize > 0 { + // distribute padding equally among all columns + numColumnsPadded := 0 + for colIdx := 0; paddingSize > 0 && colIdx < t.numColumns; colIdx++ { + colWidthMax := t.getColumnWidthMax(colIdx) + if colWidthMax == 0 || t.maxColumnLengths[colIdx] < colWidthMax { + t.maxColumnLengths[colIdx]++ + numColumnsPadded++ + paddingSize-- + } + } + + // avoid endless looping because all columns are at max size and cannot + // be expanded any further + if numColumnsPadded == 0 { + break + } + } +} + func (t *Table) initForRenderRows() { // auto-index: calc the index column's max length t.autoIndexVIndexMaxLength = len(fmt.Sprint(len(t.rowsRaw))) @@ -206,27 +258,11 @@ func (t *Table) initForRenderRowsStringify(rows []Row, hint renderHint) []rowStr } func (t *Table) initForRenderRowSeparator() { - t.maxRowLength = 0 - if t.autoIndex { - t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft) - t.maxRowLength += len(fmt.Sprint(len(t.rows))) - t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingRight) - if t.style.Options.SeparateColumns { - t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) - } - } - if t.style.Options.SeparateColumns { - t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator) * (t.numColumns - 1) - } t.rowSeparator = make(rowStr, t.numColumns) for colIdx, maxColumnLength := range t.maxColumnLengths { maxColumnLength += text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft + t.style.Box.PaddingRight) - t.maxRowLength += maxColumnLength t.rowSeparator[colIdx] = text.RepeatAndTrim(t.style.Box.MiddleHorizontal, maxColumnLength) } - if t.style.Options.DrawBorder { - t.maxRowLength += text.RuneWidthWithoutEscSequences(t.style.Box.Left + t.style.Box.Right) - } } func (t *Table) initForRenderSortRows() { diff --git a/table/render_test.go b/table/render_test.go index 5a44dac..995e73d 100644 --- a/table/render_test.go +++ b/table/render_test.go @@ -1352,3 +1352,182 @@ func TestTable_Render_SuppressTrailingSpaces(t *testing.T) { R123 Some big name here and it's pretty big 2021-04-19 13:37 Abcdefghijklmnopqrstuvwxyz R123 Small name 2021-04-19 13:37 Abcdefghijklmnopqrstuvwxyz`) } + +func TestTable_Render_AutoWidth(t *testing.T) { + tw := NewWriter() + tw.AppendHeader(testHeader) + tw.AppendRows(testRows) + tw.AppendFooter(testFooter) + tw.SetStyle(StyleLight) + compareOutput(t, tw.Render(), ` +┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├─────┼────────────┼───────────┼────────┼─────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├─────┼────────────┼───────────┼────────┼─────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└─────┴────────────┴───────────┴────────┴─────────────────────────────┘`) + + tw.Style().Size = SizeOptions{ + WidthMax: 0, + WidthMin: 100, + } + compareOutput(t, tw.Render(), ` +┌───────────┬──────────────────┬─────────────────┬──────────────┬──────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├───────────┼──────────────────┼─────────────────┼──────────────┼──────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├───────────┼──────────────────┼─────────────────┼──────────────┼──────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└───────────┴──────────────────┴─────────────────┴──────────────┴──────────────────────────────────┘`) + + tw.Style().Size = SizeOptions{ + WidthMax: 0, + WidthMin: 120, + } + compareOutput(t, tw.Render(), ` +┌───────────────┬──────────────────────┬─────────────────────┬──────────────────┬──────────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├───────────────┼──────────────────────┼─────────────────────┼──────────────────┼──────────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├───────────────┼──────────────────────┼─────────────────────┼──────────────────┼──────────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└───────────────┴──────────────────────┴─────────────────────┴──────────────────┴──────────────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 1, WidthMax: 4}, + }) + compareOutput(t, tw.Render(), ` +┌──────┬────────────────────────┬───────────────────────┬────────────────────┬─────────────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├──────┼────────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├──────┼────────────────────────┼───────────────────────┼────────────────────┼─────────────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└──────┴────────────────────────┴───────────────────────┴────────────────────┴─────────────────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 1, WidthMax: 4}, + {Number: 2, WidthMax: 10}, + }) + compareOutput(t, tw.Render(), ` +┌──────┬────────────┬───────────────────────────┬────────────────────────┬─────────────────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├──────┼────────────┼───────────────────────────┼────────────────────────┼─────────────────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├──────┼────────────┼───────────────────────────┼────────────────────────┼─────────────────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└──────┴────────────┴───────────────────────────┴────────────────────────┴─────────────────────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 1, WidthMax: 4}, + {Number: 2, WidthMax: 10}, + {Number: 3, WidthMax: 10}, + }) + compareOutput(t, tw.Render(), ` +┌──────┬────────────┬────────────┬────────────────────────────────┬────────────────────────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├──────┼────────────┼────────────┼────────────────────────────────┼────────────────────────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├──────┼────────────┼────────────┼────────────────────────────────┼────────────────────────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└──────┴────────────┴────────────┴────────────────────────────────┴────────────────────────────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 1, WidthMax: 4}, + {Number: 2, WidthMax: 10}, + {Number: 3, WidthMax: 10}, + {Number: 4, WidthMax: 6}, + }) + compareOutput(t, tw.Render(), ` +┌──────┬────────────┬────────────┬────────┬────────────────────────────────────────────────────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├──────┼────────────┼────────────┼────────┼────────────────────────────────────────────────────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├──────┼────────────┼────────────┼────────┼────────────────────────────────────────────────────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└──────┴────────────┴────────────┴────────┴────────────────────────────────────────────────────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 1, WidthMax: 4}, + {Number: 2, WidthMax: 10}, + {Number: 3, WidthMax: 10}, + {Number: 4, WidthMax: 6}, + {Number: 5, WidthMax: 27}, + }) + compareOutput(t, tw.Render(), ` +┌──────┬────────────┬────────────┬────────┬─────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├──────┼────────────┼────────────┼────────┼─────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├──────┼────────────┼────────────┼────────┼─────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└──────┴────────────┴────────────┴────────┴─────────────────────────────┘`) + + tw.SetColumnConfigs([]ColumnConfig{ + {Number: 2, WidthMax: 10}, + {Number: 3, WidthMax: 10}, + {Number: 4, WidthMax: 6}, + {Number: 5, WidthMax: 27}, + }) + compareOutput(t, tw.Render(), ` +┌─────────────────────────────────────────────────────┬────────────┬────────────┬────────┬─────────────────────────────┐ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ │ +├─────────────────────────────────────────────────────┼────────────┼────────────┼────────┼─────────────────────────────┤ +│ 1 │ Arya │ Stark │ 3000 │ │ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │ +│ 300 │ Tyrion │ Lannister │ 5000 │ │ +├─────────────────────────────────────────────────────┼────────────┼────────────┼────────┼─────────────────────────────┤ +│ │ │ TOTAL │ 10000 │ │ +└─────────────────────────────────────────────────────┴────────────┴────────────┴────────┴─────────────────────────────┘`) + + tw.SetColumnConfigs(nil) + tw.Style().Size = SizeOptions{ + WidthMax: 60, + WidthMin: 0, + } + compareOutput(t, tw.Render(), ` +┌─────┬────────────┬───────────┬────────┬───────────────── ≈ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ ≈ +├─────┼────────────┼───────────┼────────┼───────────────── ≈ +│ 1 │ Arya │ Stark │ 3000 │ ≈ +│ 20 │ Jon │ Snow │ 2000 │ You know nothing ≈ +│ 300 │ Tyrion │ Lannister │ 5000 │ ≈ +├─────┼────────────┼───────────┼────────┼───────────────── ≈ +│ │ │ TOTAL │ 10000 │ ≈ +└─────┴────────────┴───────────┴────────┴───────────────── ≈`) + + // expanded columns, but truncated row - not a valid usage scenario; + // no enforcement on min < max at this point + tw.SetColumnConfigs(nil) + tw.Style().Size = SizeOptions{ + WidthMax: 60, + WidthMin: 80, + } + compareOutput(t, tw.Render(), ` +┌───────┬──────────────┬─────────────┬──────────┬───────── ≈ +│ # │ FIRST NAME │ LAST NAME │ SALARY │ ≈ +├───────┼──────────────┼─────────────┼──────────┼───────── ≈ +│ 1 │ Arya │ Stark │ 3000 │ ≈ +│ 20 │ Jon │ Snow │ 2000 │ You know ≈ +│ 300 │ Tyrion │ Lannister │ 5000 │ ≈ +├───────┼──────────────┼─────────────┼──────────┼───────── ≈ +│ │ │ TOTAL │ 10000 │ ≈ +└───────┴──────────────┴─────────────┴──────────┴───────── ≈`) +} diff --git a/table/style.go b/table/style.go index cb850e8..6044db8 100644 --- a/table/style.go +++ b/table/style.go @@ -13,6 +13,7 @@ type Style struct { Format FormatOptions // formatting options for the rows and columns HTML HTMLOptions // rendering options for HTML mode Options Options // misc. options for the table + Size SizeOptions // size (width) options for the table Title TitleOptions // formation options for the title text } @@ -34,6 +35,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } @@ -54,6 +56,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } @@ -67,6 +70,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsDark, } @@ -80,6 +84,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBright, } @@ -93,6 +98,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlueOnBlack, } @@ -106,6 +112,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsCyanOnBlack, } @@ -119,6 +126,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsGreenOnBlack, } @@ -132,6 +140,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsMagentaOnBlack, } @@ -145,6 +154,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsYellowOnBlack, } @@ -158,6 +168,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsRedOnBlack, } @@ -171,6 +182,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnBlue, } @@ -184,6 +196,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnCyan, } @@ -197,6 +210,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnGreen, } @@ -210,6 +224,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnMagenta, } @@ -223,6 +238,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnRed, } @@ -236,6 +252,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsNoBordersAndSeparators, + Size: SizeOptionsDefault, Title: TitleOptionsBlackOnYellow, } @@ -256,6 +273,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } @@ -276,6 +294,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } @@ -296,6 +315,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } @@ -316,6 +336,7 @@ var ( Format: FormatOptionsDefault, HTML: DefaultHTMLOptions, Options: OptionsDefault, + Size: SizeOptionsDefault, Title: TitleOptionsDefault, } ) @@ -798,6 +819,22 @@ var ( } ) +// SizeOptions defines the way to control the width of the table output. +type SizeOptions struct { + // WidthMax is the maximum allotted width for the full row + WidthMax int + // WidthMin is the minimum allotted width for the full row + WidthMin int +} + +var ( + // SizeOptionsDefault defines sensible size options - basically NONE. + SizeOptionsDefault = SizeOptions{ + WidthMax: 0, + WidthMin: 0, + } +) + // TitleOptions defines the way the title text is to be rendered. type TitleOptions struct { Align text.Align diff --git a/table/table.go b/table/table.go index 8081dfb..338d6d8 100644 --- a/table/table.go +++ b/table/table.go @@ -236,6 +236,8 @@ func (t *Table) ResetRows() { // SetAllowedRowLength sets the maximum allowed length or a row (or line of // output) when rendered as a table. Rows that are longer than this limit will // be "snipped" to the length. Length has to be a positive value to take effect. +// +// Deprecated: in favor if Style().Size.WidthMax func (t *Table) SetAllowedRowLength(length int) { t.allowedRowLength = length } @@ -634,6 +636,17 @@ func (t *Table) getRowConfig(hint renderHint) RowConfig { } } +func (t *Table) getRowWidthMax() int { + if t.allowedRowLength > 0 { + return t.allowedRowLength + } + return t.Style().Size.WidthMax +} + +func (t *Table) getRowWidthMin() int { + return t.Style().Size.WidthMin +} + func (t *Table) getSeparatorColors(hint renderHint) text.Colors { if t.style.Options.DoNotColorBordersAndSeparators { return nil diff --git a/table/writer.go b/table/writer.go index f5642aa..ec7a53a 100644 --- a/table/writer.go +++ b/table/writer.go @@ -21,7 +21,6 @@ type Writer interface { ResetFooters() ResetHeaders() ResetRows() - SetAllowedRowLength(length int) SetAutoIndex(autoIndex bool) SetCaption(format string, a ...interface{}) SetColumnConfigs(configs []ColumnConfig) @@ -35,6 +34,8 @@ type Writer interface { SuppressEmptyColumns() SuppressTrailingSpaces() + // deprecated; in favor if Style().Size.WidthMax + SetAllowedRowLength(length int) // deprecated; in favor of Style().HTML.CSSClass SetHTMLCSSClass(cssClass string) // deprecated; in favor of Pager()