1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
// Copyright 2012-2013 Apcera Inc. All rights reserved.
package termtables
import (
"fmt"
"strings"
"unicode/utf8"
)
type TableAlignment int
// These constants control the alignment which should be used when rendering
// the content of a cell.
const (
AlignLeft = TableAlignment(1)
AlignCenter = TableAlignment(2)
AlignRight = TableAlignment(3)
)
// TableStyle controls styling information for a Table as a whole.
//
// For the Border rules, only X, Y and I are needed, and all have defaults.
// The others will all default to the same as BorderI.
type TableStyle struct {
SkipBorder bool
BorderX string
BorderY string
BorderI string
BorderTop string
BorderBottom string
BorderRight string
BorderLeft string
BorderTopLeft string
BorderTopRight string
BorderBottomLeft string
BorderBottomRight string
PaddingLeft int
PaddingRight int
Width int
Alignment TableAlignment
htmlRules htmlStyleRules
}
// A CellStyle controls all style applicable to one Cell.
type CellStyle struct {
// Alignment indicates the alignment to be used in rendering the content
Alignment TableAlignment
// ColSpan indicates how many columns this Cell is expected to consume.
ColSpan int
}
// DefaultStyle is a TableStyle which can be used to get some simple
// default styling for a table, using ASCII characters for drawing borders.
var DefaultStyle = &TableStyle{
SkipBorder: false,
BorderX: "-", BorderY: "|", BorderI: "+",
PaddingLeft: 1, PaddingRight: 1,
Width: 80,
Alignment: AlignLeft,
// FIXME: the use of a Width here may interact poorly with a changing
// MaxColumns value; we don't set MaxColumns here because the evaluation
// order of a var and an init value adds undesired subtlety.
}
type renderStyle struct {
cellWidths map[int]int
columns int
// used for markdown rendering
replaceContent func(string) string
TableStyle
}
// setUtfBoxStyle changes the border characters to be suitable for use when
// the output stream can render UTF-8 characters.
func (s *TableStyle) setUtfBoxStyle() {
s.BorderX = "─"
s.BorderY = "│"
s.BorderI = "┼"
s.BorderTop = "┬"
s.BorderBottom = "┴"
s.BorderLeft = "├"
s.BorderRight = "┤"
s.BorderTopLeft = "╭"
s.BorderTopRight = "╮"
s.BorderBottomLeft = "╰"
s.BorderBottomRight = "╯"
}
// setAsciiBoxStyle changes the border characters back to their defaults
func (s *TableStyle) setAsciiBoxStyle() {
s.BorderX = "-"
s.BorderY = "|"
s.BorderI = "+"
s.BorderTop, s.BorderBottom, s.BorderLeft, s.BorderRight = "", "", "", ""
s.BorderTopLeft, s.BorderTopRight, s.BorderBottomLeft, s.BorderBottomRight = "", "", "", ""
s.fillStyleRules()
}
// fillStyleRules populates members of the TableStyle box-drawing specification
// with BorderI as the default.
func (s *TableStyle) fillStyleRules() {
if s.BorderTop == "" {
s.BorderTop = s.BorderI
}
if s.BorderBottom == "" {
s.BorderBottom = s.BorderI
}
if s.BorderLeft == "" {
s.BorderLeft = s.BorderI
}
if s.BorderRight == "" {
s.BorderRight = s.BorderI
}
if s.BorderTopLeft == "" {
s.BorderTopLeft = s.BorderI
}
if s.BorderTopRight == "" {
s.BorderTopRight = s.BorderI
}
if s.BorderBottomLeft == "" {
s.BorderBottomLeft = s.BorderI
}
if s.BorderBottomRight == "" {
s.BorderBottomRight = s.BorderI
}
}
func createRenderStyle(table *Table) *renderStyle {
style := &renderStyle{TableStyle: *table.Style, cellWidths: map[int]int{}}
style.TableStyle.fillStyleRules()
if table.outputMode == outputMarkdown {
style.buildReplaceContent(table.Style.BorderY)
}
// FIXME: handle actually defined width condition
// loop over the rows and cells to calculate widths
for _, element := range table.elements {
// skip separators
if _, ok := element.(*Separator); ok {
continue
}
// iterate over cells
if row, ok := element.(*Row); ok {
for i, cell := range row.cells {
// FIXME: need to support sizing with colspan handling
if cell.colSpan > 1 {
continue
}
if style.cellWidths[i] < cell.Width() {
style.cellWidths[i] = cell.Width()
}
}
}
}
style.columns = len(style.cellWidths)
// calculate actual width
width := utf8.RuneCountInString(style.BorderLeft) // start at '1' for left border
internalBorderWidth := utf8.RuneCountInString(style.BorderI)
lastIndex := 0
for i, v := range style.cellWidths {
width += v + style.PaddingLeft + style.PaddingRight + internalBorderWidth
if i > lastIndex {
lastIndex = i
}
}
if internalBorderWidth != utf8.RuneCountInString(style.BorderRight) {
width += utf8.RuneCountInString(style.BorderRight) - internalBorderWidth
}
if table.titleCell != nil {
titleMinWidth := 0 +
table.titleCell.Width() +
utf8.RuneCountInString(style.BorderLeft) +
utf8.RuneCountInString(style.BorderRight) +
style.PaddingLeft +
style.PaddingRight
if width < titleMinWidth {
// minWidth must be set to include padding of the title, as required
style.cellWidths[lastIndex] += (titleMinWidth - width)
width = titleMinWidth
}
}
// right border is covered in loop
style.Width = width
return style
}
// CellWidth returns the width of the cell at the supplied index, where the
// width is the number of tty character-cells required to draw the glyphs.
func (s *renderStyle) CellWidth(i int) int {
return s.cellWidths[i]
}
// buildReplaceContent creates a function closure, with minimal bound lexical
// state, which replaces content
func (s *renderStyle) buildReplaceContent(bad string) {
replacement := fmt.Sprintf("&#x%02x;", bad)
s.replaceContent = func(old string) string {
return strings.Replace(old, bad, replacement, -1)
}
}
|