1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-05-01 22:17:51 +08:00
termdash/widgets/table/content_layout.go
2019-03-17 01:52:45 -04:00

157 lines
5.1 KiB
Go

// 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
}