mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-30 13:48:54 +08:00
POC code that determines column widths.
This commit is contained in:
parent
2fc4ffc9e3
commit
e36e1d7ba7
@ -33,6 +33,16 @@ func NewCells(text string, opts ...cell.Option) []*Cell {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CellsWidth returns the width the cells will use on a terminal if printed out.
|
||||||
|
// This takes into account if some of the runes are full-width runes.
|
||||||
|
func CellsWidth(cells []*Cell) int {
|
||||||
|
width := 0
|
||||||
|
for _, c := range cells {
|
||||||
|
width += runewidth.RuneWidth(c.Rune)
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
// Cell represents a single cell on the terminal.
|
// Cell represents a single cell on the terminal.
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
// Rune is the rune stored in the cell.
|
// Rune is the rune stored in the cell.
|
||||||
|
@ -625,3 +625,35 @@ func TestRemWidth(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCellsWidth(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
cells []*Cell
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "ascii characters",
|
||||||
|
cells: NewCells("hello"),
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "string from mattn/runewidth/runewidth_test",
|
||||||
|
cells: NewCells("■㈱の世界①"),
|
||||||
|
want: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "string using termdash characters",
|
||||||
|
cells: NewCells("⇄…⇧⇩"),
|
||||||
|
want: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
if got := CellsWidth(tc.cells); got != tc.want {
|
||||||
|
t.Errorf("CellsWidth => %v, want %v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ package table
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/mum4k/termdash/align"
|
"github.com/mum4k/termdash/align"
|
||||||
"github.com/mum4k/termdash/cell"
|
"github.com/mum4k/termdash/cell"
|
||||||
@ -143,6 +144,8 @@ func ContentCellOpts(cellOpts ...cell.Option) ContentOption {
|
|||||||
|
|
||||||
// ContentRowHeight sets the height of rows to the provided number of cells.
|
// ContentRowHeight sets the height of rows to the provided number of cells.
|
||||||
// The number must be a non-zero positive integer.
|
// The number must be a non-zero positive integer.
|
||||||
|
// Rows still use larger than provided height if wrapping is enabled and the
|
||||||
|
// content doesn't fit.
|
||||||
// Defaults to row height automatically adjusted to the content.
|
// Defaults to row height automatically adjusted to the content.
|
||||||
// This is a hierarchical option and can be overridden when provided at Row
|
// This is a hierarchical option and can be overridden when provided at Row
|
||||||
// level.
|
// level.
|
||||||
@ -229,8 +232,14 @@ type Content struct {
|
|||||||
// rows are the rows in the table.
|
// rows are the rows in the table.
|
||||||
rows []*Row
|
rows []*Row
|
||||||
|
|
||||||
|
// layout describes the layout of this table on a canvas.
|
||||||
|
layout *contentLayout
|
||||||
|
|
||||||
// opts are the options provided to NewContent.
|
// opts are the options provided to NewContent.
|
||||||
opts *contentOptions
|
opts *contentOptions
|
||||||
|
|
||||||
|
// mu protects the Content
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContent returns a new Content instance.
|
// NewContent returns a new Content instance.
|
||||||
@ -240,8 +249,9 @@ type Content struct {
|
|||||||
// allowing for the CellColSpan option.
|
// allowing for the CellColSpan option.
|
||||||
func NewContent(cols Columns, rows []*Row, opts ...ContentOption) (*Content, error) {
|
func NewContent(cols Columns, rows []*Row, opts ...ContentOption) (*Content, error) {
|
||||||
c := &Content{
|
c := &Content{
|
||||||
cols: cols,
|
cols: cols,
|
||||||
opts: newContentOptions(opts...),
|
layout: &contentLayout{},
|
||||||
|
opts: newContentOptions(opts...),
|
||||||
}
|
}
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
if err := c.addRow(r); err != nil {
|
if err := c.addRow(r); err != nil {
|
||||||
@ -296,6 +306,9 @@ func (c *Content) addRow(row *Row) error {
|
|||||||
|
|
||||||
// AddRowWithOpts adds a row to the content and applies the options.
|
// AddRowWithOpts adds a row to the content and applies the options.
|
||||||
func (c *Content) AddRowWithOpts(cells []*Cell, opts ...RowOption) error {
|
func (c *Content) AddRowWithOpts(cells []*Cell, opts ...RowOption) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if err := c.addRow(NewRowWithOpts(cells, opts...)); err != nil {
|
if err := c.addRow(NewRowWithOpts(cells, opts...)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mum4k/termdash/align"
|
"github.com/mum4k/termdash/align"
|
||||||
"github.com/mum4k/termdash/cell"
|
"github.com/mum4k/termdash/cell"
|
||||||
|
"github.com/mum4k/termdash/internal/canvas/buffer"
|
||||||
"github.com/mum4k/termdash/internal/wrap"
|
"github.com/mum4k/termdash/internal/wrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,6 +70,8 @@ func CellOpts(cellOpts ...cell.Option) CellOption {
|
|||||||
|
|
||||||
// CellHeight sets the height of cells to the provided number of cells.
|
// CellHeight sets the height of cells to the provided number of cells.
|
||||||
// The number must be a non-zero positive integer.
|
// The number must be a non-zero positive integer.
|
||||||
|
// Rows still use larger than provided height if wrapping is enabled and the
|
||||||
|
// content doesn't fit.
|
||||||
// Defaults to cell height automatically adjusted to the content.
|
// Defaults to cell height automatically adjusted to the content.
|
||||||
// This is a hierarchical option, it overrides the one provided at Content or
|
// This is a hierarchical option, it overrides the one provided at Content or
|
||||||
// Row level.
|
// Row level.
|
||||||
@ -156,6 +159,16 @@ func (c *Cell) String() string {
|
|||||||
return fmt.Sprintf("| %v ", b.String())
|
return fmt.Sprintf("| %v ", b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// width returns the width of all the runes in this cell when they are printed
|
||||||
|
// on the terminal.
|
||||||
|
func (c *Cell) width() int {
|
||||||
|
res := 0
|
||||||
|
for _, d := range c.data {
|
||||||
|
res += buffer.CellsWidth(d.cells)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// NewCell returns a new Cell with the provided text.
|
// NewCell returns a new Cell with the provided text.
|
||||||
// If you need to apply options at the Cell or Data level use NewCellWithOpts.
|
// If you need to apply options at the Cell or Data level use NewCellWithOpts.
|
||||||
// The text contain cannot control characters (unicode.IsControl) or space
|
// The text contain cannot control characters (unicode.IsControl) or space
|
||||||
|
156
widgets/table/content_layout.go
Normal file
156
widgets/table/content_layout.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// Copyright 2019 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
// content_layout.go stores layout calculated for a canvas size.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/mum4k/termdash/internal/wrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// columnWidth is the width of a column in cells.
|
||||||
|
// This excludes any border, padding or spacing, i.e. this is the data portion
|
||||||
|
// only.
|
||||||
|
type columnWidth int
|
||||||
|
|
||||||
|
// contentLayout determines how the content gets placed onto the canvas.
|
||||||
|
type contentLayout struct {
|
||||||
|
// lastCvsAr is the are of the last canvas the content was drawn on.
|
||||||
|
// This is image.ZR if the content hasn't been drawn yet.
|
||||||
|
lastCvsAr image.Rectangle
|
||||||
|
|
||||||
|
// columnWidths are the widths of individual columns in the table.
|
||||||
|
columnWidths []columnWidth
|
||||||
|
|
||||||
|
// Details about HV lines that are the borders.
|
||||||
|
}
|
||||||
|
|
||||||
|
// newContentLayout calculates new layout for the content when drawn on a
|
||||||
|
// canvas represented with the provided area.
|
||||||
|
func newContentLayout(content *Content, cvsAr image.Rectangle) (*contentLayout, error) {
|
||||||
|
return nil, errors.New("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// columnWidths given the content and the available canvas width returns the
|
||||||
|
// widths of individual columns.
|
||||||
|
// The argument cvsWidth is assumed to exclude space required for any border,
|
||||||
|
// padding or spacing.
|
||||||
|
func columnWidths(content *Content, cvsWidth int) []columnWidth {
|
||||||
|
// This is similar to the rod-cutting problem, except instead of maximizing
|
||||||
|
// the price, we're minimizing the number of rows that would have their
|
||||||
|
// content trimmed.
|
||||||
|
|
||||||
|
idxColumnCosts := columnCosts(content, cvsWidth)
|
||||||
|
log.Printf("idxColumnCosts: %v", idxColumnCosts)
|
||||||
|
minCost, minCuts := cutCanvas(idxColumnCosts, cvsWidth, cvsWidth, int(content.cols), 0, nil)
|
||||||
|
log.Printf("minCost: %v", minCost)
|
||||||
|
log.Printf("minCuts: %v", minCuts)
|
||||||
|
|
||||||
|
var res []columnWidth
|
||||||
|
last := 0
|
||||||
|
for _, cut := range minCuts {
|
||||||
|
res = append(res, columnWidth(cut-last))
|
||||||
|
last = cut
|
||||||
|
}
|
||||||
|
res = append(res, columnWidth(cvsWidth-last))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func cutCanvas(idxColumnCosts map[int]widthCost, cvsWidth, remWidth, columns, colIdx int, cuts []int) (int, []int) {
|
||||||
|
log.Printf("cutCanvas remWidth:%d, columns:%d, colIdx:%d, cuts:%v", remWidth, columns, colIdx, cuts)
|
||||||
|
if remWidth <= 0 {
|
||||||
|
log.Printf(" -> 0")
|
||||||
|
return 0, cuts
|
||||||
|
}
|
||||||
|
|
||||||
|
minCost := math.MaxInt32
|
||||||
|
var minCuts []int
|
||||||
|
|
||||||
|
widthCosts := idxColumnCosts[colIdx]
|
||||||
|
nextColIdx := colIdx + 1
|
||||||
|
if nextColIdx > columns-1 {
|
||||||
|
log.Printf(" -> no more cuts remWidth:%d cost:%d", remWidth, widthCosts[remWidth])
|
||||||
|
return widthCosts[remWidth], cuts
|
||||||
|
}
|
||||||
|
|
||||||
|
for colWidth := 1; colWidth < remWidth; colWidth++ {
|
||||||
|
diff := cvsWidth - remWidth
|
||||||
|
idxThisCut := diff + colWidth
|
||||||
|
costThisCut := widthCosts[colWidth]
|
||||||
|
nextCost, nextCuts := cutCanvas(
|
||||||
|
idxColumnCosts,
|
||||||
|
cvsWidth,
|
||||||
|
remWidth-colWidth,
|
||||||
|
columns,
|
||||||
|
nextColIdx,
|
||||||
|
//append(cuts, colWidth+colIdx),
|
||||||
|
append(cuts, idxThisCut),
|
||||||
|
)
|
||||||
|
|
||||||
|
if newMinCost := costThisCut + nextCost; newMinCost < minCost {
|
||||||
|
log.Printf("at cuts %v, costThisCut from widthCosts:%v, at width %d:%d, nextCost:%d, minCost:%d", cuts, widthCosts, colWidth, costThisCut, nextCost, minCost)
|
||||||
|
minCost = newMinCost
|
||||||
|
minCuts = nextCuts
|
||||||
|
log.Printf("new minCost:%d minCuts:%v", minCost, minCuts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("cutCanvas remWidth:%d, columns:%d, colIdx:%d, cuts:%v -> minCost:%d, minCuts:%d", remWidth, columns, colIdx, cuts, minCost, minCuts)
|
||||||
|
return minCost, minCuts
|
||||||
|
}
|
||||||
|
|
||||||
|
// widthCost maps column widths to the number of rows that would be trimmed on
|
||||||
|
// that width because data in the column are longer than the width.
|
||||||
|
type widthCost map[int]int
|
||||||
|
|
||||||
|
// columnCosts calculates the costs of cutting the columns to various widths.
|
||||||
|
// Returns a map of column indexes to their cut costs.
|
||||||
|
func columnCosts(content *Content, cvsWidth int) map[int]widthCost {
|
||||||
|
idxColumnCosts := map[int]widthCost{}
|
||||||
|
maxColWidth := cvsWidth - (int(content.cols) - 1)
|
||||||
|
for colIdx := 0; colIdx < int(content.cols); colIdx++ {
|
||||||
|
for colWidth := 1; colWidth <= maxColWidth; colWidth++ {
|
||||||
|
wc, ok := idxColumnCosts[colIdx]
|
||||||
|
if !ok {
|
||||||
|
wc = widthCost{}
|
||||||
|
idxColumnCosts[colIdx] = wc
|
||||||
|
}
|
||||||
|
wc[colWidth] = trimmedRows(content, colIdx, colWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idxColumnCosts
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimmedRows returns the number of rows that will have data cells with
|
||||||
|
// trimmed content in column of the specified index if the assigned width of
|
||||||
|
// the column is colWidth.
|
||||||
|
func trimmedRows(content *Content, colIdx int, colWidth int) int {
|
||||||
|
trimmed := 0
|
||||||
|
for _, row := range content.rows {
|
||||||
|
tgtCell := row.cells[colIdx]
|
||||||
|
if tgtCell.hierarchical.getWrapMode() != wrap.Never {
|
||||||
|
// Cells that have wrapping enabled are never trimmed.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tgtCell.width() > colWidth {
|
||||||
|
trimmed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
}
|
181
widgets/table/content_layout_test.go
Normal file
181
widgets/table/content_layout_test.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright 2019 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColumnWidths(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
columns Columns
|
||||||
|
rows []*Row
|
||||||
|
opts []ContentOption
|
||||||
|
cvsWidth int
|
||||||
|
want []columnWidth
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "single column, wide enough",
|
||||||
|
columns: Columns(1),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell(""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 2,
|
||||||
|
want: []columnWidth{2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "single column, not wide enough",
|
||||||
|
columns: Columns(1),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell(""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 1,
|
||||||
|
want: []columnWidth{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "two columns, canvas wide enough, no trimming",
|
||||||
|
columns: Columns(2),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("a"),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell(""),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 5,
|
||||||
|
want: []columnWidth{2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "two columns, canvas not wide enough, optimal to trim first",
|
||||||
|
columns: Columns(2),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("a"),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell(""),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 4,
|
||||||
|
want: []columnWidth{1, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "two columns, canvas not wide enough, optimal to trim second",
|
||||||
|
columns: Columns(2),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("c"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("c"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell(""),
|
||||||
|
NewCell("cde"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 4,
|
||||||
|
want: []columnWidth{2, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "three columns, canvas wide enough, no trimming",
|
||||||
|
columns: Columns(3),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("cde"),
|
||||||
|
NewCell("fg"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("a"),
|
||||||
|
NewCell("c"),
|
||||||
|
NewCell("f"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("ab"),
|
||||||
|
NewCell("cde"),
|
||||||
|
NewCell("fg"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 7,
|
||||||
|
want: []columnWidth{2, 3, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "three columns, canvas not wide enough, one very long cell",
|
||||||
|
columns: Columns(3),
|
||||||
|
rows: []*Row{
|
||||||
|
NewRow(
|
||||||
|
NewCell("00"),
|
||||||
|
NewCell("11111111"),
|
||||||
|
NewCell("22"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("00"),
|
||||||
|
NewCell("11"),
|
||||||
|
NewCell("22"),
|
||||||
|
),
|
||||||
|
NewRow(
|
||||||
|
NewCell("00"),
|
||||||
|
NewCell("111"),
|
||||||
|
NewCell("22"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cvsWidth: 6,
|
||||||
|
want: []columnWidth{2, 2, 2},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
content, err := NewContent(tc.columns, tc.rows, tc.opts...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewContent => unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := columnWidths(content, tc.cvsWidth)
|
||||||
|
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||||
|
t.Errorf("columnWidths => unexpected diff (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -81,6 +81,8 @@ func RowCellOpts(cellOpts ...cell.Option) RowOption {
|
|||||||
|
|
||||||
// RowHeight sets the height of rows to the provided number of cells.
|
// RowHeight sets the height of rows to the provided number of cells.
|
||||||
// The number must be a non-zero positive integer.
|
// The number must be a non-zero positive integer.
|
||||||
|
// Rows still use larger than provided height if wrapping is enabled and the
|
||||||
|
// content doesn't fit.
|
||||||
// Defaults to row height automatically adjusted to the content.
|
// Defaults to row height automatically adjusted to the content.
|
||||||
// This is a hierarchical option, it overrides the one provided at Content
|
// This is a hierarchical option, it overrides the one provided at Content
|
||||||
// level and can be overridden when provided at the Cell level.
|
// level and can be overridden when provided at the Cell level.
|
||||||
|
@ -61,6 +61,9 @@ func validateContent(content *Content) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, d := range c.data {
|
for _, d := range c.data {
|
||||||
|
if len(d.cells) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := wrap.ValidCells(d.cells); err != nil {
|
if err := wrap.ValidCells(d.cells); err != nil {
|
||||||
return fmt.Errorf("invalid data: %v", err)
|
return fmt.Errorf("invalid data: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user