package gold import ( "fmt" "io" "io/ioutil" "os" "github.com/lucasb-eyer/go-colorful" "github.com/rakyll/statik/fs" _ "github.com/charmbracelet/gold/statik" ) type StyleType int const ( Document StyleType = iota // Block elements Heading Paragraph BlockQuote List // Text elements Text Strikethrough Emph Strong HorizontalRule // ListItem styles Item CheckedItem Enumeration // Links Link LinkText // Images Image ImageText // Code styles CodeBlock Code // Table styles Table TableCell TableHead TableBody TableRow // Definition Lists DefinitionList DefinitionTerm DefinitionDescription // HTML styles HTMLBlock HTMLSpan // Style definitions for individual heading levels H1 H2 H3 H4 H5 H6 ) type ElementStyle struct { Color *string `json:"color"` BackgroundColor *string `json:"background_color"` Underline *bool `json:"underline"` Bold *bool `json:"bold"` Italic *bool `json:"italic"` CrossedOut *bool `json:"crossed_out"` Faint *bool `json:"faint"` Conceal *bool `json:"conceal"` Overlined *bool `json:"overlined"` Inverse *bool `json:"inverse"` Blink *bool `json:"blink"` Indent *uint `json:"indent"` Margin *uint `json:"margin"` Theme string `json:"theme"` Prefix string `json:"prefix"` Suffix string `json:"suffix"` StyledPrefix string `json:"styled_prefix"` StyledSuffix string `json:"styled_suffix"` Format string `json:"format"` } func loadStyle(f string) ([]byte, error) { var r io.ReadCloser var err error r, err = os.Open(f) if err != nil { statikFS, err := fs.New() if err != nil { return nil, err } r, err = statikFS.Open("/" + f + ".json") if err != nil { return nil, err } } defer r.Close() return ioutil.ReadAll(r) } func cascadeStyles(onlyColors bool, s ...ElementStyle) ElementStyle { var r ElementStyle for _, v := range s { r = cascadeStyle(r, v, onlyColors) } return r } func cascadeStyle(parent ElementStyle, child ElementStyle, onlyColors bool) ElementStyle { s := child s.Color = parent.Color s.BackgroundColor = parent.BackgroundColor if !onlyColors { s.Indent = parent.Indent s.Margin = parent.Margin s.Underline = parent.Underline s.Bold = parent.Bold s.Italic = parent.Italic s.CrossedOut = parent.CrossedOut s.Faint = parent.Faint s.Conceal = parent.Conceal s.Overlined = parent.Overlined s.Inverse = parent.Inverse s.Blink = parent.Blink s.Prefix = parent.Prefix s.Suffix = parent.Suffix s.Format = parent.Format } if child.Color != nil { s.Color = child.Color } if child.BackgroundColor != nil { s.BackgroundColor = child.BackgroundColor } if child.Indent != nil { s.Indent = child.Indent } if child.Margin != nil { s.Margin = child.Margin } if child.Underline != nil { s.Underline = child.Underline } if child.Bold != nil { s.Bold = child.Bold } if child.Italic != nil { s.Italic = child.Italic } if child.CrossedOut != nil { s.CrossedOut = child.CrossedOut } if child.Faint != nil { s.Faint = child.Faint } if child.Conceal != nil { s.Conceal = child.Conceal } if child.Overlined != nil { s.Overlined = child.Overlined } if child.Inverse != nil { s.Inverse = child.Inverse } if child.Blink != nil { s.Blink = child.Blink } if child.Prefix != "" { s.Prefix = child.Prefix } if child.Suffix != "" { s.Suffix = child.Suffix } if child.Format != "" { s.Format = child.Format } return s } func hexToANSIColor(h string) (int, error) { c, err := colorful.Hex(h) if err != nil { return 0, err } v2ci := func(v float64) int { if v < 48 { return 0 } if v < 115 { return 1 } return int((v - 35) / 40) } // Calculate the nearest 0-based color index at 16..231 r := v2ci(c.R * 255.0) // 0..5 each g := v2ci(c.G * 255.0) b := v2ci(c.B * 255.0) ci := 36*r + 6*g + b /* 0..215 */ // Calculate the represented colors back from the index i2cv := [6]int{0, 0x5f, 0x87, 0xaf, 0xd7, 0xff} cr := i2cv[r] // r/g/b, 0..255 each cg := i2cv[g] cb := i2cv[b] // Calculate the nearest 0-based gray index at 232..255 var grayIdx int average := (r + g + b) / 3 if average > 238 { grayIdx = 23 } else { grayIdx = (average - 3) / 10 // 0..23 } gv := 8 + 10*grayIdx // same value for r/g/b, 0..255 // Return the one which is nearer to the original input rgb value c2 := colorful.Color{R: float64(cr) / 255.0, G: float64(cg) / 255.0, B: float64(cb) / 255.0} g2 := colorful.Color{R: float64(gv) / 255.0, G: float64(gv) / 255.0, B: float64(gv) / 255.0} colorDist := c.DistanceLab(c2) grayDist := c.DistanceLab(g2) if colorDist <= grayDist { return 16 + ci, nil } return 232 + grayIdx, nil } func keyToType(key string) (StyleType, error) { switch key { case "document": return Document, nil case "block_quote": return BlockQuote, nil case "list": return List, nil case "item": return Item, nil case "checked_item": return CheckedItem, nil case "enumeration": return Enumeration, nil case "paragraph": return Paragraph, nil case "heading": return Heading, nil case "h1": return H1, nil case "h2": return H2, nil case "h3": return H3, nil case "h4": return H4, nil case "h5": return H5, nil case "h6": return H6, nil case "hr": return HorizontalRule, nil case "strikethrough": return Strikethrough, nil case "emph": return Emph, nil case "strong": return Strong, nil case "link": return Link, nil case "link_text": return LinkText, nil case "image": return Image, nil case "image_text": return ImageText, nil case "text": return Text, nil case "html_block": return HTMLBlock, nil case "code_block": return CodeBlock, nil case "code": return Code, nil case "html_span": return HTMLSpan, nil case "table": return Table, nil case "table_cel": return TableCell, nil case "table_head": return TableHead, nil case "table_body": return TableBody, nil case "table_row": return TableRow, nil case "definition_list": return DefinitionList, nil case "definition_term": return DefinitionTerm, nil case "definition_description": return DefinitionDescription, nil default: return 0, fmt.Errorf("invalid style element type: %s", key) } }