cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / fileio / htmlcell.go
htmlcell.go 6.2K
1package fileio23import (4 "fmt"5 "html"6 "strings"78 "src.rest/cat/012/internal/sheet"9)1011// htmlCell is what a td says about its cell: classes, inline styles,12// attributes and the text shown.13type htmlCell struct {14 class []string15 style []string16 attrs string17 text string18 icon string // an icon set's glyph, before the text19}2021// writeCell writes the td of the cell at a: spanning a merge, with the22// text as the grid shows it at the cell's width.23func (l *htmlLayout) writeCell(b *strings.Builder, a sheet.Addr) {24 snap := l.snap25 var h htmlCell26 width := htmlColWidth(snap, a.Col)27 if m, ok := l.merged[a]; ok {28 width = 029 for col := m.From.Col; col <= m.To.Col; col++ {30 width += htmlColWidth(snap, col)31 }32 h.attrs += fmt.Sprintf(` colspan="%d" rowspan="%d"`, m.To.Col-m.From.Col+1, l.visibleRows(m))33 }34 c, filled := snap.Cells[a]35 if filled {36 h.content(snap, c, width)37 h.textStyle(c.Style)38 h.borders(c.Style.Borders)39 if c.Style.Wrap == sheet.WrapOverflow && h.text != "" && l.nextFilled(a) {40 h.class = append(h.class, "clip")41 }42 }43 if look, ok := snap.Looks[a]; ok {44 h.look(look)45 }46 if role := l.tableRole[a]; role != "" {47 h.class = append(h.class, role)48 }49 if note, ok := snap.Notes[a]; ok {50 h.class = append(h.class, "note")51 h.attrs += ` title="` + html.EscapeString(note) + `"`52 }53 fmt.Fprintf(b, `<td data-a="%s"`, a)54 if len(h.class) > 0 {55 b.WriteString(` class="` + strings.Join(h.class, " ") + `"`)56 }57 if len(h.style) > 0 {58 b.WriteString(` style="` + html.EscapeString(strings.Join(h.style, ";")) + `"`)59 }60 b.WriteString(h.attrs + ">")61 if h.icon != "" {62 b.WriteString(h.icon)63 }64 b.WriteString(html.EscapeString(h.text) + "</td>")65}6667// visibleRows is how many drawn rows a merge spans.68func (l *htmlLayout) visibleRows(m sheet.Rect) int {69 n := 070 for row := m.From.Row; row <= m.To.Row; row++ {71 if !l.snap.HiddenRows[row] {72 n++73 }74 }75 return max(n, 1)76}7778// nextFilled reports whether the cell right of a holds something, which79// a's text stops at rather than running on, as in Sheets.80func (l *htmlLayout) nextFilled(a sheet.Addr) bool {81 next := sheet.Addr{Col: a.Col + 1, Row: a.Row}82 c, ok := l.snap.Cells[next]83 return ok && (c.Input != "" || c.Value.Kind != sheet.Empty) || l.covered[next]84}8586// content is the cell's text as the grid shows it in width columns,87// where it goes, and what was typed when that isn't the text.88func (h *htmlCell) content(snap *Snapshot, c SnapCell, width int) {89 text, align := sheet.DisplayIn(c.Value, c.Format, width, snap.Locale)90 h.text = text91 switch c.Style.Align {92 case sheet.AlignLeft:93 align = sheet.AlignLeft94 case sheet.AlignCenter:95 align = sheet.AlignCenter96 case sheet.AlignRight:97 align = sheet.AlignRight98 }99 switch align {100 case sheet.AlignRight:101 h.class = append(h.class, "r")102 case sheet.AlignCenter:103 h.class = append(h.class, "c")104 }105 if c.Value.Kind == sheet.Error {106 h.class = append(h.class, "err")107 }108 if c.Input != "" && c.Input != text {109 h.attrs += ` data-f="` + html.EscapeString(c.Input) + `"`110 }111 switch c.Style.Wrap {112 case sheet.WrapOn:113 h.class = append(h.class, "wrap")114 case sheet.WrapClip:115 h.class = append(h.class, "clip")116 }117 switch c.Style.VAlign {118 case sheet.VAlignTop:119 h.class = append(h.class, "t")120 case sheet.VAlignMiddle:121 h.class = append(h.class, "m")122 }123}124125// textStyle adds bold, italic, underline and strikethrough.126func (h *htmlCell) textStyle(st sheet.Style) {127 for _, f := range []struct {128 on bool129 class string130 }{{st.Bold, "b"}, {st.Italic, "i"}, {st.Underline, "u"}, {st.Strikethrough, "s"}} {131 if f.on {132 h.class = append(h.class, f.class)133 }134 }135}136137// borders draws each edge's line in its color, or the border role's138// when it has none.139func (h *htmlCell) borders(b sheet.Borders) {140 if b.IsZero() {141 return142 }143 for _, e := range []struct {144 edge sheet.Edge145 side string146 }{{sheet.EdgeTop, "top"}, {sheet.EdgeBottom, "bottom"}, {sheet.EdgeLeft, "left"}, {sheet.EdgeRight, "right"}} {147 st := b.Stroke(e.edge)148 line := ""149 switch st.Line {150 case sheet.LineThin:151 line = "1px solid"152 case sheet.LineThick:153 line = "2px solid"154 case sheet.LineDouble:155 line = "3px double"156 default:157 continue158 }159 color := "var(--border)"160 if st.Color != sheet.ColorNone {161 color = ruleVar(st.Color)162 }163 h.style = append(h.style, "border-"+e.side+":"+line+" "+color)164 }165}166167func ruleVar(c sheet.Color) string { return fmt.Sprintf("var(--r%d)", int(c)) }168func inkVar(c sheet.Color) string { return fmt.Sprintf("var(--i%d)", int(c)) }169170// look draws what the sheet's rules show: a conditional format's171// colors and text style, a color scale, a data bar, an icon, a172// checkbox, a dropdown's marker and an invalid entry's underline.173func (h *htmlCell) look(l sheet.Look) {174 if l.Styled {175 h.ruleStyle(l.Style)176 }177 if l.Scaled {178 pct := int(l.Pos*100 + 0.5)179 h.style = append(h.style, fmt.Sprintf("background:color-mix(in srgb,%s %d%%,%s)", ruleVar(l.To), pct, ruleVar(l.From)))180 ink := l.From181 if l.Pos >= 0.5 {182 ink = l.To183 }184 h.style = append(h.style, "color:"+inkVar(ink))185 }186 if l.Bar {187 pct := int(l.BarLen*100 + 0.5)188 h.style = append(h.style, fmt.Sprintf("background:linear-gradient(to right,color-mix(in srgb,%s 55%%,transparent) %d%%,transparent %d%%)",189 ruleVar(l.BarColor), pct, pct))190 }191 if l.ValueHidden {192 h.text = ""193 }194 if l.Icon != "" {195 h.icon = fmt.Sprintf(`<span class="icon" style="color:%s">%s</span>`, ruleVar(l.IconColor), html.EscapeString(l.Icon))196 }197 switch {198 case l.Checkbox && l.Checked:199 h.text, h.class = "[✓]", append(h.class, "c")200 case l.Checkbox:201 h.text, h.class = "[ ]", append(h.class, "c")202 case l.Dropdown:203 h.text += " ▾"204 }205 if l.Invalid {206 h.class = append(h.class, "bad")207 }208}209210// ruleStyle is a single-color rule's fill, text color and text style.211func (h *htmlCell) ruleStyle(st sheet.RuleStyle) {212 switch {213 case st.Fill != sheet.ColorNone && (st.Text == sheet.ColorNone || st.Text == st.Fill):214 h.style = append(h.style, "background:"+ruleVar(st.Fill), "color:"+inkVar(st.Fill))215 case st.Fill != sheet.ColorNone:216 h.style = append(h.style, "background:"+ruleVar(st.Fill), "color:"+ruleVar(st.Text))217 case st.Text != sheet.ColorNone:218 h.style = append(h.style, "color:"+ruleVar(st.Text))219 }220 h.textStyle(sheet.Style{Bold: st.Bold, Italic: st.Italic, Underline: st.Underline, Strikethrough: st.Strikethrough})221}