oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package xlsx
2
3import (
4 "strings"
5
6 "github.com/xuri/excelize/v2"
7
8 "amadan.net/oficina/sheets/internal/sheets/store"
9)
10
11// styleFromFormat builds the excelize style a Format should render as.
12// ok is false when f describes nothing worth a style at all (the zero
13// Format), so the caller can skip SetCellStyle entirely and leave a cell
14// on the workbook's default style rather than writing a no-op one.
15//
16// NumFmt always goes through CustomNumFmt, never the builtin NumFmt id:
17// excelize's own round trip (verified directly — see the package doc)
18// only reflects a written style's NumFmt id back as CustomNumFmt when it
19// isn't one of its ~50 builtins, so relying on the id for something as
20// common as "0.00" would silently stop being lossless the day this
21// package's number-format strings happen to collide with one.
22func styleFromFormat(f *store.Format) (excelize.Style, bool) {
23 if f == nil {
24 return excelize.Style{}, false
25 }
26 var st excelize.Style
27 used := false
28
29 if f.NumberFmt != "" {
30 nf := f.NumberFmt
31 st.CustomNumFmt = &nf
32 used = true
33 }
34 // The condition includes InkHex, not just bold and italic: a cell
35 // whose only font property is a colour would otherwise get no Font
36 // element at all, and the retained ink would be dropped on export —
37 // silently, and only for the cells §11.1 is about.
38 if f.Bold || f.Italic || f.InkHex != "" {
39 st.Font = &excelize.Font{Bold: f.Bold, Italic: f.Italic}
40 if len(f.InkHex) == 8 {
41 // Same 6-char RGB convention as Fill.Color below: excelize
42 // prepends its own alpha.
43 st.Font.Color = f.InkHex[2:]
44 }
45 used = true
46 }
47 if f.Align != "" {
48 st.Alignment = &excelize.Alignment{Horizontal: f.Align}
49 used = true
50 }
51 if hex := exportFillHex(f); hex != "" {
52 // excelize's Fill.Color always takes 6-char RGB (styles.go's
53 // getPaletteColor prepends its own "FF" alpha) — see the
54 // package doc on colors.go for why this app's own FillHex is
55 // always the 8-char ARGB form.
56 st.Fill = excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{hex[2:]}}
57 used = true
58 }
59 return st, used
60}
61
62// exportFillHex resolves what hex a format's fill should render as: the
63// retained import hex when present (lossless re-export — the whole point
64// of FillHex, models.go), otherwise the intent's hue/chroma resolved at a
65// fixed lightness (colors.go), otherwise no fill at all.
66func exportFillHex(f *store.Format) string {
67 if f.FillHex != "" {
68 return f.FillHex
69 }
70 if f.FillIntentH != nil && f.FillIntentC != nil {
71 return oklchToHex(*f.FillIntentH, *f.FillIntentC)
72 }
73 return ""
74}
75
76// formatFromStyle is styleFromFormat's inverse for import: it reads back
77// what a cell's excelize style actually says and builds the Format this
78// app stores. ok is false when the style carries nothing this app
79// tracks (the workbook's default style, most commonly), so the caller
80// can skip writing a Format at all.
81func formatFromStyle(st *excelize.Style) (store.Format, bool) {
82 var f store.Format
83 used := false
84
85 if st.CustomNumFmt != nil && *st.CustomNumFmt != "" {
86 f.NumberFmt = *st.CustomNumFmt
87 used = true
88 }
89 if st.Font != nil && (st.Font.Bold || st.Font.Italic) {
90 f.Bold = st.Font.Bold
91 f.Italic = st.Font.Italic
92 used = true
93 }
94 if st.Font != nil && st.Font.Color != "" {
95 // Retained, not resolved. Spec §11.1: we render an imported
96 // file as authored, and surface a pairing that fails AA rather
97 // than silently correcting it — correcting would lie about the
98 // document, which is the same class of wrong as inventing a
99 // font colour on export.
100 f.InkHex = argbFromStyleColor(st.Font.Color)
101 used = true
102 }
103 if st.Alignment != nil && st.Alignment.Horizontal != "" {
104 f.Align = st.Alignment.Horizontal
105 used = true
106 }
107 if st.Fill.Type == "pattern" && len(st.Fill.Color) > 0 && st.Fill.Color[0] != "" {
108 hex := argbFromStyleColor(st.Fill.Color[0])
109 f.FillHex = hex
110 if hue, chroma, err := hexToOKLCH(hex); err == nil {
111 f.FillIntentH = &hue
112 f.FillIntentC = &chroma
113 }
114 used = true
115 }
116 return f, used
117}
118
119// argbFromStyleColor turns whatever excelize.Style.Fill.Color[0] hands
120// back (6-char RGB — see styleFromFormat's comment) into this package's
121// canonical 8-char ARGB form. Already-8-char input (defensive: some other
122// producer of *excelize.Style might not follow excelize's own round
123// trip) is upper-cased and returned as-is rather than double-prefixed.
124func argbFromStyleColor(color string) string {
125 c := strings.ToUpper(strings.TrimPrefix(color, "#"))
126 if len(c) == 8 {
127 return c
128 }
129 return "FF" + c
130}
131