Skip to content

Commit 9603f1a

Browse files
committed
feat: ability to add border-radius to d2 icons
1 parent 32c14d5 commit 9603f1a

File tree

6 files changed

+67
-14
lines changed

6 files changed

+67
-14
lines changed

d2compiler/compile.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,9 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
383383

384384
} else if isReserved {
385385
c.compileReserved(&obj.Attributes, f)
386-
return
386+
if keyword != "icon" {
387+
return
388+
}
387389
} else if f.Name.ScalarString() == "style" && f.Name.IsUnquoted() {
388390
if f.Map() == nil || len(f.Map().Fields) == 0 {
389391
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) {
473475
}
474476
attrs.Label.Value = scalar.ScalarString()
475477
default:
476-
attrs.Label.Value = scalar.ScalarString()
478+
name := f.LastPrimaryKey().Key.Path[0].UnquotedString.Value[0].String
479+
if *name != "icon" {
480+
attrs.Label.Value = scalar.ScalarString()
481+
}
477482
}
478483
attrs.Label.MapKey = f.LastPrimaryKey()
479484
}
@@ -757,7 +762,15 @@ func (c *compiler) compileStyleField(attrs *d2graph.Attributes, f *d2ir.Field) {
757762
}
758763
compileStyleFieldInit(attrs, f)
759764
scalar := f.Primary().Value
760-
err := attrs.Style.Apply(f.Name.ScalarString(), scalar.ScalarString())
765+
766+
parentKeyword := f.LastPrimaryKey().Key.Path[0].ScalarString()
767+
768+
var err error
769+
if parentKeyword == "icon" {
770+
err = attrs.IconStyle.Apply(f.Name.ScalarString(), scalar.ScalarString())
771+
} else {
772+
err = attrs.Style.Apply(f.Name.ScalarString(), scalar.ScalarString())
773+
}
761774
if err != nil {
762775
c.errorf(scalar, err.Error())
763776
return
@@ -779,7 +792,12 @@ func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) {
779792
case "stroke-dash":
780793
attrs.Style.StrokeDash = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
781794
case "border-radius":
782-
attrs.Style.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
795+
if attrs.Style.BorderRadius == nil {
796+
attrs.Style.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
797+
}
798+
if attrs.IconStyle.BorderRadius == nil {
799+
attrs.IconStyle.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
800+
}
783801
case "shadow":
784802
attrs.Style.Shadow = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
785803
case "3d":

d2exporter/export.go

+3
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) {
194194
if obj.Style.DoubleBorder != nil {
195195
shape.DoubleBorder, _ = strconv.ParseBool(obj.Style.DoubleBorder.Value)
196196
}
197+
if obj.IconStyle.BorderRadius != nil {
198+
shape.IconBorderRadius, _ = strconv.Atoi(obj.IconStyle.BorderRadius.Value)
199+
}
197200
}
198201

199202
func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape {

d2graph/d2graph.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ type Attributes struct {
196196
Label Scalar `json:"label"`
197197
LabelDimensions d2target.TextDimensions `json:"labelDimensions"`
198198

199-
Style Style `json:"style"`
200-
Icon *url.URL `json:"icon,omitempty"`
201-
Tooltip *Scalar `json:"tooltip,omitempty"`
202-
Link *Scalar `json:"link,omitempty"`
199+
Style Style `json:"style"`
200+
Icon *url.URL `json:"icon,omitempty"`
201+
IconStyle Style `json:"iconStyle"`
202+
Tooltip *Scalar `json:"tooltip,omitempty"`
203+
Link *Scalar `json:"link,omitempty"`
203204

204205
WidthAttr *Scalar `json:"width,omitempty"`
205206
HeightAttr *Scalar `json:"height,omitempty"`

d2renderers/d2svg/d2svg.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
11631163
}
11641164

11651165
case d2target.ShapeImage:
1166+
fmt.Fprint(writer, clipPathForIconBorderRadius(diagramHash, targetShape))
11661167
el := d2themes.NewThemableElement("image", inlineTheme)
11671168
el.X = float64(targetShape.Pos.X)
11681169
el.Y = float64(targetShape.Pos.Y)
@@ -1172,6 +1173,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
11721173
el.Fill = fill
11731174
el.Stroke = stroke
11741175
el.Style = style
1176+
el.ClipPath = fmt.Sprintf("%v-%v-icon", diagramHash, targetShape.ID)
11751177
fmt.Fprint(writer, el.Render())
11761178

11771179
// TODO should standardize "" to rectangle
@@ -1364,12 +1366,13 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
13641366

13651367
tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize))
13661368

