diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 9cf579c30..5c370c204 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -235,6 +235,8 @@ func (c *compiler) compileAttributes(attrs *d2graph.Attributes, mk *d2ast.Key) { attrs.Width = &d2graph.Scalar{MapKey: mk} } else if reserved == "height" { attrs.Height = &d2graph.Scalar{MapKey: mk} + } else if reserved == "double-border" { + attrs.Style.DoubleBorder = &d2graph.Scalar{MapKey: mk} } } diff --git a/d2exporter/export.go b/d2exporter/export.go index 9706365f0..8a636759e 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -93,6 +93,9 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) { if obj.Attributes.Style.Font != nil { shape.FontFamily = obj.Attributes.Style.Font.Value } + if obj.Attributes.Style.DoubleBorder != nil { + shape.DoubleBorder, _ = strconv.ParseBool(obj.Attributes.Style.DoubleBorder.Value) + } } func toShape(obj *d2graph.Object, theme *d2themes.Theme) d2target.Shape { diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 006b9cb64..334d70595 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -147,6 +147,7 @@ type Style struct { Italic *Scalar `json:"italic,omitempty"` Underline *Scalar `json:"underline,omitempty"` Filled *Scalar `json:"filled,omitempty"` + DoubleBorder *Scalar `json:"doubleBorder,omitempty"` } func (s *Style) Apply(key, value string) error { @@ -300,6 +301,15 @@ func (s *Style) Apply(key, value string) error { return errors.New(`expected "filled" to be true or false`) } s.Filled.Value = value + case "double-border": + if s.DoubleBorder == nil { + break + } + _, err := strconv.ParseBool(value) + if err != nil { + return errors.New(`expected "double-border" to be true or false`) + } + s.DoubleBorder.Value = value default: return fmt.Errorf("unknown style key: %s", key) } @@ -1284,8 +1294,9 @@ var StyleKeywords = map[string]struct{}{ "underline": {}, // Only for shapes - "shadow": {}, - "multiple": {}, + "shadow": {}, + "multiple": {}, + "double-border": {}, // Only for squares "3d": {}, diff --git a/d2oracle/edit.go b/d2oracle/edit.go index b379df0d5..48f7d0692 100644 --- a/d2oracle/edit.go +++ b/d2oracle/edit.go @@ -284,6 +284,11 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error { attrs.Style.Multiple.MapKey.SetScalar(mk.Value.ScalarBox()) return nil } + case "double-border": + if attrs.Style.DoubleBorder != nil { + attrs.Style.DoubleBorder.MapKey.SetScalar(mk.Value.ScalarBox()) + return nil + } case "font": if attrs.Style.Font != nil { attrs.Style.Font.MapKey.SetScalar(mk.Value.ScalarBox()) diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 8623d2ec6..f2536fe84 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -589,7 +589,7 @@ func renderOval(tl *geo.Point, width, height float64, style string) string { return fmt.Sprintf(``, cx, cy, rx, ry, style) } -func renderDoubleCircle(tl *geo.Point, width, height float64, style string) string { +func renderDoubleOval(tl *geo.Point, width, height float64, style string) string { return renderOval(tl, width, height, style) + renderOval(&geo.Point{X: tl.X + 5, Y: tl.Y + 5}, width-10, height-10, style) } @@ -764,31 +764,46 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske fmt.Fprintf(writer, closingTag) return labelMask, nil case d2target.ShapeOval: - if targetShape.Multiple { - fmt.Fprint(writer, renderOval(multipleTL, width, height, style)) - } - if sketchRunner != nil { - out, err := d2sketch.Oval(sketchRunner, targetShape) - if err != nil { - return "", err + if !targetShape.DoubleBorder { + if targetShape.Multiple { + fmt.Fprint(writer, renderOval(multipleTL, width, height, style)) } - fmt.Fprintf(writer, out) - } else { - fmt.Fprint(writer, renderOval(tl, width, height, style)) - } - case d2target.ShapeDoubleCircle: - if targetShape.Multiple { - fmt.Fprint(writer, renderDoubleCircle(multipleTL, width, height, style)) - } - if sketchRunner != nil { - out, err := d2sketch.DoubleOval(sketchRunner, targetShape) - if err != nil { - return "", err + if sketchRunner != nil { + out, err := d2sketch.Oval(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + fmt.Fprint(writer, renderOval(tl, width, height, style)) } - fmt.Fprintf(writer, out) } else { - fmt.Fprint(writer, renderDoubleCircle(tl, width, height, style)) + if targetShape.Multiple { + fmt.Fprint(writer, renderDoubleOval(multipleTL, width, height, style)) + } + if sketchRunner != nil { + out, err := d2sketch.DoubleOval(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + fmt.Fprint(writer, renderDoubleOval(tl, width, height, style)) + } } + // case d2target.ShapeDoubleCircle: + // if targetShape.Multiple { + // fmt.Fprint(writer, renderDoubleCircle(multipleTL, width, height, style)) + // } + // if sketchRunner != nil { + // out, err := d2sketch.DoubleOval(sketchRunner, targetShape) + // if err != nil { + // return "", err + // } + // fmt.Fprintf(writer, out) + // } else { + // fmt.Fprint(writer, renderDoubleCircle(tl, width, height, style)) + // } case d2target.ShapeImage: fmt.Fprintf(writer, ``, diff --git a/d2target/d2target.go b/d2target/d2target.go index f810f7cce..75113ec90 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -143,9 +143,10 @@ type Shape struct { Fill string `json:"fill"` Stroke string `json:"stroke"` - Shadow bool `json:"shadow"` - ThreeDee bool `json:"3d"` - Multiple bool `json:"multiple"` + Shadow bool `json:"shadow"` + ThreeDee bool `json:"3d"` + Multiple bool `json:"multiple"` + DoubleBorder bool `json:"double-border"` Tooltip string `json:"tooltip"` Link string `json:"link"` diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index c527928b8..0fa299c0d 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -42,14 +42,13 @@ oval: {shape: "oval"} circle: {shape: "circle"} hexagon: {shape: "hexagon"} cloud: {shape: "cloud"} -double_circle: {shape: "double_circle"} rectangle -> square -> page parallelogram -> document -> cylinder queue -> package -> step callout -> stored_data -> person diamond -> oval -> circle -hexagon -> cloud -> double_circle +hexagon -> cloud `, }, { @@ -72,14 +71,13 @@ oval: {shape: "oval"} circle: {shape: "circle"} hexagon: {shape: "hexagon"} cloud: {shape: "cloud"} -double_circle: {shape: "double_circle"} rectangle -> square -> page parallelogram -> document -> cylinder queue -> package -> step callout -> stored_data -> person diamond -> oval -> circle -hexagon -> cloud -> double_circle +hexagon -> cloud rectangle.multiple: true square.multiple: true @@ -98,7 +96,6 @@ oval.multiple: true circle.multiple: true hexagon.multiple: true cloud.multiple: true -double_circle.multiple: true `, }, { @@ -121,14 +118,13 @@ oval: {shape: "oval"} circle: {shape: "circle"} hexagon: {shape: "hexagon"} cloud: {shape: "cloud"} -double_circle: {shape: "double_circle"} rectangle -> square -> page parallelogram -> document -> cylinder queue -> package -> step callout -> stored_data -> person diamond -> oval -> circle -hexagon -> cloud -> double_circle +hexagon -> cloud rectangle.shadow: true square.shadow: true @@ -147,7 +143,6 @@ oval.shadow: true circle.shadow: true hexagon.shadow: true cloud.shadow: true -double_circle.shadow: true `, }, { diff --git a/lib/shape/shape.go b/lib/shape/shape.go index edfc5e6f3..6422c588f 100644 --- a/lib/shape/shape.go +++ b/lib/shape/shape.go @@ -24,7 +24,6 @@ const ( CIRCLE_TYPE = "Circle" HEXAGON_TYPE = "Hexagon" CLOUD_TYPE = "Cloud" - DOUBLE_CIRCLE_TYPE = "DoubleCircle" TABLE_TYPE = "Table" CLASS_TYPE = "Class" @@ -109,8 +108,6 @@ func NewShape(shapeType string, box *geo.Box) Shape { return NewCallout(box) case CIRCLE_TYPE: return NewCircle(box) - case DOUBLE_CIRCLE_TYPE: - return NewDoubleCircle(box) case CLASS_TYPE: return NewClass(box) case CLOUD_TYPE: diff --git a/lib/shape/shape_double_circle.go b/lib/shape/shape_double_circle.go deleted file mode 100644 index f67e18b09..000000000 --- a/lib/shape/shape_double_circle.go +++ /dev/null @@ -1,37 +0,0 @@ -package shape - -import ( - "math" - - "oss.terrastruct.com/d2/lib/geo" -) - -type shapeDoubleCircle struct { - *baseShape -} - -func NewDoubleCircle(box *geo.Box) Shape { - return shapeDoubleCircle{ - baseShape: &baseShape{ - Type: DOUBLE_CIRCLE_TYPE, - Box: box, - }, - } -} - -func (s shapeDoubleCircle) AspectRatio1() bool { - return true -} - -func (s shapeDoubleCircle) GetDimensionsToFit(width, height, padding float64) (float64, float64) { - radius := math.Ceil(math.Sqrt(math.Pow(width/2, 2)+math.Pow(height/2, 2))) + padding - return radius * 2, radius * 2 -} - -func (s shapeDoubleCircle) GetInsidePlacement(width, height, padding float64) geo.Point { - return *geo.NewPoint(s.Box.TopLeft.X+math.Ceil(s.Box.Width/2-width/2), s.Box.TopLeft.Y+math.Ceil(s.Box.Height/2-height/2)) -} - -func (s shapeDoubleCircle) Perimeter() []geo.Intersectable { - return []geo.Intersectable{geo.NewEllipse(s.Box.Center(), s.Box.Width/2, s.Box.Height/2)} -}