mirror of
https://github.com/alecthomas/chroma.git
synced 2025-03-19 21:10:15 +02:00
added svg formatter
basic version without any options. colors and font-styles seem to be ok. rough support for text background in styles like `murphy` using predrawn rectangles (svg has no text background attribute). things to improve: - svg width attribute (`<svg width=""`) - linenumbers - highlighting - embedded font - tabwidth option - margins? - better position/width calculation (rectangles not correctly drawn on resize)
This commit is contained in:
parent
0ff62486a5
commit
22511fb8e4
@ -64,6 +64,8 @@ command, for Go.
|
|||||||
HTMLBaseLine int `help:"Base line number." default:"1"`
|
HTMLBaseLine int `help:"Base line number." default:"1"`
|
||||||
HTMLPreventSurroundingPre bool `help:"Prevent the surrounding pre tag."`
|
HTMLPreventSurroundingPre bool `help:"Prevent the surrounding pre tag."`
|
||||||
|
|
||||||
|
SVG bool `help:"Output SVG representation of tokens."`
|
||||||
|
|
||||||
Files []string `arg:"" optional:"" help:"Files to highlight." type:"existingfile"`
|
Files []string `arg:"" optional:"" help:"Files to highlight." type:"existingfile"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -123,6 +125,10 @@ func main() {
|
|||||||
cli.Formatter = "html"
|
cli.Formatter = "html"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cli.SVG {
|
||||||
|
cli.Formatter = "svg"
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve user-specified style, clone it, and add some overrides.
|
// Retrieve user-specified style, clone it, and add some overrides.
|
||||||
builder := styles.Get(cli.Style).Builder()
|
builder := styles.Get(cli.Style).Builder()
|
||||||
if cli.HTMLHighlightStyle != "" {
|
if cli.HTMLHighlightStyle != "" {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/chroma"
|
"github.com/alecthomas/chroma"
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/formatters/html"
|
||||||
|
"github.com/alecthomas/chroma/formatters/svg"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -20,6 +21,7 @@ var (
|
|||||||
}))
|
}))
|
||||||
// Default HTML formatter outputs self-contained HTML.
|
// Default HTML formatter outputs self-contained HTML.
|
||||||
htmlFull = Register("html", html.New(html.Standalone(), html.WithClasses())) // nolint
|
htmlFull = Register("html", html.New(html.Standalone(), html.WithClasses())) // nolint
|
||||||
|
Svg = Register("svg", svg.New())
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fallback formatter.
|
// Fallback formatter.
|
||||||
|
145
formatters/svg/svg.go
Normal file
145
formatters/svg/svg.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package svg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option sets an option of the SVG formatter.
|
||||||
|
type Option func(f *Formatter)
|
||||||
|
|
||||||
|
// New SVG formatter.
|
||||||
|
func New(options ...Option) *Formatter {
|
||||||
|
f := &Formatter{}
|
||||||
|
for _, option := range options {
|
||||||
|
option(f)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatter that generates SVG.
|
||||||
|
type Formatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if perr := recover(); perr != nil {
|
||||||
|
err = perr.(error)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
f.writeSVG(w, style, iterator.Tokens())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgEscaper = strings.NewReplacer(
|
||||||
|
`&`, "&",
|
||||||
|
`<`, "<",
|
||||||
|
`>`, ">",
|
||||||
|
`"`, """,
|
||||||
|
` `, " ",
|
||||||
|
` `, "    ",
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeString escapes special characters.
|
||||||
|
func escapeString(s string) string {
|
||||||
|
return svgEscaper.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) writeSVG(w io.Writer, style *chroma.Style, tokens []chroma.Token) { // nolint: gocyclo
|
||||||
|
svgStyles := f.styleToSVG(style)
|
||||||
|
lines := chroma.SplitTokensIntoLines(tokens)
|
||||||
|
|
||||||
|
fmt.Fprint(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
||||||
|
fmt.Fprint(w, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
|
||||||
|
fmt.Fprintf(w, "<svg height=\"%d\" xmlns=\"http://www.w3.org/2000/svg\">\n", len(lines)*22)
|
||||||
|
fmt.Fprintf(w, "<rect width=\"100%%\" height=\"100%%\" fill=\"%s\"/>\n", style.Get(chroma.Background).Background.String())
|
||||||
|
fmt.Fprintf(w, "<g font-family=\"Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace\" font-size=\"14px\" fill=\"%s\">\n", style.Get(chroma.Text).Colour.String())
|
||||||
|
|
||||||
|
f.writeTokenBackgrounds(w, lines, style)
|
||||||
|
|
||||||
|
for index, tokens := range lines {
|
||||||
|
fmt.Fprintf(w, "<text x=\"0\" y=\"%d\" xml:space=\"preserve\">", (14+7)*(index+1))
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
text := escapeString(token.String())
|
||||||
|
attr := f.styleAttr(svgStyles, token.Type)
|
||||||
|
if attr != "" {
|
||||||
|
text = fmt.Sprintf("<tspan %s>%s</tspan>", attr, text)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, text)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "</text>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, "\n</g>\n")
|
||||||
|
fmt.Fprint(w, "</svg>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no background attribute for text in SVG so simply calculate the position and text
|
||||||
|
// of tokens with a background color that differs from the default and add a rectangle for each before
|
||||||
|
// adding the token.
|
||||||
|
func (f *Formatter) writeTokenBackgrounds(w io.Writer, lines [][]chroma.Token, style *chroma.Style) {
|
||||||
|
for index, tokens := range lines {
|
||||||
|
lineLength := 0
|
||||||
|
for _, token := range tokens {
|
||||||
|
length := len(strings.Replace(token.String(), ` `, " ", -1))
|
||||||
|
tokenBackground := style.Get(token.Type).Background
|
||||||
|
if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background {
|
||||||
|
fmt.Fprintf(w, "<rect id=\"%s\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"%s\" />\n", escapeString(token.String()), lineLength*8, (14+7)*index+7, length*8, 14, style.Get(token.Type).Background.String())
|
||||||
|
}
|
||||||
|
lineLength += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType) string {
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.SubCategory()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
tt = tt.Category()
|
||||||
|
if _, ok := styles[tt]; !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styles[tt]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Formatter) styleToSVG(style *chroma.Style) map[chroma.TokenType]string {
|
||||||
|
converted := map[chroma.TokenType]string{}
|
||||||
|
bg := style.Get(chroma.Background)
|
||||||
|
// Convert the style.
|
||||||
|
for t := range chroma.StandardTypes {
|
||||||
|
entry := style.Get(t)
|
||||||
|
if t != chroma.Background {
|
||||||
|
entry = entry.Sub(bg)
|
||||||
|
}
|
||||||
|
if entry.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
converted[t] = StyleEntryToSVG(entry)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
// StyleEntryToSVG converts a chroma.StyleEntry to SVG attributes.
|
||||||
|
func StyleEntryToSVG(e chroma.StyleEntry) string {
|
||||||
|
var styles []string
|
||||||
|
|
||||||
|
if e.Colour.IsSet() {
|
||||||
|
styles = append(styles, "fill=\""+e.Colour.String()+"\"")
|
||||||
|
}
|
||||||
|
if e.Bold == chroma.Yes {
|
||||||
|
styles = append(styles, "font-weight=\"bold\"")
|
||||||
|
}
|
||||||
|
if e.Italic == chroma.Yes {
|
||||||
|
styles = append(styles, "font-style=\"italic\"")
|
||||||
|
}
|
||||||
|
if e.Underline == chroma.Yes {
|
||||||
|
styles = append(styles, "text-decoration=\"underline\"")
|
||||||
|
}
|
||||||
|
return strings.Join(styles, " ")
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user