diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 2846c7587..c0a78d01d 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -877,6 +877,11 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske if targetShape.Opacity != 1.0 { opacityStyle = fmt.Sprintf(" style='opacity:%f'", targetShape.Opacity) } + + // this clipPath must be defined outside `g` element + if targetShape.BorderRadius != 0 { + fmt.Fprint(writer, tableHeaderBorderRadius(targetShape)) + } fmt.Fprintf(writer, ``, svg.EscapeText(targetShape.ID), opacityStyle) tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)) width := float64(targetShape.Width) diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go index e4f0695ac..1de55d4aa 100644 --- a/d2renderers/d2svg/table.go +++ b/d2renderers/d2svg/table.go @@ -3,6 +3,7 @@ package d2svg import ( "fmt" "io" + "strings" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes" @@ -12,6 +13,23 @@ import ( "oss.terrastruct.com/util-go/go2" ) +func tableHeaderBorderRadius(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(``, shape.ID) + out += fmt.Sprintf(` ` +} + func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string { rectEl := d2themes.NewThemableElement("rect") rectEl.X, rectEl.Y = box.TopLeft.X, box.TopLeft.Y @@ -19,6 +37,9 @@ func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, tex rectEl.Fill = shape.Fill rectEl.ClassName = "class_header" str := rectEl.Render() + if shape.BorderRadius != 0 { + str = strings.Replace(str, "/>", fmt.Sprintf(`clip-path="url(#%v)" />`, shape.ID), 1) + } if text != "" { tl := label.InsideMiddleLeft.GetPointOnBox( @@ -91,6 +112,8 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) { rectEl.Fill, rectEl.Stroke = d2themes.ShapeTheme(targetShape) rectEl.ClassName = "shape" rectEl.Style = targetShape.CSSStyle() + rectEl.Rx = float64(targetShape.BorderRadius) + rectEl.Ry = float64(targetShape.BorderRadius) fmt.Fprint(writer, rectEl.Render()) box := geo.NewBox( @@ -113,15 +136,20 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) { rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight) rowBox.TopLeft.Y += headerBox.Height - for _, f := range targetShape.Columns { + for idx, f := range targetShape.Columns { fmt.Fprint(writer, tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth)), ) rowBox.TopLeft.Y += rowHeight lineEl := d2themes.NewThemableElement("line") - lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y - lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y + if idx == len(targetShape.Columns)-1 && targetShape.BorderRadius != 0 { + lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X+float64(targetShape.BorderRadius), rowBox.TopLeft.Y + lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width-float64(targetShape.BorderRadius), rowBox.TopLeft.Y + } else { + lineEl.X1, lineEl.Y1 = rowBox.TopLeft.X, rowBox.TopLeft.Y + lineEl.X2, lineEl.Y2 = rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y + } lineEl.Stroke = targetShape.Fill lineEl.Style = "stroke-width:2" fmt.Fprint(writer, lineEl.Render())