mirror of
https://github.com/mum4k/termdash.git
synced 2025-05-01 22:17:51 +08:00
157 lines
5.1 KiB
Go
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
|
|
}
|