diff --git a/d2compiler/compile.go b/d2compiler/compile.go
index 97f55f4a1..41b260f11 100644
--- a/d2compiler/compile.go
+++ b/d2compiler/compile.go
@@ -383,7 +383,9 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
} else if isReserved {
c.compileReserved(&obj.Attributes, f)
- return
+ if keyword != "icon" {
+ return
+ }
} else if f.Name.ScalarString() == "style" && f.Name.IsUnquoted() {
if f.Map() == nil || len(f.Map().Fields) == 0 {
c.errorf(f.LastRef().AST(), `"style" expected to be set to a map of key-values, or contain an additional keyword like "style.opacity: 0.4"`)
@@ -473,7 +475,10 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
}
attrs.Label.Value = scalar.ScalarString()
default:
- attrs.Label.Value = scalar.ScalarString()
+ name := f.LastPrimaryKey().Key.Path[0].UnquotedString.Value[0].String
+ if *name != "icon" {
+ attrs.Label.Value = scalar.ScalarString()
+ }
}
attrs.Label.MapKey = f.LastPrimaryKey()
}
@@ -757,7 +762,15 @@ func (c *compiler) compileStyleField(attrs *d2graph.Attributes, f *d2ir.Field) {
}
compileStyleFieldInit(attrs, f)
scalar := f.Primary().Value
- err := attrs.Style.Apply(f.Name.ScalarString(), scalar.ScalarString())
+
+ parentKeyword := f.LastPrimaryKey().Key.Path[0].ScalarString()
+
+ var err error
+ if parentKeyword == "icon" {
+ err = attrs.IconStyle.Apply(f.Name.ScalarString(), scalar.ScalarString())
+ } else {
+ err = attrs.Style.Apply(f.Name.ScalarString(), scalar.ScalarString())
+ }
if err != nil {
c.errorf(scalar, err.Error())
return
@@ -779,7 +792,12 @@ func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) {
case "stroke-dash":
attrs.Style.StrokeDash = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "border-radius":
- attrs.Style.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ if attrs.Style.BorderRadius == nil {
+ attrs.Style.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ }
+ if attrs.IconStyle.BorderRadius == nil {
+ attrs.IconStyle.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
+ }
case "shadow":
attrs.Style.Shadow = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
case "3d":
diff --git a/d2exporter/export.go b/d2exporter/export.go
index dc98b51e3..f52134da1 100644
--- a/d2exporter/export.go
+++ b/d2exporter/export.go
@@ -194,6 +194,9 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) {
if obj.Style.DoubleBorder != nil {
shape.DoubleBorder, _ = strconv.ParseBool(obj.Style.DoubleBorder.Value)
}
+ if obj.IconStyle.BorderRadius != nil {
+ shape.IconBorderRadius, _ = strconv.Atoi(obj.IconStyle.BorderRadius.Value)
+ }
}
func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape {
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index ed387918a..ba9a02657 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -196,10 +196,11 @@ type Attributes struct {
Label Scalar `json:"label"`
LabelDimensions d2target.TextDimensions `json:"labelDimensions"`
- Style Style `json:"style"`
- Icon *url.URL `json:"icon,omitempty"`
- Tooltip *Scalar `json:"tooltip,omitempty"`
- Link *Scalar `json:"link,omitempty"`
+ Style Style `json:"style"`
+ Icon *url.URL `json:"icon,omitempty"`
+ IconStyle Style `json:"iconStyle"`
+ Tooltip *Scalar `json:"tooltip,omitempty"`
+ Link *Scalar `json:"link,omitempty"`
WidthAttr *Scalar `json:"width,omitempty"`
HeightAttr *Scalar `json:"height,omitempty"`
diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index 8c28b9e5e..bfea683de 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -1163,6 +1163,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
}
case d2target.ShapeImage:
+ fmt.Fprint(writer, clipPathForIconBorderRadius(diagramHash, targetShape))
el := d2themes.NewThemableElement("image", inlineTheme)
el.X = float64(targetShape.Pos.X)
el.Y = float64(targetShape.Pos.Y)
@@ -1172,6 +1173,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
el.Fill = fill
el.Stroke = stroke
el.Style = style
+ el.ClipPath = fmt.Sprintf("%v-%v-icon", diagramHash, targetShape.ID)
fmt.Fprint(writer, el.Render())
// TODO should standardize "" to rectangle
@@ -1364,12 +1366,13 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize))
- fmt.Fprintf(writer, ``,
+ fmt.Fprintf(writer, ``,
html.EscapeString(targetShape.Icon.String()),
tl.X,
tl.Y,
iconSize,
iconSize,
+ targetShape.IconBorderRadius,
)
}
diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go
index f7b2782db..1fb2f15b4 100644
--- a/d2renderers/d2svg/table.go
+++ b/d2renderers/d2svg/table.go
@@ -41,6 +41,33 @@ func clipPathForBorderRadius(diagramHash string, shape d2target.Shape) string {
return out + `fill="none" /> `
}
+func clipPathForIconBorderRadius(diagramHash string, shape d2target.Shape) string {
+ box := geo.NewBox(
+ geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)),
+ float64(shape.Width),
+ float64(shape.Height),
+ )
+ topX, topY := box.TopLeft.X+box.Width, box.TopLeft.Y
+
+ out := fmt.Sprintf(``, diagramHash, shape.ID)
+ out += fmt.Sprintf(` `
+}
+
func tableHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64, inlineTheme *d2themes.Theme) string {
rectEl := d2themes.NewThemableElement("rect", inlineTheme)
rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y
diff --git a/d2target/d2target.go b/d2target/d2target.go
index 3da7cec08..c50f16db7 100644
--- a/d2target/d2target.go
+++ b/d2target/d2target.go
@@ -493,11 +493,12 @@ type Shape struct {
Multiple bool `json:"multiple"`
DoubleBorder bool `json:"double-border"`
- Tooltip string `json:"tooltip"`
- Link string `json:"link"`
- PrettyLink string `json:"prettyLink,omitempty"`
- Icon *url.URL `json:"icon"`
- IconPosition string `json:"iconPosition"`
+ Tooltip string `json:"tooltip"`
+ Link string `json:"link"`
+ PrettyLink string `json:"prettyLink,omitempty"`
+ Icon *url.URL `json:"icon"`
+ IconBorderRadius int `json:"iconBorderRadius"`
+ IconPosition string `json:"iconPosition"`
// Whether the shape should allow shapes behind it to bleed through
// Currently just used for sequence diagram groups