1367-
fmt.Fprintf(writer, `<image href="%s" x="%f" y="%f" width="%d" height="%d" />`,
1369+
fmt.Fprintf(writer, `<image href="%s" x="%f" y="%f" width="%d" height="%d" clip-path="inset(0 round %dpx)" />`,
13681370
html.EscapeString(targetShape.Icon.String()),
13691371
tl.X,
13701372
tl.Y,
13711373
iconSize,
13721374
iconSize,
1375+
targetShape.IconBorderRadius,
13731376
)
13741377
}
13751378

d2renderers/d2svg/table.go

+27
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,33 @@ func clipPathForBorderRadius(diagramHash string, shape d2target.Shape) string {
4141
return out + `fill="none" /> </clipPath>`
4242
}
4343

44+
func clipPathForIconBorderRadius(diagramHash string, shape d2target.Shape) string {
45+
box := geo.NewBox(
46+
geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)),
47+
float64(shape.Width),
48+
float64(shape.Height),
49+
)
50+
topX, topY := box.TopLeft.X+box.Width, box.TopLeft.Y
51+
52+
out := fmt.Sprintf(`<clipPath id="%v-%v-icon">`, diagramHash, shape.ID)
53+
out += fmt.Sprintf(`<path d="M %f %f L %f %f S %f %f %f %f `, box.TopLeft.X, box.TopLeft.Y+float64(shape.IconBorderRadius), box.TopLeft.X, box.TopLeft.Y+float64(shape.IconBorderRadius), box.TopLeft.X, box.TopLeft.Y, box.TopLeft.X+float64(shape.IconBorderRadius), box.TopLeft.Y)
54+
out += fmt.Sprintf(`L %f %f L %f %f `, box.TopLeft.X+box.Width-float64(shape.IconBorderRadius), box.TopLeft.Y, topX-float64(shape.IconBorderRadius), topY)
55+
56+
out += fmt.Sprintf(`S %f %f %f %f `, topX, topY, topX, topY+float64(shape.IconBorderRadius))
57+
out += fmt.Sprintf(`L %f %f `, topX, topY+box.Height-float64(shape.IconBorderRadius))
58+
59+
if len(shape.Columns) != 0 {
60+
out += fmt.Sprintf(`L %f %f L %f %f`, topX, topY+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height)
61+
} else {
62+
out += fmt.Sprintf(`S %f % f %f %f `, topX, topY+box.Height, topX-float64(shape.IconBorderRadius), topY+box.Height)
63+
out += fmt.Sprintf(`L %f %f `, box.TopLeft.X+float64(shape.IconBorderRadius), box.TopLeft.Y+box.Height)
64+
out += fmt.Sprintf(`S %f %f %f %f`, box.TopLeft.X, box.TopLeft.Y+box.Height, box.TopLeft.X, box.TopLeft.Y+box.Height-float64(shape.IconBorderRadius))
65+
out += fmt.Sprintf(`L %f %f`, box.TopLeft.X, box.TopLeft.Y+float64(shape.IconBorderRadius))
66+
}
67+
out += fmt.Sprintf(`Z %f %f" `, box.TopLeft.X, box.TopLeft.Y)
68+
return out + `fill="none" /> </clipPath>`
69+
}
70+
4471
func tableHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64, inlineTheme *d2themes.Theme) string {
4572
rectEl := d2themes.NewThemableElement("rect", inlineTheme)
4673
rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y

d2target/d2target.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,12 @@ type Shape struct {
493493
Multiple bool `json:"multiple"`
494494
DoubleBorder bool `json:"double-border"`
495495

496-
Tooltip string `json:"tooltip"`
497-
Link string `json:"link"`
498-
PrettyLink string `json:"prettyLink,omitempty"`
499-
Icon *url.URL `json:"icon"`
500-
IconPosition string `json:"iconPosition"`
496+
Tooltip string `json:"tooltip"`
497+
Link string `json:"link"`
498+
PrettyLink string `json:"prettyLink,omitempty"`
499+
Icon *url.URL `json:"icon"`
500+
IconBorderRadius int `json:"iconBorderRadius"`
501+
IconPosition string `json:"iconPosition"`
501502

502503
// Whether the shape should allow shapes behind it to bleed through
503504
// Currently just used for sequence diagram groups

0 commit comments

Comments
 (0)