770 lines
21 KiB
Go
770 lines
21 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib/xdg"
|
|
"git.sr.ht/~rockorager/vaxis"
|
|
"github.com/emersion/go-message/mail"
|
|
"github.com/go-ini/ini"
|
|
)
|
|
|
|
type StyleObject int32
|
|
|
|
const (
|
|
STYLE_DEFAULT StyleObject = iota
|
|
STYLE_ERROR
|
|
STYLE_WARNING
|
|
STYLE_SUCCESS
|
|
|
|
STYLE_TITLE
|
|
STYLE_HEADER
|
|
|
|
STYLE_STATUSLINE_DEFAULT
|
|
STYLE_STATUSLINE_ERROR
|
|
STYLE_STATUSLINE_WARNING
|
|
STYLE_STATUSLINE_SUCCESS
|
|
|
|
STYLE_MSGLIST_DEFAULT
|
|
STYLE_MSGLIST_UNREAD
|
|
STYLE_MSGLIST_READ
|
|
STYLE_MSGLIST_FLAGGED
|
|
STYLE_MSGLIST_DELETED
|
|
STYLE_MSGLIST_MARKED
|
|
STYLE_MSGLIST_RESULT
|
|
STYLE_MSGLIST_ANSWERED
|
|
STYLE_MSGLIST_FORWARDED
|
|
STYLE_MSGLIST_THREAD_FOLDED
|
|
STYLE_MSGLIST_GUTTER
|
|
STYLE_MSGLIST_PILL
|
|
STYLE_MSGLIST_THREAD_CONTEXT
|
|
STYLE_MSGLIST_THREAD_ORPHAN
|
|
|
|
STYLE_DIRLIST_DEFAULT
|
|
STYLE_DIRLIST_UNREAD
|
|
STYLE_DIRLIST_RECENT
|
|
|
|
STYLE_PART_SWITCHER
|
|
STYLE_PART_FILENAME
|
|
STYLE_PART_MIMETYPE
|
|
|
|
STYLE_COMPLETION_DEFAULT
|
|
STYLE_COMPLETION_DESCRIPTION
|
|
STYLE_COMPLETION_GUTTER
|
|
STYLE_COMPLETION_PILL
|
|
|
|
STYLE_TAB
|
|
STYLE_STACK
|
|
STYLE_SPINNER
|
|
STYLE_BORDER
|
|
|
|
STYLE_SELECTOR_DEFAULT
|
|
STYLE_SELECTOR_FOCUSED
|
|
STYLE_SELECTOR_CHOOSER
|
|
)
|
|
|
|
var StyleNames = map[string]StyleObject{
|
|
"default": STYLE_DEFAULT,
|
|
"error": STYLE_ERROR,
|
|
"warning": STYLE_WARNING,
|
|
"success": STYLE_SUCCESS,
|
|
|
|
"title": STYLE_TITLE,
|
|
"header": STYLE_HEADER,
|
|
|
|
"statusline_default": STYLE_STATUSLINE_DEFAULT,
|
|
"statusline_error": STYLE_STATUSLINE_ERROR,
|
|
"statusline_warning": STYLE_STATUSLINE_WARNING,
|
|
"statusline_success": STYLE_STATUSLINE_SUCCESS,
|
|
|
|
"msglist_default": STYLE_MSGLIST_DEFAULT,
|
|
"msglist_unread": STYLE_MSGLIST_UNREAD,
|
|
"msglist_read": STYLE_MSGLIST_READ,
|
|
"msglist_flagged": STYLE_MSGLIST_FLAGGED,
|
|
"msglist_deleted": STYLE_MSGLIST_DELETED,
|
|
"msglist_marked": STYLE_MSGLIST_MARKED,
|
|
"msglist_result": STYLE_MSGLIST_RESULT,
|
|
"msglist_answered": STYLE_MSGLIST_ANSWERED,
|
|
"msglist_forwarded": STYLE_MSGLIST_FORWARDED,
|
|
"msglist_gutter": STYLE_MSGLIST_GUTTER,
|
|
"msglist_pill": STYLE_MSGLIST_PILL,
|
|
|
|
"msglist_thread_folded": STYLE_MSGLIST_THREAD_FOLDED,
|
|
"msglist_thread_context": STYLE_MSGLIST_THREAD_CONTEXT,
|
|
"msglist_thread_orphan": STYLE_MSGLIST_THREAD_ORPHAN,
|
|
|
|
"dirlist_default": STYLE_DIRLIST_DEFAULT,
|
|
"dirlist_unread": STYLE_DIRLIST_UNREAD,
|
|
"dirlist_recent": STYLE_DIRLIST_RECENT,
|
|
|
|
"part_switcher": STYLE_PART_SWITCHER,
|
|
"part_filename": STYLE_PART_FILENAME,
|
|
"part_mimetype": STYLE_PART_MIMETYPE,
|
|
|
|
"completion_default": STYLE_COMPLETION_DEFAULT,
|
|
"completion_description": STYLE_COMPLETION_DESCRIPTION,
|
|
"completion_gutter": STYLE_COMPLETION_GUTTER,
|
|
"completion_pill": STYLE_COMPLETION_PILL,
|
|
|
|
"tab": STYLE_TAB,
|
|
"stack": STYLE_STACK,
|
|
"spinner": STYLE_SPINNER,
|
|
"border": STYLE_BORDER,
|
|
|
|
"selector_default": STYLE_SELECTOR_DEFAULT,
|
|
"selector_focused": STYLE_SELECTOR_FOCUSED,
|
|
"selector_chooser": STYLE_SELECTOR_CHOOSER,
|
|
}
|
|
|
|
type StyleHeaderPattern struct {
|
|
RawPattern string
|
|
Re *regexp.Regexp
|
|
}
|
|
|
|
type Style struct {
|
|
Fg vaxis.Color
|
|
Bg vaxis.Color
|
|
Bold bool
|
|
Blink bool
|
|
Underline bool
|
|
Reverse bool
|
|
Italic bool
|
|
Dim bool
|
|
// Only for msglist, maps header -> pattern/regexp
|
|
// All regexps must match in order for the style to be applied
|
|
headerPatterns map[string]*StyleHeaderPattern
|
|
}
|
|
|
|
func (s Style) Get() vaxis.Style {
|
|
vx := vaxis.Style{
|
|
Foreground: s.Fg,
|
|
Background: s.Bg,
|
|
}
|
|
if s.Bold {
|
|
vx.Attribute |= vaxis.AttrBold
|
|
}
|
|
if s.Blink {
|
|
vx.Attribute |= vaxis.AttrBlink
|
|
}
|
|
if s.Underline {
|
|
vx.UnderlineStyle |= vaxis.UnderlineSingle
|
|
}
|
|
if s.Reverse {
|
|
vx.Attribute |= vaxis.AttrReverse
|
|
}
|
|
if s.Italic {
|
|
vx.Attribute |= vaxis.AttrItalic
|
|
}
|
|
if s.Dim {
|
|
vx.Attribute |= vaxis.AttrDim
|
|
}
|
|
return vx
|
|
}
|
|
|
|
func (s *Style) Normal() {
|
|
s.Bold = false
|
|
s.Blink = false
|
|
s.Underline = false
|
|
s.Reverse = false
|
|
s.Italic = false
|
|
s.Dim = false
|
|
}
|
|
|
|
func (s *Style) Default() *Style {
|
|
s.Fg = 0
|
|
s.Bg = 0
|
|
return s
|
|
}
|
|
|
|
func (s *Style) Reset() *Style {
|
|
s.Default()
|
|
s.Normal()
|
|
return s
|
|
}
|
|
|
|
func (s *Style) hasSameHeaderPatterns(other map[string]*StyleHeaderPattern) bool {
|
|
return maps.EqualFunc(s.headerPatterns, other, func(a, b *StyleHeaderPattern) bool {
|
|
return a.RawPattern == b.RawPattern
|
|
})
|
|
}
|
|
|
|
func boolSwitch(val string, cur_val bool) (bool, error) {
|
|
switch val {
|
|
case "true":
|
|
return true, nil
|
|
case "false":
|
|
return false, nil
|
|
case "toggle":
|
|
return !cur_val, nil
|
|
default:
|
|
return cur_val, errors.New(
|
|
"Bool Switch attribute must be true, false, or toggle")
|
|
}
|
|
}
|
|
|
|
func extractColor(val string) vaxis.Color {
|
|
// Check if the string can be interpreted as a number, indicating a
|
|
// reference to the color number. Otherwise retrieve the number based
|
|
// on the name.
|
|
if i, err := strconv.ParseUint(val, 10, 8); err == nil {
|
|
return vaxis.IndexColor(uint8(i))
|
|
}
|
|
if strings.HasPrefix(val, "#") {
|
|
val = strings.TrimPrefix(val, "#")
|
|
hex, err := strconv.ParseUint(val, 16, 32)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return vaxis.HexColor(uint32(hex))
|
|
}
|
|
return colorNames[val]
|
|
}
|
|
|
|
func (s *Style) Set(attr, val string) error {
|
|
switch attr {
|
|
case "fg":
|
|
s.Fg = extractColor(val)
|
|
case "bg":
|
|
s.Bg = extractColor(val)
|
|
case "bold":
|
|
if state, err := boolSwitch(val, s.Bold); err != nil {
|
|
return err
|
|
} else {
|
|
s.Bold = state
|
|
}
|
|
case "blink":
|
|
if state, err := boolSwitch(val, s.Blink); err != nil {
|
|
return err
|
|
} else {
|
|
s.Blink = state
|
|
}
|
|
case "underline":
|
|
if state, err := boolSwitch(val, s.Underline); err != nil {
|
|
return err
|
|
} else {
|
|
s.Underline = state
|
|
}
|
|
case "reverse":
|
|
if state, err := boolSwitch(val, s.Reverse); err != nil {
|
|
return err
|
|
} else {
|
|
s.Reverse = state
|
|
}
|
|
case "italic":
|
|
if state, err := boolSwitch(val, s.Italic); err != nil {
|
|
return err
|
|
} else {
|
|
s.Italic = state
|
|
}
|
|
case "dim":
|
|
if state, err := boolSwitch(val, s.Dim); err != nil {
|
|
return err
|
|
} else {
|
|
s.Dim = state
|
|
}
|
|
case "default":
|
|
s.Default()
|
|
case "normal":
|
|
s.Normal()
|
|
default:
|
|
return errors.New("Unknown style attribute: " + attr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s Style) composeWith(styles []*Style) Style {
|
|
newStyle := s
|
|
for _, st := range styles {
|
|
if st.Fg != s.Fg && st.Fg != 0 {
|
|
newStyle.Fg = st.Fg
|
|
}
|
|
if st.Bg != s.Bg && st.Bg != 0 {
|
|
newStyle.Bg = st.Bg
|
|
}
|
|
if st.Bold != s.Bold {
|
|
newStyle.Bold = st.Bold
|
|
}
|
|
if st.Blink != s.Blink {
|
|
newStyle.Blink = st.Blink
|
|
}
|
|
if st.Underline != s.Underline {
|
|
newStyle.Underline = st.Underline
|
|
}
|
|
if st.Reverse != s.Reverse {
|
|
newStyle.Reverse = st.Reverse
|
|
}
|
|
if st.Italic != s.Italic {
|
|
newStyle.Italic = st.Italic
|
|
}
|
|
if st.Dim != s.Dim {
|
|
newStyle.Dim = st.Dim
|
|
}
|
|
}
|
|
return newStyle
|
|
}
|
|
|
|
type StyleConf struct {
|
|
base Style
|
|
dynamic []Style
|
|
}
|
|
|
|
type StyleSet struct {
|
|
objects map[StyleObject]*StyleConf
|
|
selected map[StyleObject]*StyleConf
|
|
user map[string]*Style
|
|
path string
|
|
}
|
|
|
|
const defaultStyleset string = `
|
|
*.selected.bg = 12
|
|
*.selected.fg = 15
|
|
*.selected.bold = true
|
|
statusline_*.dim = true
|
|
*warning.dim = false
|
|
*warning.bold = true
|
|
*warning.fg = 11
|
|
*success.dim = false
|
|
*success.bold = true
|
|
*success.fg = 10
|
|
*error.dim = false
|
|
*error.bold = true
|
|
*error.fg = 9
|
|
border.fg = 12
|
|
border.bold = true
|
|
title.bg = 12
|
|
title.fg = 15
|
|
title.bold = true
|
|
header.fg = 4
|
|
header.bold = true
|
|
msglist_unread.bold = true
|
|
msglist_deleted.dim = true
|
|
msglist_marked.bg = 6
|
|
msglist_marked.fg = 15
|
|
msglist_pill.bg = 12
|
|
msglist_pill.fg = 15
|
|
part_mimetype.fg = 12
|
|
selector_chooser.bold = true
|
|
selector_focused.bold = true
|
|
selector_focused.bg = 12
|
|
selector_focused.fg = 15
|
|
completion_*.bg = 8
|
|
completion_pill.bg = 12
|
|
completion_default.fg = 15
|
|
completion_description.fg = 15
|
|
completion_description.dim = true
|
|
`
|
|
|
|
func NewStyleSet() StyleSet {
|
|
ss := StyleSet{
|
|
objects: make(map[StyleObject]*StyleConf),
|
|
selected: make(map[StyleObject]*StyleConf),
|
|
user: make(map[string]*Style),
|
|
}
|
|
for _, so := range StyleNames {
|
|
ss.objects[so] = new(StyleConf)
|
|
ss.selected[so] = new(StyleConf)
|
|
}
|
|
f, err := ini.Load([]byte(defaultStyleset))
|
|
if err == nil {
|
|
err = ss.ParseStyleSet(f)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ss
|
|
}
|
|
|
|
func (c *StyleConf) getStyle(h *mail.Header) *Style {
|
|
if h == nil {
|
|
return &c.base
|
|
}
|
|
style := &c.base
|
|
|
|
// All dynamic styles must be iterated through, as later ones might be a
|
|
// narrower match based due to multiple header patterns.
|
|
for _, s := range c.dynamic {
|
|
allMatch := true
|
|
for header, pattern := range s.headerPatterns {
|
|
val, _ := h.Text(header)
|
|
allMatch = allMatch && pattern.Re.MatchString(val)
|
|
}
|
|
|
|
if allMatch {
|
|
s := c.base.composeWith([]*Style{&s})
|
|
style = &s
|
|
}
|
|
}
|
|
return style
|
|
}
|
|
|
|
func (ss StyleSet) Get(so StyleObject, h *mail.Header) vaxis.Style {
|
|
return ss.objects[so].getStyle(h).Get()
|
|
}
|
|
|
|
func (ss StyleSet) Selected(so StyleObject, h *mail.Header) vaxis.Style {
|
|
return ss.selected[so].getStyle(h).Get()
|
|
}
|
|
|
|
func (ss StyleSet) UserStyle(name string) vaxis.Style {
|
|
if style, found := ss.user[name]; found {
|
|
return style.Get()
|
|
}
|
|
return vaxis.Style{}
|
|
}
|
|
|
|
func (ss StyleSet) Compose(
|
|
so StyleObject, sos []StyleObject, h *mail.Header,
|
|
) vaxis.Style {
|
|
base := *ss.objects[so].getStyle(h)
|
|
styles := make([]*Style, len(sos))
|
|
for i, so := range sos {
|
|
styles[i] = ss.objects[so].getStyle(h)
|
|
}
|
|
|
|
return base.composeWith(styles).Get()
|
|
}
|
|
|
|
func (ss StyleSet) ComposeSelected(
|
|
so StyleObject, sos []StyleObject, h *mail.Header,
|
|
) vaxis.Style {
|
|
base := *ss.selected[so].getStyle(h)
|
|
styles := make([]*Style, len(sos))
|
|
for i, so := range sos {
|
|
styles[i] = ss.selected[so].getStyle(h)
|
|
}
|
|
|
|
return base.composeWith(styles).Get()
|
|
}
|
|
|
|
func findStyleSet(stylesetName string, stylesetsDir []string) (string, error) {
|
|
for _, dir := range stylesetsDir {
|
|
stylesetPath := xdg.ExpandHome(dir, stylesetName)
|
|
if _, err := os.Stat(stylesetPath); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
return stylesetPath, nil
|
|
}
|
|
|
|
return "", fmt.Errorf(
|
|
"Can't find styleset %q in any of %v", stylesetName, stylesetsDir)
|
|
}
|
|
|
|
func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
|
|
defaultSection, err := file.GetSection(ini.DefaultSection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// parse non-selected items first
|
|
for _, key := range defaultSection.Keys() {
|
|
err = ss.parseKey(key, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// override with selected items afterwards
|
|
for _, key := range defaultSection.Keys() {
|
|
err = ss.parseKey(key, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
user, err := file.GetSection("user")
|
|
if err != nil {
|
|
// This errors if the section doesn't exist, which is ok
|
|
return nil
|
|
}
|
|
for _, key := range user.KeyStrings() {
|
|
tokens := strings.Split(key, ".")
|
|
var styleName, attr string
|
|
switch len(tokens) {
|
|
case 2:
|
|
styleName, attr = tokens[0], tokens[1]
|
|
default:
|
|
return errors.New("Style parsing error: " + key)
|
|
}
|
|
val := user.KeysHash()[key]
|
|
s, ok := ss.user[styleName]
|
|
if !ok {
|
|
// Haven't seen this name before, add it to the map
|
|
s = &Style{}
|
|
ss.user[styleName] = s
|
|
}
|
|
if err := s.Set(attr, val); err != nil {
|
|
return fmt.Errorf("[user].%s=%s: %w", key, val, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
styleObjRe = regexp.MustCompile(`^([\w\*\?]+)(\.(?:[\w-]+,.+?)+?)?(\.selected)?\.(\w+)$`)
|
|
styleHeaderPatternsRe = regexp.MustCompile(`([\w-]+),(~/(?:.+?)/|(?:.+?))\.`)
|
|
)
|
|
|
|
func (ss *StyleSet) parseKey(key *ini.Key, selected bool) error {
|
|
groups := styleObjRe.FindStringSubmatch(key.Name())
|
|
if groups == nil {
|
|
return errors.New("invalid style syntax: " + key.Name())
|
|
}
|
|
if (groups[3] == ".selected") != selected {
|
|
return nil
|
|
}
|
|
obj, attr := groups[1], groups[4]
|
|
|
|
// As there can be multiple header patterns, match them separately, one
|
|
// by one
|
|
headerMatches := styleHeaderPatternsRe.FindAllStringSubmatch(groups[2]+".", -1)
|
|
headerPatterns := make(map[string]*StyleHeaderPattern)
|
|
for _, match := range headerMatches {
|
|
headerPatterns[match[1]] = &StyleHeaderPattern{
|
|
RawPattern: match[2],
|
|
}
|
|
}
|
|
|
|
objRe, err := fnmatchToRegex(obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
num := 0
|
|
for sn, so := range StyleNames {
|
|
if !objRe.MatchString(sn) {
|
|
continue
|
|
}
|
|
if !selected {
|
|
err = ss.objects[so].update(headerPatterns, attr, key.Value())
|
|
if err != nil {
|
|
return fmt.Errorf("%s=%s: %w", key.Name(), key.Value(), err)
|
|
}
|
|
}
|
|
err = ss.selected[so].update(headerPatterns, attr, key.Value())
|
|
if err != nil {
|
|
return fmt.Errorf("%s=%s: %w", key.Name(), key.Value(), err)
|
|
}
|
|
num++
|
|
}
|
|
if num == 0 {
|
|
return errors.New("unknown style object: " + obj)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *StyleConf) update(headerPatterns map[string]*StyleHeaderPattern, attr, val string) error {
|
|
if len(headerPatterns) == 0 {
|
|
return (&c.base).Set(attr, val)
|
|
}
|
|
|
|
// Check existing entries and overwrite ones with same header/pattern
|
|
for i := range c.dynamic {
|
|
s := &c.dynamic[i]
|
|
if s.hasSameHeaderPatterns(headerPatterns) {
|
|
return s.Set(attr, val)
|
|
}
|
|
}
|
|
|
|
s := Style{}
|
|
err := (&s).Set(attr, val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, p := range headerPatterns {
|
|
var pattern string
|
|
switch {
|
|
case strings.HasPrefix(p.RawPattern, "~/"):
|
|
pattern = p.RawPattern[2 : len(p.RawPattern)-1]
|
|
case strings.HasPrefix(p.RawPattern, "~"):
|
|
pattern = p.RawPattern[1:]
|
|
default:
|
|
pattern = "^" + regexp.QuoteMeta(p.RawPattern) + "$"
|
|
}
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.Re = re
|
|
}
|
|
|
|
s.headerPatterns = headerPatterns
|
|
c.dynamic = append(c.dynamic, s)
|
|
return nil
|
|
}
|
|
|
|
func (ss *StyleSet) LoadStyleSet(stylesetName string, stylesetDirs []string) error {
|
|
filepath, err := findStyleSet(stylesetName, stylesetDirs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var options ini.LoadOptions
|
|
options.SpaceBeforeInlineComment = true
|
|
|
|
file, err := ini.LoadSources(options, filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ss.path = filepath
|
|
|
|
return ss.ParseStyleSet(file)
|
|
}
|
|
|
|
func fnmatchToRegex(pattern string) (*regexp.Regexp, error) {
|
|
p := regexp.QuoteMeta(pattern)
|
|
p = strings.ReplaceAll(p, `\*`, `.*`)
|
|
return regexp.Compile(strings.ReplaceAll(p, `\?`, `.`))
|
|
}
|
|
|
|
var colorNames = map[string]vaxis.Color{
|
|
"black": vaxis.IndexColor(0),
|
|
"maroon": vaxis.IndexColor(1),
|
|
"green": vaxis.IndexColor(2),
|
|
"olive": vaxis.IndexColor(3),
|
|
"navy": vaxis.IndexColor(4),
|
|
"purple": vaxis.IndexColor(5),
|
|
"teal": vaxis.IndexColor(6),
|
|
"silver": vaxis.IndexColor(7),
|
|
"gray": vaxis.IndexColor(8),
|
|
"red": vaxis.IndexColor(9),
|
|
"lime": vaxis.IndexColor(10),
|
|
"yellow": vaxis.IndexColor(11),
|
|
"blue": vaxis.IndexColor(12),
|
|
"fuchsia": vaxis.IndexColor(13),
|
|
"aqua": vaxis.IndexColor(14),
|
|
"white": vaxis.IndexColor(15),
|
|
"aliceblue": vaxis.HexColor(0xF0F8FF),
|
|
"antiquewhite": vaxis.HexColor(0xFAEBD7),
|
|
"aquamarine": vaxis.HexColor(0x7FFFD4),
|
|
"azure": vaxis.HexColor(0xF0FFFF),
|
|
"beige": vaxis.HexColor(0xF5F5DC),
|
|
"bisque": vaxis.HexColor(0xFFE4C4),
|
|
"blanchedalmond": vaxis.HexColor(0xFFEBCD),
|
|
"blueviolet": vaxis.HexColor(0x8A2BE2),
|
|
"brown": vaxis.HexColor(0xA52A2A),
|
|
"burlywood": vaxis.HexColor(0xDEB887),
|
|
"cadetblue": vaxis.HexColor(0x5F9EA0),
|
|
"chartreuse": vaxis.HexColor(0x7FFF00),
|
|
"chocolate": vaxis.HexColor(0xD2691E),
|
|
"coral": vaxis.HexColor(0xFF7F50),
|
|
"cornflowerblue": vaxis.HexColor(0x6495ED),
|
|
"cornsilk": vaxis.HexColor(0xFFF8DC),
|
|
"crimson": vaxis.HexColor(0xDC143C),
|
|
"darkblue": vaxis.HexColor(0x00008B),
|
|
"darkcyan": vaxis.HexColor(0x008B8B),
|
|
"darkgoldenrod": vaxis.HexColor(0xB8860B),
|
|
"darkgray": vaxis.HexColor(0xA9A9A9),
|
|
"darkgreen": vaxis.HexColor(0x006400),
|
|
"darkkhaki": vaxis.HexColor(0xBDB76B),
|
|
"darkmagenta": vaxis.HexColor(0x8B008B),
|
|
"darkolivegreen": vaxis.HexColor(0x556B2F),
|
|
"darkorange": vaxis.HexColor(0xFF8C00),
|
|
"darkorchid": vaxis.HexColor(0x9932CC),
|
|
"darkred": vaxis.HexColor(0x8B0000),
|
|
"darksalmon": vaxis.HexColor(0xE9967A),
|
|
"darkseagreen": vaxis.HexColor(0x8FBC8F),
|
|
"darkslateblue": vaxis.HexColor(0x483D8B),
|
|
"darkslategray": vaxis.HexColor(0x2F4F4F),
|
|
"darkturquoise": vaxis.HexColor(0x00CED1),
|
|
"darkviolet": vaxis.HexColor(0x9400D3),
|
|
"deeppink": vaxis.HexColor(0xFF1493),
|
|
"deepskyblue": vaxis.HexColor(0x00BFFF),
|
|
"dimgray": vaxis.HexColor(0x696969),
|
|
"dodgerblue": vaxis.HexColor(0x1E90FF),
|
|
"firebrick": vaxis.HexColor(0xB22222),
|
|
"floralwhite": vaxis.HexColor(0xFFFAF0),
|
|
"forestgreen": vaxis.HexColor(0x228B22),
|
|
"gainsboro": vaxis.HexColor(0xDCDCDC),
|
|
"ghostwhite": vaxis.HexColor(0xF8F8FF),
|
|
"gold": vaxis.HexColor(0xFFD700),
|
|
"goldenrod": vaxis.HexColor(0xDAA520),
|
|
"greenyellow": vaxis.HexColor(0xADFF2F),
|
|
"honeydew": vaxis.HexColor(0xF0FFF0),
|
|
"hotpink": vaxis.HexColor(0xFF69B4),
|
|
"indianred": vaxis.HexColor(0xCD5C5C),
|
|
"indigo": vaxis.HexColor(0x4B0082),
|
|
"ivory": vaxis.HexColor(0xFFFFF0),
|
|
"khaki": vaxis.HexColor(0xF0E68C),
|
|
"lavender": vaxis.HexColor(0xE6E6FA),
|
|
"lavenderblush": vaxis.HexColor(0xFFF0F5),
|
|
"lawngreen": vaxis.HexColor(0x7CFC00),
|
|
"lemonchiffon": vaxis.HexColor(0xFFFACD),
|
|
"lightblue": vaxis.HexColor(0xADD8E6),
|
|
"lightcoral": vaxis.HexColor(0xF08080),
|
|
"lightcyan": vaxis.HexColor(0xE0FFFF),
|
|
"lightgoldenrodyellow": vaxis.HexColor(0xFAFAD2),
|
|
"lightgray": vaxis.HexColor(0xD3D3D3),
|
|
"lightgreen": vaxis.HexColor(0x90EE90),
|
|
"lightpink": vaxis.HexColor(0xFFB6C1),
|
|
"lightsalmon": vaxis.HexColor(0xFFA07A),
|
|
"lightseagreen": vaxis.HexColor(0x20B2AA),
|
|
"lightskyblue": vaxis.HexColor(0x87CEFA),
|
|
"lightslategray": vaxis.HexColor(0x778899),
|
|
"lightsteelblue": vaxis.HexColor(0xB0C4DE),
|
|
"lightyellow": vaxis.HexColor(0xFFFFE0),
|
|
"limegreen": vaxis.HexColor(0x32CD32),
|
|
"linen": vaxis.HexColor(0xFAF0E6),
|
|
"mediumaquamarine": vaxis.HexColor(0x66CDAA),
|
|
"mediumblue": vaxis.HexColor(0x0000CD),
|
|
"mediumorchid": vaxis.HexColor(0xBA55D3),
|
|
"mediumpurple": vaxis.HexColor(0x9370DB),
|
|
"mediumseagreen": vaxis.HexColor(0x3CB371),
|
|
"mediumslateblue": vaxis.HexColor(0x7B68EE),
|
|
"mediumspringgreen": vaxis.HexColor(0x00FA9A),
|
|
"mediumturquoise": vaxis.HexColor(0x48D1CC),
|
|
"mediumvioletred": vaxis.HexColor(0xC71585),
|
|
"midnightblue": vaxis.HexColor(0x191970),
|
|
"mintcream": vaxis.HexColor(0xF5FFFA),
|
|
"mistyrose": vaxis.HexColor(0xFFE4E1),
|
|
"moccasin": vaxis.HexColor(0xFFE4B5),
|
|
"navajowhite": vaxis.HexColor(0xFFDEAD),
|
|
"oldlace": vaxis.HexColor(0xFDF5E6),
|
|
"olivedrab": vaxis.HexColor(0x6B8E23),
|
|
"orange": vaxis.HexColor(0xFFA500),
|
|
"orangered": vaxis.HexColor(0xFF4500),
|
|
"orchid": vaxis.HexColor(0xDA70D6),
|
|
"palegoldenrod": vaxis.HexColor(0xEEE8AA),
|
|
"palegreen": vaxis.HexColor(0x98FB98),
|
|
"paleturquoise": vaxis.HexColor(0xAFEEEE),
|
|
"palevioletred": vaxis.HexColor(0xDB7093),
|
|
"papayawhip": vaxis.HexColor(0xFFEFD5),
|
|
"peachpuff": vaxis.HexColor(0xFFDAB9),
|
|
"peru": vaxis.HexColor(0xCD853F),
|
|
"pink": vaxis.HexColor(0xFFC0CB),
|
|
"plum": vaxis.HexColor(0xDDA0DD),
|
|
"powderblue": vaxis.HexColor(0xB0E0E6),
|
|
"rebeccapurple": vaxis.HexColor(0x663399),
|
|
"rosybrown": vaxis.HexColor(0xBC8F8F),
|
|
"royalblue": vaxis.HexColor(0x4169E1),
|
|
"saddlebrown": vaxis.HexColor(0x8B4513),
|
|
"salmon": vaxis.HexColor(0xFA8072),
|
|
"sandybrown": vaxis.HexColor(0xF4A460),
|
|
"seagreen": vaxis.HexColor(0x2E8B57),
|
|
"seashell": vaxis.HexColor(0xFFF5EE),
|
|
"sienna": vaxis.HexColor(0xA0522D),
|
|
"skyblue": vaxis.HexColor(0x87CEEB),
|
|
"slateblue": vaxis.HexColor(0x6A5ACD),
|
|
"slategray": vaxis.HexColor(0x708090),
|
|
"snow": vaxis.HexColor(0xFFFAFA),
|
|
"springgreen": vaxis.HexColor(0x00FF7F),
|
|
"steelblue": vaxis.HexColor(0x4682B4),
|
|
"tan": vaxis.HexColor(0xD2B48C),
|
|
"thistle": vaxis.HexColor(0xD8BFD8),
|
|
"tomato": vaxis.HexColor(0xFF6347),
|
|
"turquoise": vaxis.HexColor(0x40E0D0),
|
|
"violet": vaxis.HexColor(0xEE82EE),
|
|
"wheat": vaxis.HexColor(0xF5DEB3),
|
|
"whitesmoke": vaxis.HexColor(0xF5F5F5),
|
|
"yellowgreen": vaxis.HexColor(0x9ACD32),
|
|
}
|