oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "html/template"
5 "strconv"
6 "strings"
7
8 "amadan.net/oficina/sheets/internal/sheets/store"
9)
10
11// cellPaint is a cell's formatting, rendered for the table view.
12//
13// The grid gets this from paint.js, which sets data- attributes on the
14// td and writes the OKLCH components into custom properties. The table
15// view has no script, so the server does the same job — and the result
16// has to be the SAME attributes and the SAME properties, because
17// sheets.css already styles them and a second vocabulary here would be
18// a second implementation of "what bold looks like".
19//
20// PRODUCT.md's second principle is the reason this exists at all: the
21// scriptless path is the product, not the fallback, and a screen that
22// can set bold but cannot show it is the worse half of the feature.
23type cellPaint struct {
24 Bold bool
25 Italic bool
26 Align string // "", "left", "center", "right"
27 // Fill and Ink say which of the two colour mechanisms applies.
28 // Fill is a colour THIS app applied, stored as OKLCH intent and
29 // resolved per theme; FillHex is a colour an imported FILE
30 // authored, painted exactly as written (spec §11.1). They are
31 // mutually exclusive in practice and are kept as separate flags
32 // rather than one enum, because the CSS distinguishes them too.
33 Fill bool
34 Ink bool
35 FillHex bool
36 InkHex bool
37 // LowContrast mirrors the grid's data-contrast="low", so the cells
38 // the contrast notice names are the ones marked on both screens.
39 LowContrast bool
40 // Vars is the inline custom-property declaration. template.CSS
41 // because it is interpolated into a style attribute — and it is
42 // only ever built by paintOf below, from numbers formatted by
43 // strconv and hex strings already validated on the way in.
44 Vars template.CSS
45}
46
47// Any reports whether there is anything to paint. The template uses it
48// to skip the whole attribute block for the overwhelmingly common
49// unformatted cell.
50func (p cellPaint) Any() bool {
51 return p.Bold || p.Italic || p.Align != "" || p.Fill || p.Ink ||
52 p.FillHex || p.InkHex
53}
54
55// paintOf converts a stored Format into the table view's paint.
56//
57// It deliberately reads store.Format directly rather than going through
58// styleOf/wireStyle. wireStyle is the JSON the GRID receives, with short
59// keys and omitempty, shaped for a stream that pays per keystroke; the
60// table view is a rendered page and needs Go values. Routing one through
61// the other would make a change to the wire format silently a change to
62// this page.
63func paintOf(f *store.Format) cellPaint {
64 if f == nil {
65 return cellPaint{}
66 }
67 p := cellPaint{
68 Bold: f.Bold, Italic: f.Italic, Align: f.Align,
69 LowContrast: lowContrast(f),
70 }
71 var vars strings.Builder
72 if f.FillIntentH != nil && f.FillIntentC != nil {
73 p.Fill = true
74 vars.WriteString("--sheet-h:" + num(*f.FillIntentH) + ";")
75 vars.WriteString("--sheet-c:" + num(*f.FillIntentC) + ";")
76 }
77 if f.InkIntentH != nil && f.InkIntentC != nil {
78 p.Ink = true
79 vars.WriteString("--sheet-kh:" + num(*f.InkIntentH) + ";")
80 vars.WriteString("--sheet-kc:" + num(*f.InkIntentC) + ";")
81 }
82 // An imported literal colour, through previewsvg.go's safeHex —
83 // the same check the thumbnail already applies to the same field,
84 // and reused rather than rewritten so the two screens cannot come
85 // to different conclusions about what a colour is.
86 //
87 // It is not belt and braces here. This string came out of somebody
88 // else's .xlsx and is about to be interpolated into a style
89 // attribute, and html/template cannot look inside a template.CSS
90 // value: declaring that type is a promise the caller makes, so the
91 // caller has to be the one that keeps it.
92 if hex := safeHex(f.FillHex); hex != "" {
93 p.FillHex = true
94 vars.WriteString("--sheet-fx:" + hex + ";")
95 }
96 if hex := safeHex(f.InkHex); hex != "" {
97 p.InkHex = true
98 vars.WriteString("--sheet-k:" + hex + ";")
99 }
100 p.Vars = template.CSS(vars.String())
101 return p
102}
103
104// num renders an OKLCH component. 'g' with -1 precision, matching how
105// the grid's JSON carries these, so a fill looks identical on the two
106// screens rather than differing in the last decimal place.
107func num(f float64) string { return strconv.FormatFloat(f, 'g', -1, 64) }
108