diff --git a/d2compiler/compile.go b/d2compiler/compile.go index c89a4853e..aa3c0adfa 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -235,6 +235,12 @@ 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" { + if attrs.Shape.Value != "" && !strings.EqualFold(attrs.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(attrs.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(attrs.Shape.Value, d2target.ShapeCircle) && !strings.EqualFold(attrs.Shape.Value, d2target.ShapeOval) { + c.errorf(mk.Range.Start, mk.Range.End, `key "double-border" can only be applied to squares, rectangles, circles, ovals`) + return + } + 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 f4b99a03e..06411bc21 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) } @@ -1287,8 +1297,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/d2sketch/sketch.go b/d2renderers/d2sketch/sketch.go index 16697c0b1..e95a6fe5a 100644 --- a/d2renderers/d2sketch/sketch.go +++ b/d2renderers/d2sketch/sketch.go @@ -91,6 +91,47 @@ func Rect(r *Runner, shape d2target.Shape) (string, error) { return output, nil } +func DoubleRect(r *Runner, shape d2target.Shape) (string, error) { + jsBigRect := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + pathsBigRect, err := computeRoughPathData(r, jsBigRect) + if err != nil { + return "", err + } + jsSmallRect := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width-d2target.INNER_BORDER_OFFSET*2, shape.Height-d2target.INNER_BORDER_OFFSET*2, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + pathsSmallRect, err := computeRoughPathData(r, jsSmallRect) + if err != nil { + return "", err + } + output := "" + for _, p := range pathsBigRect { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shape.CSSStyle(), + ) + } + for _, p := range pathsSmallRect { + output += fmt.Sprintf( + ``, + shape.Pos.X+d2target.INNER_BORDER_OFFSET, shape.Pos.Y+d2target.INNER_BORDER_OFFSET, p, shape.CSSStyle(), + ) + } + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, shape.Width, shape.Height, + ) + return output, nil +} + func Oval(r *Runner, shape d2target.Shape) (string, error) { js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { fill: "%s", @@ -116,6 +157,47 @@ func Oval(r *Runner, shape d2target.Shape) (string, error) { return output, nil } +func DoubleOval(r *Runner, shape d2target.Shape) (string, error) { + jsBigCircle := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + jsSmallCircle := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width/2, shape.Height/2, shape.Width-d2target.INNER_BORDER_OFFSET*2, shape.Height-d2target.INNER_BORDER_OFFSET*2, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + pathsBigCircle, err := computeRoughPathData(r, jsBigCircle) + if err != nil { + return "", err + } + pathsSmallCircle, err := computeRoughPathData(r, jsSmallCircle) + if err != nil { + return "", err + } + output := "" + for _, p := range pathsBigCircle { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shape.CSSStyle(), + ) + } + for _, p := range pathsSmallCircle { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shape.CSSStyle(), + ) + } + output += fmt.Sprintf( + ``, + shape.Pos.X+shape.Width/2, shape.Pos.Y+shape.Height/2, shape.Width/2, shape.Height/2, + ) + return output, nil +} + // TODO need to personalize this per shape like we do in Terrastruct app func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) { output := "" diff --git a/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg index 07980441b..f0a8aabf2 100644 --- a/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg +++ b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg @@ -51,7 +51,7 @@ width="1597" height="835" viewBox="-102 -102 1597 835"> \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json index e37d0a2b3..bdd6a327a 100644 --- a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json @@ -20,6 +20,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": { @@ -70,6 +71,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": { diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg index 6f53328fa..b7128f39d 100644 --- a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg @@ -39,7 +39,7 @@ width="452" height="332" viewBox="-90 -90 452 332"> \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_loop_panic/dagre/board.exp.json b/e2etests/testdata/regression/elk_loop_panic/dagre/board.exp.json index ca5d11974..33dc2e5da 100644 --- a/e2etests/testdata/regression/elk_loop_panic/dagre/board.exp.json +++ b/e2etests/testdata/regression/elk_loop_panic/dagre/board.exp.json @@ -20,6 +20,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -60,6 +61,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -100,6 +102,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, diff --git a/e2etests/testdata/regression/elk_loop_panic/dagre/sketch.exp.svg b/e2etests/testdata/regression/elk_loop_panic/dagre/sketch.exp.svg index 48ca96d52..c66e878a7 100644 --- a/e2etests/testdata/regression/elk_loop_panic/dagre/sketch.exp.svg +++ b/e2etests/testdata/regression/elk_loop_panic/dagre/sketch.exp.svg @@ -39,7 +39,7 @@ width="630" height="430" viewBox="-102 -102 630 430">x

linux: because a PC is a terrible thing to waste

-
a You don't have to know how the computer works,just how to work the computer. +a You don't have to know how the computer works,just how to work the computer. x

linux: because a PC is a terrible thing to waste

-
a You don't have to know how the computer works,just how to work the computer. +a You don't have to know how the computer works,just how to work the computer. aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 +aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 diff --git a/e2etests/testdata/stable/chaos2/elk/board.exp.json b/e2etests/testdata/stable/chaos2/elk/board.exp.json index 4c3490841..2ea0a10ba 100644 --- a/e2etests/testdata/stable/chaos2/elk/board.exp.json +++ b/e2etests/testdata/stable/chaos2/elk/board.exp.json @@ -20,6 +20,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -60,6 +61,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -100,6 +102,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -140,6 +143,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -180,6 +184,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -219,6 +224,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -259,6 +265,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -298,6 +305,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -338,6 +346,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -378,6 +387,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -418,6 +428,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -458,6 +469,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -498,6 +510,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -538,6 +551,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -577,6 +591,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, diff --git a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg index e1498a328..d38284143 100644 --- a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg @@ -796,7 +796,7 @@ width="1275" height="2738" viewBox="-90 -90 1275 2738">aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 +aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 diff --git a/e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json b/e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json index 29604db23..297f00d56 100644 --- a/e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json +++ b/e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json @@ -20,6 +20,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -60,6 +61,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -100,6 +102,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, @@ -140,6 +143,7 @@ "shadow": false, "3d": false, "multiple": false, + "double-border": false, "tooltip": "", "link": "", "icon": null, diff --git a/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg b/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg index 7bc1e8325..f23abb2ed 100644 --- a/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg @@ -39,7 +39,7 @@ width="698" height="630" viewBox="-102 -102 698 630">xyThe top of the mountain

Cats, no less liquid than their shadows, offer no angles to the wind.

If we can't fix it, it ain't broke.

Dieters live life in the fasting lane.

-
JoeDonaldi am top lefti am top righti am bottom lefti am bottom right +JoeDonaldi am top lefti am top righti am bottom lefti am bottom right xyThe top of the mountain

Cats, no less liquid than their shadows, offer no angles to the wind.

If we can't fix it, it ain't broke.

Dieters live life in the fasting lane.

-
JoeDonaldi am top lefti am top righti am bottom lefti am bottom right +JoeDonaldi am top lefti am top righti am bottom lefti am bottom right poll the peopleresultsunfavorablefavorablewill of the people

A winning strategy

-
+ poll the peopleresultsunfavorablefavorablewill of the people

A winning strategy

-
+ mixed togethersugarsolution we get +mixed togethersugarsolution we get mixed togethersugarsolution we get +mixed togethersugarsolution we get

Markdown: Syntax

-
ab +ab

Markdown: Syntax

-
ab +ab markdown

Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
+ markdown

Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
+ markdown

Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
+ markdown

Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

-
+

code

-
ab +ab

code

-
ab +ab bearmama bearpapa bear +bearmama bearpapa bear bearmama bearpapa bear +bearmama bearpapa bear