1 package chroma
2
3 import (
4 "encoding/xml"
5 "fmt"
6 "io"
7 "sort"
8 "strings"
9 )
10
11
12 type Trilean uint8
13
14
15 const (
16 Pass Trilean = iota
17 Yes
18 No
19 )
20
21 func (t Trilean) String() string {
22 switch t {
23 case Yes:
24 return "Yes"
25 case No:
26 return "No"
27 default:
28 return "Pass"
29 }
30 }
31
32
33 func (t Trilean) Prefix(s string) string {
34 if t == Yes {
35 return s
36 } else if t == No {
37 return "no" + s
38 }
39 return ""
40 }
41
42
43 type StyleEntry struct {
44
45 Colour Colour
46 Background Colour
47 Border Colour
48
49 Bold Trilean
50 Italic Trilean
51 Underline Trilean
52 NoInherit bool
53 }
54
55 func (s StyleEntry) MarshalText() ([]byte, error) {
56 return []byte(s.String()), nil
57 }
58
59 func (s StyleEntry) String() string {
60 out := []string{}
61 if s.Bold != Pass {
62 out = append(out, s.Bold.Prefix("bold"))
63 }
64 if s.Italic != Pass {
65 out = append(out, s.Italic.Prefix("italic"))
66 }
67 if s.Underline != Pass {
68 out = append(out, s.Underline.Prefix("underline"))
69 }
70 if s.NoInherit {
71 out = append(out, "noinherit")
72 }
73 if s.Colour.IsSet() {
74 out = append(out, s.Colour.String())
75 }
76 if s.Background.IsSet() {
77 out = append(out, "bg:"+s.Background.String())
78 }
79 if s.Border.IsSet() {
80 out = append(out, "border:"+s.Border.String())
81 }
82 return strings.Join(out, " ")
83 }
84
85
86 func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
87 out := StyleEntry{}
88 if e.Colour != s.Colour {
89 out.Colour = s.Colour
90 }
91 if e.Background != s.Background {
92 out.Background = s.Background
93 }
94 if e.Bold != s.Bold {
95 out.Bold = s.Bold
96 }
97 if e.Italic != s.Italic {
98 out.Italic = s.Italic
99 }
100 if e.Underline != s.Underline {
101 out.Underline = s.Underline
102 }
103 if e.Border != s.Border {
104 out.Border = s.Border
105 }
106 return out
107 }
108
109
110
111
112 func (s StyleEntry) Inherit(ancestors ...StyleEntry) StyleEntry {
113 out := s
114 for i := len(ancestors) - 1; i >= 0; i-- {
115 if out.NoInherit {
116 return out
117 }
118 ancestor := ancestors[i]
119 if !out.Colour.IsSet() {
120 out.Colour = ancestor.Colour
121 }
122 if !out.Background.IsSet() {
123 out.Background = ancestor.Background
124 }
125 if !out.Border.IsSet() {
126 out.Border = ancestor.Border
127 }
128 if out.Bold == Pass {
129 out.Bold = ancestor.Bold
130 }
131 if out.Italic == Pass {
132 out.Italic = ancestor.Italic
133 }
134 if out.Underline == Pass {
135 out.Underline = ancestor.Underline
136 }
137 }
138 return out
139 }
140
141 func (s StyleEntry) IsZero() bool {
142 return s.Colour == 0 && s.Background == 0 && s.Border == 0 && s.Bold == Pass && s.Italic == Pass &&
143 s.Underline == Pass && !s.NoInherit
144 }
145
146
147
148
149 type StyleBuilder struct {
150 entries map[TokenType]string
151 name string
152 parent *Style
153 }
154
155 func NewStyleBuilder(name string) *StyleBuilder {
156 return &StyleBuilder{name: name, entries: map[TokenType]string{}}
157 }
158
159 func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
160 for ttype, entry := range entries {
161 s.entries[ttype] = entry
162 }
163 return s
164 }
165
166 func (s *StyleBuilder) Get(ttype TokenType) StyleEntry {
167
168 entry, _ := ParseStyleEntry(s.entries[ttype])
169 if s.parent != nil {
170 entry = entry.Inherit(s.parent.Get(ttype))
171 }
172 return entry
173 }
174
175
176
177
178 func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder {
179 s.entries[ttype] = entry
180 return s
181 }
182
183 func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
184 s.entries[ttype] = entry.String()
185 return s
186 }
187
188
189
190
191 func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder {
192 types := make(map[TokenType]struct{})
193 for tt := range s.entries {
194 types[tt] = struct{}{}
195 }
196 if s.parent != nil {
197 for _, tt := range s.parent.Types() {
198 types[tt] = struct{}{}
199 }
200 }
201 for tt := range types {
202 s.AddEntry(tt, transform(s.Get(tt)))
203 }
204 return s
205 }
206
207 func (s *StyleBuilder) Build() (*Style, error) {
208 style := &Style{
209 Name: s.name,
210 entries: map[TokenType]StyleEntry{},
211 parent: s.parent,
212 }
213 for ttype, descriptor := range s.entries {
214 entry, err := ParseStyleEntry(descriptor)
215 if err != nil {
216 return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err)
217 }
218 style.entries[ttype] = entry
219 }
220 return style, nil
221 }
222
223
224 type StyleEntries map[TokenType]string
225
226
227 func NewXMLStyle(r io.Reader) (*Style, error) {
228 dec := xml.NewDecoder(r)
229 style := &Style{}
230 return style, dec.Decode(style)
231 }
232
233
234 func MustNewXMLStyle(r io.Reader) *Style {
235 style, err := NewXMLStyle(r)
236 if err != nil {
237 panic(err)
238 }
239 return style
240 }
241
242
243 func NewStyle(name string, entries StyleEntries) (*Style, error) {
244 return NewStyleBuilder(name).AddAll(entries).Build()
245 }
246
247
248 func MustNewStyle(name string, entries StyleEntries) *Style {
249 style, err := NewStyle(name, entries)
250 if err != nil {
251 panic(err)
252 }
253 return style
254 }
255
256
257
258
259 type Style struct {
260 Name string
261 entries map[TokenType]StyleEntry
262 parent *Style
263 }
264
265 func (s *Style) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
266 if s.parent != nil {
267 return fmt.Errorf("cannot marshal style with parent")
268 }
269 start.Name = xml.Name{Local: "style"}
270 start.Attr = []xml.Attr{{Name: xml.Name{Local: "name"}, Value: s.Name}}
271 if err := e.EncodeToken(start); err != nil {
272 return err
273 }
274 sorted := make([]TokenType, 0, len(s.entries))
275 for ttype := range s.entries {
276 sorted = append(sorted, ttype)
277 }
278 sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
279 for _, ttype := range sorted {
280 entry := s.entries[ttype]
281 el := xml.StartElement{Name: xml.Name{Local: "entry"}}
282 el.Attr = []xml.Attr{
283 {Name: xml.Name{Local: "type"}, Value: ttype.String()},
284 {Name: xml.Name{Local: "style"}, Value: entry.String()},
285 }
286 if err := e.EncodeToken(el); err != nil {
287 return err
288 }
289 if err := e.EncodeToken(xml.EndElement{Name: el.Name}); err != nil {
290 return err
291 }
292 }
293 return e.EncodeToken(xml.EndElement{Name: start.Name})
294 }
295
296 func (s *Style) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
297 for _, attr := range start.Attr {
298 if attr.Name.Local == "name" {
299 s.Name = attr.Value
300 } else {
301 return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
302 }
303 }
304 if s.Name == "" {
305 return fmt.Errorf("missing style name attribute")
306 }
307 s.entries = map[TokenType]StyleEntry{}
308 for {
309 tok, err := d.Token()
310 if err != nil {
311 return err
312 }
313 switch el := tok.(type) {
314 case xml.StartElement:
315 if el.Name.Local != "entry" {
316 return fmt.Errorf("unexpected element %s", el.Name.Local)
317 }
318 var ttype TokenType
319 var entry StyleEntry
320 for _, attr := range el.Attr {
321 switch attr.Name.Local {
322 case "type":
323 ttype, err = TokenTypeString(attr.Value)
324 if err != nil {
325 return err
326 }
327
328 case "style":
329 entry, err = ParseStyleEntry(attr.Value)
330 if err != nil {
331 return err
332 }
333
334 default:
335 return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
336 }
337 }
338 s.entries[ttype] = entry
339
340 case xml.EndElement:
341 if el.Name.Local == start.Name.Local {
342 return nil
343 }
344 }
345 }
346 }
347
348
349 func (s *Style) Types() []TokenType {
350 dedupe := map[TokenType]bool{}
351 for tt := range s.entries {
352 dedupe[tt] = true
353 }
354 if s.parent != nil {
355 for _, tt := range s.parent.Types() {
356 dedupe[tt] = true
357 }
358 }
359 out := make([]TokenType, 0, len(dedupe))
360 for tt := range dedupe {
361 out = append(out, tt)
362 }
363 return out
364 }
365
366
367
368
369 func (s *Style) Builder() *StyleBuilder {
370 return &StyleBuilder{
371 name: s.Name,
372 entries: map[TokenType]string{},
373 parent: s,
374 }
375 }
376
377
378
379
380 func (s *Style) Has(ttype TokenType) bool {
381 return !s.get(ttype).IsZero() || s.synthesisable(ttype)
382 }
383
384
385
386 func (s *Style) Get(ttype TokenType) StyleEntry {
387 return s.get(ttype).Inherit(
388 s.get(Background),
389 s.get(Text),
390 s.get(ttype.Category()),
391 s.get(ttype.SubCategory()))
392 }
393
394 func (s *Style) get(ttype TokenType) StyleEntry {
395 out := s.entries[ttype]
396 if out.IsZero() && s.parent != nil {
397 return s.parent.get(ttype)
398 }
399 if out.IsZero() && s.synthesisable(ttype) {
400 out = s.synthesise(ttype)
401 }
402 return out
403 }
404
405 func (s *Style) synthesise(ttype TokenType) StyleEntry {
406 bg := s.get(Background)
407 text := StyleEntry{Colour: bg.Colour}
408 text.Colour = text.Colour.BrightenOrDarken(0.5)
409
410 switch ttype {
411
412 case LineHighlight:
413 return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
414
415
416 case LineNumbers, LineNumbersTable:
417 return text
418
419 default:
420 return StyleEntry{}
421 }
422 }
423
424 func (s *Style) synthesisable(ttype TokenType) bool {
425 return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
426 }
427
428
429 func MustParseStyleEntry(entry string) StyleEntry {
430 out, err := ParseStyleEntry(entry)
431 if err != nil {
432 panic(err)
433 }
434 return out
435 }
436
437
438 func ParseStyleEntry(entry string) (StyleEntry, error) {
439 out := StyleEntry{}
440 parts := strings.Fields(entry)
441 for _, part := range parts {
442 switch {
443 case part == "italic":
444 out.Italic = Yes
445 case part == "noitalic":
446 out.Italic = No
447 case part == "bold":
448 out.Bold = Yes
449 case part == "nobold":
450 out.Bold = No
451 case part == "underline":
452 out.Underline = Yes
453 case part == "nounderline":
454 out.Underline = No
455 case part == "inherit":
456 out.NoInherit = false
457 case part == "noinherit":
458 out.NoInherit = true
459 case part == "bg:":
460 out.Background = 0
461 case strings.HasPrefix(part, "bg:#"):
462 out.Background = ParseColour(part[3:])
463 if !out.Background.IsSet() {
464 return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
465 }
466 case strings.HasPrefix(part, "border:#"):
467 out.Border = ParseColour(part[7:])
468 if !out.Border.IsSet() {
469 return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
470 }
471 case strings.HasPrefix(part, "#"):
472 out.Colour = ParseColour(part)
473 if !out.Colour.IsSet() {
474 return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
475 }
476 default:
477 return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
478 }
479 }
480 return out, nil
481 }
482
View as plain text