vary sketch overlay color depending on the background and small refactoring
This commit is contained in:
parent
fb6eee9a24
commit
63a0c1e2b1
9 changed files with 328 additions and 111 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -56,9 +56,6 @@ var LinkIcon string
|
|||
//go:embed style.css
|
||||
var baseStylesheet string
|
||||
|
||||
//go:embed sketchstyle.css
|
||||
var sketchStyleCSS string
|
||||
|
||||
//go:embed github-markdown.css
|
||||
var mdCSS string
|
||||
|
||||
|
|
@ -1248,12 +1245,11 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
backgroundEl.Fill = color.N7
|
||||
|
||||
// generate elements that will be appended to the SVG tag
|
||||
themeStylesheet := themeCSS(themeID, darkThemeID)
|
||||
sketchStylesheet := ""
|
||||
if sketchRunner != nil {
|
||||
sketchStylesheet = "\n" + sketchStyleCSS
|
||||
themeStylesheet, err := themeCSS(themeID, darkThemeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svgOut := fmt.Sprintf(`<style type="text/css"><![CDATA[%s%s%s]]></style>`, baseStylesheet, themeStylesheet, sketchStylesheet)
|
||||
svgOut := fmt.Sprintf(`<style type="text/css"><![CDATA[%s%s]]></style>`, baseStylesheet, themeStylesheet)
|
||||
// this script won't run in --watch mode because script tags are ignored when added via el.innerHTML = element
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
||||
svgOut += fmt.Sprintf(`<script type="application/javascript"><![CDATA[%s]]></script>`, fitToScreenScript)
|
||||
|
|
@ -1268,7 +1264,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
svgOut += fmt.Sprintf(`<style type="text/css">%s</style>`, mdCSS)
|
||||
}
|
||||
if sketchRunner != nil {
|
||||
svgOut += d2sketch.DefineFillPattern()
|
||||
svgOut += d2sketch.DefineFillPatterns()
|
||||
}
|
||||
svgOut += embedFonts(buf, diagram.FontFamily)
|
||||
|
||||
|
|
@ -1282,20 +1278,29 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
return []byte(docRendered), nil
|
||||
}
|
||||
|
||||
func themeCSS(themeID, darkThemeID int64) (stylesheet string) {
|
||||
out := singleThemeRulesets(themeID)
|
||||
// TODO include only colors that are being used to reduce size
|
||||
func themeCSS(themeID, darkThemeID int64) (stylesheet string, err error) {
|
||||
out, err := singleThemeRulesets(themeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if darkThemeID != math.MaxInt64 {
|
||||
out += fmt.Sprintf("@media screen and (prefers-color-scheme:dark){%s}", singleThemeRulesets(darkThemeID))
|
||||
darkOut, err := singleThemeRulesets(darkThemeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf("@media screen and (prefers-color-scheme:dark){%s}", darkOut)
|
||||
}
|
||||
|
||||
return out
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func singleThemeRulesets(themeID int64) (rulesets string) {
|
||||
func singleThemeRulesets(themeID int64) (rulesets string, err error) {
|
||||
out := ""
|
||||
theme := d2themescatalog.Find(themeID)
|
||||
|
||||
// Global theme colors
|
||||
for _, property := range []string{"fill", "stroke", "background-color", "color"} {
|
||||
out += fmt.Sprintf(".%s-N1{%s:%s;}.%s-N2{%s:%s;}.%s-N3{%s:%s;}.%s-N4{%s:%s;}.%s-N5{%s:%s;}.%s-N6{%s:%s;}.%s-N7{%s:%s;}.%s-B1{%s:%s;}.%s-B2{%s:%s;}.%s-B3{%s:%s;}.%s-B4{%s:%s;}.%s-B5{%s:%s;}.%s-B6{%s:%s;}.%s-AA2{%s:%s;}.%s-AA4{%s:%s;}.%s-AA5{%s:%s;}.%s-AB4{%s:%s;}.%s-AB5{%s:%s;}",
|
||||
property, property, theme.Colors.Neutrals.N1,
|
||||
|
|
@ -1319,6 +1324,7 @@ func singleThemeRulesets(themeID int64) (rulesets string) {
|
|||
)
|
||||
}
|
||||
|
||||
// Markdown specific rulesets
|
||||
out += fmt.Sprintf(".md{--color-fg-default:%s;--color-fg-muted:%s;--color-fg-subtle:%s;--color-canvas-default:%s;--color-canvas-subtle:%s;--color-border-default:%s;--color-border-muted:%s;--color-neutral-muted:%s;--color-accent-fg:%s;--color-accent-emphasis:%s;--color-attention-subtle:%s;--color-danger-fg:%s;}",
|
||||
theme.Colors.Neutrals.N1, theme.Colors.Neutrals.N2, theme.Colors.Neutrals.N3,
|
||||
theme.Colors.Neutrals.N7, theme.Colors.Neutrals.N6,
|
||||
|
|
@ -1329,7 +1335,91 @@ func singleThemeRulesets(themeID int64) (rulesets string) {
|
|||
"red",
|
||||
)
|
||||
|
||||
return out
|
||||
// Sketch style specific rulesets
|
||||
lc, err := color.LuminanceCategory(theme.Colors.B1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B1, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.B2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B2, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.B3)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B3, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.B4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B4, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.B5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B5, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.B6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.B6, lc, blendMode(lc))
|
||||
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N1, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N2, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N3)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N3, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N4, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N5, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N6, lc, blendMode(lc))
|
||||
lc, err = color.LuminanceCategory(theme.Colors.Neutrals.N7)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out += fmt.Sprintf(".sketch-overlay-%s{fill:url(#streaks-%s);mix-blend-mode:%s}", color.N7, lc, blendMode(lc))
|
||||
|
||||
// TODO Add the rest of the colors so we can allow the user to specify theme colors too
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func blendMode(lc string) string {
|
||||
switch lc {
|
||||
case "bright":
|
||||
return "darken"
|
||||
case "normal":
|
||||
return "color-burn"
|
||||
case "dark":
|
||||
return "overlay"
|
||||
case "darker":
|
||||
return "lighten"
|
||||
}
|
||||
panic("invalid luminance category")
|
||||
}
|
||||
|
||||
type DiagramObject interface {
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
.sketch-overlay {
|
||||
fill: url(#streaks);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
|
@ -10,3 +10,20 @@
|
|||
mix-Blend-mode: multiply;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.sketch-overlay-bright {
|
||||
fill: url(#streaks-bright);
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
.sketch-overlay-normal {
|
||||
fill: url(#streaks-normal);
|
||||
mix-blend-mode: color-burn;
|
||||
}
|
||||
.sketch-overlay-dark {
|
||||
fill: url(#streaks-dark);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
.sketch-overlay-darker {
|
||||
fill: url(#streaks-darker);
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
"github.com/mazznoer/csscolorparser"
|
||||
)
|
||||
|
||||
var themeRegex = regexp.MustCompile("^B[1-6]$")
|
||||
var themeColorRegex = regexp.MustCompile(`^N[1-7]|B[1-6]|AA[245]|AB[45]$`)
|
||||
|
||||
func IsThemeColor(colorString string) bool {
|
||||
return themeColorRegex.Match([]byte(colorString))
|
||||
}
|
||||
|
||||
func Darken(colorString string) (string, error) {
|
||||
if themeRegex.MatchString(colorString) {
|
||||
if IsThemeColor(colorString) {
|
||||
switch colorString[1] {
|
||||
case '1':
|
||||
return B1, nil
|
||||
|
|
@ -24,13 +29,15 @@ func Darken(colorString string) (string, error) {
|
|||
return B4, nil
|
||||
case '6':
|
||||
return B5, nil
|
||||
default:
|
||||
return "", fmt.Errorf("darkening color \"%s\" is not yet supported", colorString) // TODO Add the rest of the colors so we can allow the user to specify theme colors too
|
||||
}
|
||||
}
|
||||
|
||||
return DarkenCSS(colorString)
|
||||
return darkenCSS(colorString)
|
||||
}
|
||||
|
||||
func DarkenCSS(colorString string) (string, error) {
|
||||
func darkenCSS(colorString string) (string, error) {
|
||||
c, err := csscolorparser.Parse(colorString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -40,6 +47,38 @@ func DarkenCSS(colorString string) (string, error) {
|
|||
return colorful.Hsl(h, s, l-.1).Clamped().Hex(), nil
|
||||
}
|
||||
|
||||
func LuminanceCategory(colorString string) (string, error) {
|
||||
l, err := Luminance(colorString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch {
|
||||
case l >= .88:
|
||||
return "bright", nil
|
||||
case l >= .55:
|
||||
return "normal", nil
|
||||
case l >= .30:
|
||||
return "dark", nil
|
||||
default:
|
||||
return "darker", nil
|
||||
}
|
||||
}
|
||||
|
||||
func Luminance(colorString string) (float64, error) {
|
||||
c, err := csscolorparser.Parse(colorString)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
l := float64(
|
||||
float64(0.299)*float64(c.R) +
|
||||
float64(0.587)*float64(c.G) +
|
||||
float64(0.114)*float64(c.B),
|
||||
)
|
||||
return l, nil
|
||||
}
|
||||
|
||||
const (
|
||||
N1 = "N1"
|
||||
N2 = "N2"
|
||||
|
|
|
|||
52
lib/svg/style/common.go
Normal file
52
lib/svg/style/common.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
)
|
||||
|
||||
func ShapeStyle(shape d2target.Shape) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
if shape.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ShapeTheme(shape d2target.Shape) (fill, stroke string) {
|
||||
if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass {
|
||||
// Fill is used for header fill in these types
|
||||
// This fill property is just background of rows
|
||||
fill = shape.Stroke
|
||||
// Stroke (border) of these shapes should match the header fill
|
||||
stroke = shape.Fill
|
||||
} else {
|
||||
fill = shape.Fill
|
||||
stroke = shape.Stroke
|
||||
}
|
||||
return fill, stroke
|
||||
}
|
||||
|
||||
func ConnectionStyle(connection d2target.Connection) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
if connection.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ConnectionTheme(connection d2target.Connection) (stroke string) {
|
||||
return connection.Stroke
|
||||
}
|
||||
|
|
@ -3,57 +3,10 @@ package style
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
)
|
||||
|
||||
func ShapeStyle(shape d2target.Shape) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
if shape.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ShapeTheme(shape d2target.Shape) (fill, stroke string) {
|
||||
if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass {
|
||||
// Fill is used for header fill in these types
|
||||
// This fill property is just background of rows
|
||||
fill = shape.Stroke
|
||||
// Stroke (border) of these shapes should match the header fill
|
||||
stroke = shape.Fill
|
||||
} else {
|
||||
fill = shape.Fill
|
||||
stroke = shape.Stroke
|
||||
}
|
||||
return fill, stroke
|
||||
}
|
||||
|
||||
func ConnectionStyle(connection d2target.Connection) string {
|
||||
out := ""
|
||||
|
||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
if connection.StrokeDash != 0 {
|
||||
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ConnectionTheme(connection d2target.Connection) (stroke string) {
|
||||
return connection.Stroke
|
||||
}
|
||||
|
||||
// ThemableElement is a helper class for creating new XML elements.
|
||||
// This should be preffered over formatting and must be used
|
||||
// whenever Fill, Stroke, BackgroundColor or Color contains a color from a theme.
|
||||
|
|
@ -128,8 +81,6 @@ func NewThemableElement(tag string) *ThemableElement {
|
|||
}
|
||||
|
||||
func (el *ThemableElement) Render() string {
|
||||
re := regexp.MustCompile(`^N[1-7]|B[1-6]|AA[245]|AB[45]$`)
|
||||
|
||||
out := "<" + el.tag
|
||||
|
||||
if el.X != math.MaxFloat64 {
|
||||
|
|
@ -195,22 +146,22 @@ func (el *ThemableElement) Render() string {
|
|||
style := el.Style
|
||||
|
||||
// Add class {property}-{theme color} if the color is from a theme, set the property otherwise
|
||||
if re.MatchString(el.Stroke) {
|
||||
if color.IsThemeColor(el.Stroke) {
|
||||
class += fmt.Sprintf(" stroke-%s", el.Stroke)
|
||||
} else if len(el.Stroke) > 0 {
|
||||
out += fmt.Sprintf(` stroke="%s"`, el.Stroke)
|
||||
}
|
||||
if re.MatchString(el.Fill) {
|
||||
if color.IsThemeColor(el.Fill) {
|
||||
class += fmt.Sprintf(" fill-%s", el.Fill)
|
||||
} else if len(el.Fill) > 0 {
|
||||
out += fmt.Sprintf(` fill="%s"`, el.Fill)
|
||||
}
|
||||
if re.MatchString(el.BackgroundColor) {
|
||||
if color.IsThemeColor(el.BackgroundColor) {
|
||||
class += fmt.Sprintf(" background-color-%s", el.BackgroundColor)
|
||||
} else if len(el.BackgroundColor) > 0 {
|
||||
out += fmt.Sprintf(` background-color="%s"`, el.BackgroundColor)
|
||||
}
|
||||
if re.MatchString(el.Color) {
|
||||
if color.IsThemeColor(el.Color) {
|
||||
class += fmt.Sprintf(" color-%s", el.Color)
|
||||
} else if len(el.Color) > 0 {
|
||||
out += fmt.Sprintf(` color="%s"`, el.Color)
|
||||
34
lib/svg/style/sketch_overlay.go
Normal file
34
lib/svg/style/sketch_overlay.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
)
|
||||
|
||||
type ThemableSketchOverlay struct {
|
||||
el *ThemableElement
|
||||
fill string
|
||||
}
|
||||
|
||||
func NewThemableSketchOverlay(el *ThemableElement, fill string) *ThemableSketchOverlay {
|
||||
return &ThemableSketchOverlay{
|
||||
el,
|
||||
fill,
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: Do not reuse the element afterwards as this function changes the Class propery
|
||||
func (o *ThemableSketchOverlay) Render() (string, error) {
|
||||
if color.IsThemeColor(o.fill) {
|
||||
o.el.Class += fmt.Sprintf(" sketch-overlay-%s", o.fill) // e.g. sketch-overlay-B3
|
||||
} else {
|
||||
lc, err := color.LuminanceCategory(o.fill)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
o.el.Class += fmt.Sprintf(" sketch-overlay-%s", lc) // e.g. sketch-overlay-dark
|
||||
}
|
||||
|
||||
return o.el.Render(), nil
|
||||
}
|
||||
Loading…
Reference in a new issue