Skip to content

Commit

Permalink
customize theme colors in vars-config
Browse files Browse the repository at this point in the history
  • Loading branch information
alixander committed Dec 13, 2023
1 parent 4bc18d7 commit 27da0cb
Show file tree
Hide file tree
Showing 13 changed files with 3,716 additions and 24 deletions.
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#### Features 🚀

- Themes can be customized via `d2-config` vars. [#1777](https://github.com/terrastruct/d2/pull/1777)
- Icons can be added for special objects (sql_table, class, code, markdown, latex). [#1774](https://github.com/terrastruct/d2/pull/1774)

#### Improvements 🧹
Expand Down
79 changes: 79 additions & 0 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,5 +1375,84 @@ func compileConfig(ir *d2ir.Map) *d2target.Config {
config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString())
}

f = configMap.GetField("theme-overrides")
if f != nil {
config.ThemeOverrides = compileThemeOverrides(f.Map())
}
f = configMap.GetField("dark-theme-overrides")
if f != nil {
config.DarkThemeOverrides = compileThemeOverrides(f.Map())
}

return config
}

func compileThemeOverrides(m *d2ir.Map) *d2target.ThemeOverrides {
if m == nil {
return nil
}
themeOverrides := d2target.ThemeOverrides{}

if m.GetField("N1") != nil {
themeOverrides.N1 = go2.Pointer(m.GetField("N1").Primary().Value.ScalarString())
}
if m.GetField("B1") != nil {
themeOverrides.B1 = go2.Pointer(m.GetField("B1").Primary().Value.ScalarString())
}
if m.GetField("B2") != nil {
themeOverrides.B2 = go2.Pointer(m.GetField("B2").Primary().Value.ScalarString())
}
if m.GetField("B3") != nil {
themeOverrides.B3 = go2.Pointer(m.GetField("B3").Primary().Value.ScalarString())
}
if m.GetField("B4") != nil {
themeOverrides.B4 = go2.Pointer(m.GetField("B4").Primary().Value.ScalarString())
}
if m.GetField("B5") != nil {
themeOverrides.B5 = go2.Pointer(m.GetField("B5").Primary().Value.ScalarString())
}
if m.GetField("B6") != nil {
themeOverrides.B6 = go2.Pointer(m.GetField("B6").Primary().Value.ScalarString())
}
if m.GetField("AA2") != nil {
themeOverrides.AA2 = go2.Pointer(m.GetField("AA2").Primary().Value.ScalarString())
}
if m.GetField("AA4") != nil {
themeOverrides.AA4 = go2.Pointer(m.GetField("AA4").Primary().Value.ScalarString())
}
if m.GetField("AA5") != nil {
themeOverrides.AA5 = go2.Pointer(m.GetField("AA5").Primary().Value.ScalarString())
}
if m.GetField("AB4") != nil {
themeOverrides.AB4 = go2.Pointer(m.GetField("AB4").Primary().Value.ScalarString())
}
if m.GetField("AB5") != nil {
themeOverrides.AB5 = go2.Pointer(m.GetField("AB5").Primary().Value.ScalarString())
}
if m.GetField("N1") != nil {
themeOverrides.N1 = go2.Pointer(m.GetField("N1").Primary().Value.ScalarString())
}
if m.GetField("N2") != nil {
themeOverrides.N2 = go2.Pointer(m.GetField("N2").Primary().Value.ScalarString())
}
if m.GetField("N3") != nil {
themeOverrides.N3 = go2.Pointer(m.GetField("N3").Primary().Value.ScalarString())
}
if m.GetField("N4") != nil {
themeOverrides.N4 = go2.Pointer(m.GetField("N4").Primary().Value.ScalarString())
}
if m.GetField("N5") != nil {
themeOverrides.N5 = go2.Pointer(m.GetField("N5").Primary().Value.ScalarString())
}
if m.GetField("N6") != nil {
themeOverrides.N6 = go2.Pointer(m.GetField("N6").Primary().Value.ScalarString())
}
if m.GetField("N7") != nil {
themeOverrides.N7 = go2.Pointer(m.GetField("N7").Primary().Value.ScalarString())
}

if themeOverrides != (d2target.ThemeOverrides{}) {
return &themeOverrides
}
return nil
}
4 changes: 2 additions & 2 deletions d2ir/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (c *compiler) validateConfigs(configs *Field) {
for _, f := range configs.Map().Fields {
var val string
if f.Primary() == nil {
if f.Name != "theme-colors" {
if f.Name != "theme-overrides" && f.Name != "dark-theme-overrides" {
c.errorf(f.LastRef().AST(), `"%s" needs a value`, f.Name)
continue
}
Expand All @@ -184,7 +184,7 @@ func (c *compiler) validateConfigs(configs *Field) {
c.errorf(f.LastRef().AST(), `expected a boolean for "%s", got "%s"`, f.Name, val)
continue
}
case "theme-colors":
case "theme-overrides", "dark-theme-overrides":
if f.Map() == nil {
c.errorf(f.LastRef().AST(), `"%s" needs a map`, f.Name)
continue
Expand Down
2 changes: 2 additions & 0 deletions d2lib/d2.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ func applyConfigs(config *d2target.Config, compileOpts *CompileOptions, renderOp
if renderOpts.Center == nil {
renderOpts.Center = config.Center
}
renderOpts.ThemeOverrides = config.ThemeOverrides
renderOpts.DarkThemeOverrides = config.DarkThemeOverrides
}

func applyDefaults(compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
Expand Down
2 changes: 1 addition & 1 deletion d2renderers/d2animate/d2animate.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO

d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily, rootDiagram.GetNestedCorpus())

themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID)
themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID, renderOpts.ThemeOverrides, renderOpts.DarkThemeOverrides)
if err != nil {
return nil, err
}
Expand Down
25 changes: 14 additions & 11 deletions d2renderers/d2svg/d2svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ var grain string
var paper string

type RenderOpts struct {
Pad *int64
Sketch *bool
Center *bool
ThemeID *int64
DarkThemeID *int64
Font string
Pad *int64
Sketch *bool
Center *bool
ThemeID *int64
DarkThemeID *int64
ThemeOverrides *d2target.ThemeOverrides
DarkThemeOverrides *d2target.ThemeOverrides
Font string
// the svg will be scaled by this factor, if unset the svg will fit to screen
Scale *float64

Expand Down Expand Up @@ -1854,7 +1856,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
upperBuf := &bytes.Buffer{}
if opts.MasterID == "" {
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
themeStylesheet, err := ThemeCSS(diagramHash, &themeID, darkThemeID)
themeStylesheet, err := ThemeCSS(diagramHash, &themeID, darkThemeID, opts.ThemeOverrides, opts.DarkThemeOverrides)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2016,17 +2018,17 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
}

// TODO include only colors that are being used to reduce size
func ThemeCSS(diagramHash string, themeID *int64, darkThemeID *int64) (stylesheet string, err error) {
func ThemeCSS(diagramHash string, themeID *int64, darkThemeID *int64, overrides, darkOverrides *d2target.ThemeOverrides) (stylesheet string, err error) {
if themeID == nil {
themeID = &d2themescatalog.NeutralDefault.ID
}
out, err := singleThemeRulesets(diagramHash, *themeID)
out, err := singleThemeRulesets(diagramHash, *themeID, overrides)
if err != nil {
return "", err
}

if darkThemeID != nil {
darkOut, err := singleThemeRulesets(diagramHash, *darkThemeID)
darkOut, err := singleThemeRulesets(diagramHash, *darkThemeID, darkOverrides)
if err != nil {
return "", err
}
Expand All @@ -2036,9 +2038,10 @@ func ThemeCSS(diagramHash string, themeID *int64, darkThemeID *int64) (styleshee
return out, nil
}

func singleThemeRulesets(diagramHash string, themeID int64) (rulesets string, err error) {
func singleThemeRulesets(diagramHash string, themeID int64, overrides *d2target.ThemeOverrides) (rulesets string, err error) {
out := ""
theme := d2themescatalog.Find(themeID)
theme.ApplyOverrides(overrides)

// Global theme colors
for _, property := range []string{"fill", "stroke", "background-color", "color"} {
Expand Down
35 changes: 26 additions & 9 deletions d2target/d2target.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,35 @@ const (
var BorderOffset = geo.NewVector(5, 5)

type Config struct {
Sketch *bool `json:"sketch"`
ThemeID *int64 `json:"themeID"`
DarkThemeID *int64 `json:"darkThemeID"`
Pad *int64 `json:"pad"`
Center *bool `json:"center"`
LayoutEngine *string `json:"layoutEngine"`
ThemeOverrides *ThemeOverrides `json:"themeOverrides"`
Sketch *bool `json:"sketch"`
ThemeID *int64 `json:"themeID"`
DarkThemeID *int64 `json:"darkThemeID"`
Pad *int64 `json:"pad"`
Center *bool `json:"center"`
LayoutEngine *string `json:"layoutEngine"`
ThemeOverrides *ThemeOverrides `json:"themeOverrides,omitempty"`
DarkThemeOverrides *ThemeOverrides `json:"darkThemeOverrides,omitempty"`
}

type ThemeOverrides struct {
N1 *string `json:"n1"`
// TODO
N1 *string `json:"n1"`
N2 *string `json:"n2"`
N3 *string `json:"n3"`
N4 *string `json:"n4"`
N5 *string `json:"n5"`
N6 *string `json:"n6"`
N7 *string `json:"n7"`
B1 *string `json:"b1"`
B2 *string `json:"b2"`
B3 *string `json:"b3"`
B4 *string `json:"b4"`
B5 *string `json:"b5"`
B6 *string `json:"b6"`
AA2 *string `json:"aa2"`
AA4 *string `json:"aa4"`
AA5 *string `json:"aa5"`
AB4 *string `json:"ab4"`
AB5 *string `json:"ab5"`
}

type Diagram struct {
Expand Down
69 changes: 68 additions & 1 deletion d2themes/d2themes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// Color codes: darkest (N1) -> lightest (N7)
package d2themes

import "oss.terrastruct.com/d2/lib/color"
import (
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/color"
)

type Theme struct {
ID int64 `json:"id"`
Expand All @@ -26,6 +29,70 @@ func (t *Theme) IsDark() bool {
return t.ID >= 200 && t.ID < 300
}

func (t *Theme) ApplyOverrides(overrides *d2target.ThemeOverrides) {
if overrides == nil {
return
}

if overrides.B1 != nil {
t.Colors.B1 = *overrides.B1
}
if overrides.B2 != nil {
t.Colors.B2 = *overrides.B2
}
if overrides.B3 != nil {
t.Colors.B3 = *overrides.B3
}
if overrides.B4 != nil {
t.Colors.B4 = *overrides.B4
}
if overrides.B5 != nil {
t.Colors.B5 = *overrides.B5
}
if overrides.B5 != nil {
t.Colors.B5 = *overrides.B5
}
if overrides.B6 != nil {
t.Colors.B6 = *overrides.B6
}
if overrides.AA2 != nil {
t.Colors.AA2 = *overrides.AA2
}
if overrides.AA4 != nil {
t.Colors.AA4 = *overrides.AA4
}
if overrides.AA5 != nil {
t.Colors.AA5 = *overrides.AA5
}
if overrides.AB4 != nil {
t.Colors.AB4 = *overrides.AB4
}
if overrides.AB5 != nil {
t.Colors.AB5 = *overrides.AB5
}
if overrides.N1 != nil {
t.Colors.Neutrals.N1 = *overrides.N1
}
if overrides.N2 != nil {
t.Colors.Neutrals.N2 = *overrides.N2
}
if overrides.N3 != nil {
t.Colors.Neutrals.N3 = *overrides.N3
}
if overrides.N4 != nil {
t.Colors.Neutrals.N4 = *overrides.N4
}
if overrides.N5 != nil {
t.Colors.Neutrals.N5 = *overrides.N5
}
if overrides.N6 != nil {
t.Colors.Neutrals.N6 = *overrides.N6
}
if overrides.N7 != nil {
t.Colors.Neutrals.N7 = *overrides.N7
}
}

type Neutral struct {
N1 string `json:"n1"`
N2 string `json:"n2"`
Expand Down
Loading

0 comments on commit 27da0cb

Please sign in to comment.