diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index 89ee90444..8643768b7 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -509,6 +509,91 @@ func defineShadowFilter(writer io.Writer) {
`)
}
+func render3dRect(targetShape d2target.Shape) string {
+ moveTo := func(p d2target.Point) string {
+ return fmt.Sprintf("M%d,%d ", p.X+targetShape.Pos.X, p.Y+targetShape.Pos.Y)
+ }
+ lineTo := func(p d2target.Point) string {
+ return fmt.Sprintf("L%d,%d ", p.X+targetShape.Pos.X, p.Y+targetShape.Pos.Y)
+ }
+
+ // draw border all in one path to prevent overlapping sections
+ var borderSegments []string
+ borderSegments = append(borderSegments,
+ moveTo(d2target.Point{X: 0, Y: 0}),
+ )
+ for _, v := range []d2target.Point{
+ {X: threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
+ {X: targetShape.Width, Y: targetShape.Height},
+ {X: 0, Y: targetShape.Height},
+ {X: 0, Y: 0},
+ {X: targetShape.Width, Y: 0},
+ {X: targetShape.Width, Y: targetShape.Height},
+ } {
+ borderSegments = append(borderSegments, lineTo(v))
+ }
+ // move to top right to draw last segment without overlapping
+ borderSegments = append(borderSegments,
+ moveTo(d2target.Point{X: targetShape.Width, Y: 0}),
+ )
+ borderSegments = append(borderSegments,
+ lineTo(d2target.Point{X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset}),
+ )
+ border := targetShape
+ border.Fill = "none"
+ borderStyle := shapeStyle(border)
+ renderedBorder := fmt.Sprintf(``,
+ strings.Join(borderSegments, ""), borderStyle)
+
+ // create mask from border stroke, to cut away from the shape fills
+ maskID := fmt.Sprintf("border-mask-%v", escapeText(targetShape.ID))
+ borderMask := strings.Join([]string{
+ fmt.Sprintf(``,
+ maskID, targetShape.Pos.X, targetShape.Pos.Y-threeDeeOffset, targetShape.Width+threeDeeOffset, targetShape.Height+threeDeeOffset,
+ ),
+ fmt.Sprintf(``,
+ targetShape.Pos.X, targetShape.Pos.Y-threeDeeOffset, targetShape.Width+threeDeeOffset, targetShape.Height+threeDeeOffset,
+ ),
+ fmt.Sprintf(``,
+ strings.Join(borderSegments, ""), borderStyle),
+ }, "\n")
+
+ // render the main rectangle without stroke and the border mask
+ mainShape := targetShape
+ mainShape.Stroke = "none"
+ mainRect := fmt.Sprintf(``,
+ targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(mainShape), maskID,
+ )
+
+ // render the side shapes in the darkened color without stroke and the border mask
+ var sidePoints []string
+ for _, v := range []d2target.Point{
+ {X: 0, Y: 0},
+ {X: threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: -threeDeeOffset},
+ {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
+ {X: targetShape.Width, Y: targetShape.Height},
+ {X: targetShape.Width, Y: 0},
+ } {
+ sidePoints = append(sidePoints,
+ fmt.Sprintf("%d,%d", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
+ )
+ }
+ darkerColor, err := color.Darken(targetShape.Fill)
+ if err != nil {
+ darkerColor = targetShape.Fill
+ }
+ sideShape := targetShape
+ sideShape.Fill = darkerColor
+ sideShape.Stroke = "none"
+ renderedSides := fmt.Sprintf(``,
+ strings.Join(sidePoints, " "), shapeStyle(sideShape), maskID)
+
+ return borderMask + mainRect + renderedSides + renderedBorder
+}
+
func drawShape(writer io.Writer, targetShape d2target.Shape) error {
fmt.Fprintf(writer, ``, escapeText(targetShape.ID))
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
@@ -561,50 +646,15 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error {
// TODO should standardize "" to rectangle
case d2target.ShapeRectangle, "":
if targetShape.ThreeDee {
- darkerColor, err := color.Darken(targetShape.Fill)
- if err != nil {
- darkerColor = targetShape.Fill
+ fmt.Fprint(writer, render3dRect(targetShape))
+ } else {
+ if targetShape.Multiple {
+ fmt.Fprintf(writer, ``,
+ targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
}
- sideShape := targetShape
- sideShape.Fill = darkerColor
- sideStyle := shapeStyle(sideShape)
-
- var topPolygonPoints []string
- for _, v := range []d2target.Point{
- {X: 0, Y: 0},
- {X: threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width + threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width, Y: 0},
- {X: 0, Y: 0},
- } {
- topPolygonPoints = append(topPolygonPoints,
- fmt.Sprintf("%d,%d ", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
- )
- }
- fmt.Fprintf(writer, ``,
- strings.Join(topPolygonPoints, ""), sideStyle)
-
- var rightPolygonPoints []string
- for _, v := range []d2target.Point{
- {X: targetShape.Width, Y: 0},
- {X: targetShape.Width + threeDeeOffset, Y: -1 * threeDeeOffset},
- {X: targetShape.Width + threeDeeOffset, Y: targetShape.Height - threeDeeOffset},
- {X: targetShape.Width, Y: targetShape.Height},
- } {
- rightPolygonPoints = append(rightPolygonPoints,
- fmt.Sprintf("%d,%d ", v.X+targetShape.Pos.X, v.Y+targetShape.Pos.Y),
- )
- }
- fmt.Fprintf(writer, ``,
- strings.Join(rightPolygonPoints, ""), sideStyle)
- }
- if targetShape.Multiple {
fmt.Fprintf(writer, ``,
- targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
+ targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
}
- fmt.Fprintf(writer, ``,
- targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
-
case d2target.ShapeText, d2target.ShapeCode:
default:
if targetShape.Multiple {
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index dc2e70b72..f3c89d37c 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -913,7 +913,7 @@ y: {
opacity: 0.6
fill: red
3d: true
- stroke: black
+ stroke: black
}
}