diff --git a/d2cli/main.go b/d2cli/main.go index 6e49ad0615..6ed6b80db0 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -84,6 +84,9 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { if err != nil { return err } + codeThemeFlag := ms.Opts.String("CODE_THEME", "code-theme", "", "github", "Chroma syntax highlighting theme for code blocks.") + codeDarkThemeFlag := ms.Opts.String("CODE_DARK_THEME", "code-dark-theme", "", "catppuccin-mocha", "Chroma syntax highlighting theme for code blocks in dark mode.") + padFlag, err := ms.Opts.Int64("D2_PAD", "pad", "", d2svg.DEFAULT_PADDING, "pixels padded around the rendered diagram") if err != nil { return err @@ -337,15 +340,17 @@ func Run(ctx context.Context, ms *xmain.State) (err error) { } renderOpts := d2svg.RenderOpts{ - Pad: padFlag, - Sketch: sketchFlag, - Center: centerFlag, - ThemeID: themeFlag, - DarkThemeID: darkThemeFlag, - Scale: scale, - NoXMLTag: noXMLTagFlag, - Salt: saltFlag, - OmitVersion: omitVersionFlag, + Pad: padFlag, + Sketch: sketchFlag, + Center: centerFlag, + ThemeID: themeFlag, + DarkThemeID: darkThemeFlag, + CodeTheme: *codeThemeFlag, + CodeDarkTheme: *codeDarkThemeFlag, + Scale: scale, + NoXMLTag: noXMLTagFlag, + Salt: saltFlag, + OmitVersion: omitVersionFlag, } if *watchFlag { @@ -946,6 +951,8 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts MasterID: opts.MasterID, ThemeID: opts.ThemeID, DarkThemeID: opts.DarkThemeID, + CodeTheme: opts.CodeTheme, + CodeDarkTheme: opts.CodeDarkTheme, ThemeOverrides: opts.ThemeOverrides, DarkThemeOverrides: opts.DarkThemeOverrides, NoXMLTag: opts.NoXMLTag, @@ -1045,6 +1052,8 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt Scale: scale, ThemeID: opts.ThemeID, DarkThemeID: opts.DarkThemeID, + CodeTheme: opts.CodeTheme, + CodeDarkTheme: opts.CodeDarkTheme, ThemeOverrides: opts.ThemeOverrides, DarkThemeOverrides: opts.DarkThemeOverrides, OmitVersion: opts.OmitVersion, @@ -1153,6 +1162,8 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present Scale: scale, ThemeID: opts.ThemeID, DarkThemeID: opts.DarkThemeID, + CodeTheme: opts.CodeTheme, + CodeDarkTheme: opts.CodeDarkTheme, ThemeOverrides: opts.ThemeOverrides, DarkThemeOverrides: opts.DarkThemeOverrides, OmitVersion: opts.OmitVersion, @@ -1448,6 +1459,8 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug Scale: scale, ThemeID: opts.ThemeID, DarkThemeID: opts.DarkThemeID, + CodeTheme: opts.CodeTheme, + CodeDarkTheme: opts.CodeDarkTheme, ThemeOverrides: opts.ThemeOverrides, DarkThemeOverrides: opts.DarkThemeOverrides, OmitVersion: opts.OmitVersion, diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 5e5b5028b3..751d830855 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -4,6 +4,7 @@ package d2svg import ( "bytes" + _ "embed" "encoding/base64" "errors" @@ -11,11 +12,10 @@ import ( "hash/fnv" "html" "io" + "math" "sort" "strings" - "math" - "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/formatters" "github.com/alecthomas/chroma/v2/lexers" @@ -41,8 +41,7 @@ import ( ) const ( - DEFAULT_PADDING = 100 - + DEFAULT_PADDING = 100 appendixIconRadius = 16 // Legend constants @@ -84,6 +83,8 @@ type RenderOpts struct { DarkThemeID *int64 ThemeOverrides *d2target.ThemeOverrides DarkThemeOverrides *d2target.ThemeOverrides + CodeTheme string + CodeDarkTheme string Font string // the svg will be scaled by this factor, if unset the svg will fit to screen Scale *float64 @@ -178,7 +179,7 @@ func dimensions(diagram *d2target.Diagram, pad int) (left, top, width, height in return left, top, width, height } -func RenderLegend(buf *bytes.Buffer, diagram *d2target.Diagram, diagramHash string, theme *d2themes.Theme) error { +func RenderLegend(buf *bytes.Buffer, diagram *d2target.Diagram, diagramHash string, theme *d2themes.Theme, codeTheme string, codeDarkTheme string) error { if diagram.Legend == nil || (len(diagram.Legend.Shapes) == 0 && len(diagram.Legend.Connections) == 0) { return nil } @@ -285,7 +286,7 @@ func RenderLegend(buf *bytes.Buffer, diagram *d2target.Diagram, diagramHash stri iconX := legendX + LEGEND_PADDING iconY := currentY - shapeIcon, err := renderLegendShapeIcon(s, iconX, iconY, diagramHash, theme) + shapeIcon, err := renderLegendShapeIcon(s, iconX, iconY, diagramHash, theme, codeTheme, codeDarkTheme) if err != nil { return err } @@ -332,7 +333,7 @@ func RenderLegend(buf *bytes.Buffer, diagram *d2target.Diagram, diagramHash stri iconX := legendX + LEGEND_PADDING iconY := currentY + LEGEND_ICON_SIZE/2 - connIcon, err := renderLegendConnectionIcon(c, iconX, iconY, theme) + connIcon, err := renderLegendConnectionIcon(c, iconX, iconY, theme, codeTheme, codeDarkTheme) if err != nil { return err } @@ -364,7 +365,7 @@ func RenderLegend(buf *bytes.Buffer, diagram *d2target.Diagram, diagramHash stri return nil } -func renderLegendShapeIcon(s d2target.Shape, x, y int, diagramHash string, theme *d2themes.Theme) (string, error) { +func renderLegendShapeIcon(s d2target.Shape, x, y int, diagramHash string, theme *d2themes.Theme, codeTheme string, codeDarkTheme string) (string, error) { iconShape := s const sizeFactor = 5 iconShape.Pos.X = 0 @@ -377,7 +378,7 @@ func renderLegendShapeIcon(s d2target.Shape, x, y int, diagramHash string, theme finalBuf := &bytes.Buffer{} fmt.Fprintf(finalBuf, ``, x, y, 1.0/sizeFactor) - _, err := drawShape(buf, appendixBuf, diagramHash, iconShape, nil, theme) + _, err := drawShape(buf, appendixBuf, diagramHash, iconShape, nil, theme, codeTheme, codeDarkTheme) if err != nil { return "", err } @@ -389,7 +390,7 @@ func renderLegendShapeIcon(s d2target.Shape, x, y int, diagramHash string, theme return finalBuf.String(), nil } -func renderLegendConnectionIcon(c d2target.Connection, x, y int, theme *d2themes.Theme) (string, error) { +func renderLegendConnectionIcon(c d2target.Connection, x, y int, theme *d2themes.Theme, codeTheme string, codeDarkTheme string) (string, error) { finalBuf := &bytes.Buffer{} buf := &bytes.Buffer{} @@ -426,7 +427,7 @@ func renderLegendConnectionIcon(c d2target.Connection, x, y int, theme *d2themes fmt.Fprintf(finalBuf, ``, x, y, 1.0/sizeFactor) - _, err := drawConnection(buf, legendHash, legendConn, markers, idToShape, nil, theme) + _, err := drawConnection(buf, legendHash, legendConn, markers, idToShape, nil, theme, codeTheme, codeDarkTheme) if err != nil { return "", err } @@ -466,7 +467,6 @@ func arrowheadMarker(isTarget bool, id string, connection d2target.Connection, i polygonEl.Fill = connection.Stroke polygonEl.ClassName = "connection" polygonEl.Attributes = fmt.Sprintf(`stroke-width="%d"`, connection.StrokeWidth) - if isTarget { polygonEl.Points = fmt.Sprintf("%f,%f %f,%f %f,%f %f,%f", 0., 0., @@ -1003,7 +1003,7 @@ func makeBorderLabelMask(labelPosition label.Position, labelTL *geo.Point, label ) } -func drawConnection(writer io.Writer, diagramHash string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme) (labelMask string, _ error) { +func drawConnection(writer io.Writer, diagramHash string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme, codeTheme string, codeDarkTheme string) (labelMask string, _ error) { opacityStyle := "" if connection.Opacity != 1.0 { opacityStyle = fmt.Sprintf(" style='opacity:%f'", connection.Opacity) @@ -1210,13 +1210,13 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co lexer = lexers.Fallback } for _, isLight := range []bool{true, false} { - theme := "github" + theme := codeTheme if !isLight { - theme = "catppuccin-mocha" + theme = codeDarkTheme } style := styles.Get(theme) if style == nil { - return labelMask, errors.New(`code snippet style "github" not found`) + return labelMask, errors.New(`code snippet style not found`) } iterator, err := lexer.Tokenise(nil, connection.Label) if err != nil { @@ -1599,7 +1599,7 @@ func render3DHexagon(diagramHash string, targetShape d2target.Shape, inlineTheme return borderMask + mainShapeRendered + renderedSides + renderedBorder } -func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme) (labelMask string, err error) { +func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape d2target.Shape, jsRunner jsrunner.JSRunner, inlineTheme *d2themes.Theme, codeTheme string, codeDarkTheme string) (labelMask string, err error) { closingTag := "" if targetShape.Link != "" { @@ -2105,13 +2105,14 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape lexer = lexers.Fallback } for _, isLight := range []bool{true, false} { - theme := "github" + theme := codeTheme if !isLight { - theme = "catppuccin-mocha" + theme = codeDarkTheme } + style := styles.Get(theme) if style == nil { - return labelMask, errors.New(`code snippet style "github" not found`) + return labelMask, errors.New(`code snippet style not found`) } formatter := formatters.Get("svg") if formatter == nil { @@ -2729,12 +2730,18 @@ func appendOnTrigger(buf *bytes.Buffer, source string, triggers []string, newCon } var DEFAULT_DARK_THEME *int64 = nil // no theme selected +var DEFAULT_CODE_THEME = "github" +var DEFAULT_CODE_DARK_THEME = "catppuccin-mocha" func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { var jsRunner jsrunner.JSRunner pad := DEFAULT_PADDING themeID := d2themescatalog.NeutralDefault.ID darkThemeID := DEFAULT_DARK_THEME + + codeTheme := DEFAULT_CODE_THEME + codeDarkTheme := DEFAULT_CODE_DARK_THEME + var scale *float64 if opts != nil { if opts.Pad != nil { @@ -2752,6 +2759,14 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { } darkThemeID = opts.DarkThemeID scale = opts.Scale + + if opts.CodeTheme != "" { + codeTheme = opts.CodeTheme + } + if opts.CodeDarkTheme != "" { + codeDarkTheme = opts.CodeDarkTheme + } + } else { opts = &RenderOpts{} } @@ -2829,7 +2844,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { } for _, obj := range allObjects { if c, is := obj.(d2target.Connection); is { - labelMask, err := drawConnection(buf, isolatedDiagramHash, c, markers, idToShape, jsRunner, inlineTheme) + labelMask, err := drawConnection(buf, isolatedDiagramHash, c, markers, idToShape, jsRunner, inlineTheme, codeTheme, codeDarkTheme) if err != nil { return nil, err } @@ -2837,7 +2852,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { labelMasks = append(labelMasks, labelMask) } } else if s, is := obj.(d2target.Shape); is { - labelMask, err := drawShape(buf, appendixItemBuf, diagramHash, s, jsRunner, inlineTheme) + labelMask, err := drawShape(buf, appendixItemBuf, diagramHash, s, jsRunner, inlineTheme, codeTheme, codeDarkTheme) if err != nil { return nil, err } else if labelMask != "" { @@ -2852,7 +2867,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { if diagram.Legend != nil && (len(diagram.Legend.Shapes) > 0 || len(diagram.Legend.Connections) > 0) { legendBuf := &bytes.Buffer{} - err := RenderLegend(legendBuf, diagram, diagramHash, inlineTheme) + err := RenderLegend(legendBuf, diagram, diagramHash, inlineTheme, codeTheme, codeDarkTheme) if err != nil { return nil, err }