implement sketch
This commit is contained in:
parent
0e4a8d450d
commit
07fb1a3d86
215 changed files with 3762 additions and 64 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
#### Features 🚀
|
#### Features 🚀
|
||||||
|
|
||||||
|
- `sketch` flag renders the diagram to look like it was sketched by hand. [#492](https://github.com/terrastruct/d2/pull/492)
|
||||||
|
|
||||||
#### Improvements 🧹
|
#### Improvements 🧹
|
||||||
|
|
||||||
- Improved label placements for shapes with images to avoid overlapping container labels. [#474](https://github.com/terrastruct/d2/pull/474)
|
- Improved label placements for shapes with images to avoid overlapping container labels. [#474](https://github.com/terrastruct/d2/pull/474)
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ Port listening address when used with
|
||||||
Set the diagram theme to the passed integer. For a list of available options, see
|
Set the diagram theme to the passed integer. For a list of available options, see
|
||||||
.Lk https://oss.terrastruct.com/d2
|
.Lk https://oss.terrastruct.com/d2
|
||||||
.Ns .
|
.Ns .
|
||||||
|
.It Fl s , -sketch Ar false
|
||||||
|
Renders the diagram to look like it was sketched by hand
|
||||||
|
.Ns .
|
||||||
.It Fl -pad Ar 100
|
.It Fl -pad Ar 100
|
||||||
Pixels padded around the rendered diagram
|
Pixels padded around the rendered diagram
|
||||||
.Ns .
|
.Ns .
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ func test(t *testing.T, textPath, text string) {
|
||||||
ruler, err := textmeasure.NewRuler()
|
ruler, err := textmeasure.NewRuler()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = g.SetDimensions(nil, ruler)
|
err = g.SetDimensions(nil, ruler, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = d2dagrelayout.Layout(ctx, g)
|
err = d2dagrelayout.Layout(ctx, g)
|
||||||
|
|
@ -128,7 +128,7 @@ func test(t *testing.T, textPath, text string) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = d2exporter.Export(ctx, g, 0)
|
_, err = d2exporter.Export(ctx, g, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,21 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Export(ctx context.Context, g *d2graph.Graph, themeID int64) (*d2target.Diagram, error) {
|
func Export(ctx context.Context, g *d2graph.Graph, themeID int64, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) {
|
||||||
theme := d2themescatalog.Find(themeID)
|
theme := d2themescatalog.Find(themeID)
|
||||||
|
|
||||||
diagram := d2target.NewDiagram()
|
diagram := d2target.NewDiagram()
|
||||||
|
if fontFamily == nil {
|
||||||
|
defaultFont := d2fonts.SourceSansPro
|
||||||
|
fontFamily = &defaultFont
|
||||||
|
}
|
||||||
|
diagram.FontFamily = fontFamily
|
||||||
|
|
||||||
diagram.Shapes = make([]d2target.Shape, len(g.Objects))
|
diagram.Shapes = make([]d2target.Shape, len(g.Objects))
|
||||||
for i := range g.Objects {
|
for i := range g.Objects {
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ func run(t *testing.T, tc testCase) {
|
||||||
ruler, err := textmeasure.NewRuler()
|
ruler, err := textmeasure.NewRuler()
|
||||||
assert.JSON(t, nil, err)
|
assert.JSON(t, nil, err)
|
||||||
|
|
||||||
err = g.SetDimensions(nil, ruler)
|
err = g.SetDimensions(nil, ruler, nil)
|
||||||
assert.JSON(t, nil, err)
|
assert.JSON(t, nil, err)
|
||||||
|
|
||||||
err = d2dagrelayout.Layout(ctx, g)
|
err = d2dagrelayout.Layout(ctx, g)
|
||||||
|
|
@ -224,7 +224,7 @@ func run(t *testing.T, tc testCase) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := d2exporter.Export(ctx, g, tc.themeID)
|
got, err := d2exporter.Export(ctx, g, tc.themeID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -841,7 +841,7 @@ func getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t
|
||||||
return nil, fmt.Errorf("text not pre-measured and no ruler provided")
|
return nil, fmt.Errorf("text not pre-measured and no ruler provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText) *d2target.TextDimensions {
|
func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily) *d2target.TextDimensions {
|
||||||
if dims := findMeasured(mtexts, t); dims != nil {
|
if dims := findMeasured(mtexts, t); dims != nil {
|
||||||
return dims
|
return dims
|
||||||
}
|
}
|
||||||
|
|
@ -861,7 +861,11 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2
|
||||||
} else if t.IsItalic {
|
} else if t.IsItalic {
|
||||||
style = d2fonts.FONT_STYLE_ITALIC
|
style = d2fonts.FONT_STYLE_ITALIC
|
||||||
}
|
}
|
||||||
w, h = ruler.Measure(d2fonts.SourceSansPro.Font(t.FontSize, style), t.Text)
|
if fontFamily == nil {
|
||||||
|
defaultFont := d2fonts.SourceSansPro
|
||||||
|
fontFamily = &defaultFont
|
||||||
|
}
|
||||||
|
w, h = ruler.Measure(fontFamily.Font(t.FontSize, style), t.Text)
|
||||||
}
|
}
|
||||||
return d2target.NewTextDimensions(w, h)
|
return d2target.NewTextDimensions(w, h)
|
||||||
}
|
}
|
||||||
|
|
@ -870,13 +874,13 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendTextDedup(texts []*d2target.MText, t *d2target.MText) []*d2target.MText {
|
func appendTextDedup(texts []*d2target.MText, t *d2target.MText) []*d2target.MText {
|
||||||
if getTextDimensions(texts, nil, t) == nil {
|
if getTextDimensions(texts, nil, t, nil) == nil {
|
||||||
return append(texts, t)
|
return append(texts, t)
|
||||||
}
|
}
|
||||||
return texts
|
return texts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler) error {
|
func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, fontFamily *d2fonts.FontFamily) error {
|
||||||
for _, obj := range g.Objects {
|
for _, obj := range g.Objects {
|
||||||
obj.Box = &geo.Box{}
|
obj.Box = &geo.Box{}
|
||||||
// TODO fix edge cases for unnamed class etc
|
// TODO fix edge cases for unnamed class etc
|
||||||
|
|
@ -905,7 +909,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
}
|
}
|
||||||
innerLabelPadding = 0
|
innerLabelPadding = 0
|
||||||
} else {
|
} else {
|
||||||
dims = getTextDimensions(mtexts, ruler, obj.Text())
|
dims = getTextDimensions(mtexts, ruler, obj.Text(), fontFamily)
|
||||||
}
|
}
|
||||||
if dims == nil {
|
if dims == nil {
|
||||||
if obj.Attributes.Shape.Value == d2target.ShapeImage {
|
if obj.Attributes.Shape.Value == d2target.ShapeImage {
|
||||||
|
|
@ -959,7 +963,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
maxWidth := dims.Width
|
maxWidth := dims.Width
|
||||||
|
|
||||||
for _, f := range obj.Class.Fields {
|
for _, f := range obj.Class.Fields {
|
||||||
fdims := getTextDimensions(mtexts, ruler, f.Text())
|
fdims := getTextDimensions(mtexts, ruler, f.Text(), fontFamily)
|
||||||
if fdims == nil {
|
if fdims == nil {
|
||||||
return fmt.Errorf("dimensions for class field %#v not found", f.Text())
|
return fmt.Errorf("dimensions for class field %#v not found", f.Text())
|
||||||
}
|
}
|
||||||
|
|
@ -969,7 +973,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, m := range obj.Class.Methods {
|
for _, m := range obj.Class.Methods {
|
||||||
mdims := getTextDimensions(mtexts, ruler, m.Text())
|
mdims := getTextDimensions(mtexts, ruler, m.Text(), fontFamily)
|
||||||
if mdims == nil {
|
if mdims == nil {
|
||||||
return fmt.Errorf("dimensions for class method %#v not found", m.Text())
|
return fmt.Errorf("dimensions for class method %#v not found", m.Text())
|
||||||
}
|
}
|
||||||
|
|
@ -988,7 +992,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
}
|
}
|
||||||
if anyRowText != nil {
|
if anyRowText != nil {
|
||||||
// 10px of padding top and bottom so text doesn't look squished
|
// 10px of padding top and bottom so text doesn't look squished
|
||||||
rowHeight := getTextDimensions(mtexts, ruler, anyRowText).Height + 20
|
rowHeight := getTextDimensions(mtexts, ruler, anyRowText, fontFamily).Height + 20
|
||||||
obj.Height = float64(rowHeight * (len(obj.Class.Fields) + len(obj.Class.Methods) + 2))
|
obj.Height = float64(rowHeight * (len(obj.Class.Fields) + len(obj.Class.Methods) + 2))
|
||||||
}
|
}
|
||||||
// Leave room for padding
|
// Leave room for padding
|
||||||
|
|
@ -1043,7 +1047,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
for _, label := range endpointLabels {
|
for _, label := range endpointLabels {
|
||||||
t := edge.Text()
|
t := edge.Text()
|
||||||
t.Text = label
|
t.Text = label
|
||||||
dims := getTextDimensions(mtexts, ruler, t)
|
dims := getTextDimensions(mtexts, ruler, t, fontFamily)
|
||||||
edge.MinWidth += dims.Width
|
edge.MinWidth += dims.Width
|
||||||
// Some padding as it's not totally near the end
|
// Some padding as it's not totally near the end
|
||||||
edge.MinHeight += dims.Height + 5
|
edge.MinHeight += dims.Height + 5
|
||||||
|
|
@ -1053,7 +1057,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dims := getTextDimensions(mtexts, ruler, edge.Text())
|
dims := getTextDimensions(mtexts, ruler, edge.Text(), fontFamily)
|
||||||
if dims == nil {
|
if dims == nil {
|
||||||
return fmt.Errorf("dimensions for edge label %#v not found", edge.Text())
|
return fmt.Errorf("dimensions for edge label %#v not found", edge.Text())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
d2lib/d2.go
13
d2lib/d2.go
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2exporter"
|
"oss.terrastruct.com/d2/d2exporter"
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
)
|
)
|
||||||
|
|
@ -20,7 +21,13 @@ type CompileOptions struct {
|
||||||
Ruler *textmeasure.Ruler
|
Ruler *textmeasure.Ruler
|
||||||
Layout func(context.Context, *d2graph.Graph) error
|
Layout func(context.Context, *d2graph.Graph) error
|
||||||
|
|
||||||
ThemeID int64
|
// FontFamily controls the font family used for all texts that are not the following:
|
||||||
|
// - code
|
||||||
|
// - latex
|
||||||
|
// - pre-measured (web setting)
|
||||||
|
// TODO maybe some will want to configure code font too, but that's much lower priority
|
||||||
|
FontFamily *d2fonts.FontFamily
|
||||||
|
ThemeID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
|
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
|
||||||
|
|
@ -36,7 +43,7 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(g.Objects) > 0 {
|
if len(g.Objects) > 0 {
|
||||||
err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler)
|
err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler, opts.FontFamily)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +55,7 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diagram, err := d2exporter.Export(ctx, g, opts.ThemeID)
|
diagram, err := d2exporter.Export(ctx, g, opts.ThemeID, opts.FontFamily)
|
||||||
return diagram, g, err
|
return diagram, g, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// d2fonts holds fonts for renderings
|
// d2fonts holds fonts for renderings
|
||||||
|
|
||||||
// TODO write a script to do this as part of CI
|
// TODO write a script to do this as part of CI
|
||||||
|
// Currently using an online converter: https://dopiaza.org/tools/datauri/index.php
|
||||||
package d2fonts
|
package d2fonts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -8,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FontFamily int
|
type FontFamily string
|
||||||
type FontStyle string
|
type FontStyle string
|
||||||
|
|
||||||
type Font struct {
|
type Font struct {
|
||||||
|
|
@ -38,8 +39,9 @@ const (
|
||||||
FONT_STYLE_BOLD FontStyle = "bold"
|
FONT_STYLE_BOLD FontStyle = "bold"
|
||||||
FONT_STYLE_ITALIC FontStyle = "italic"
|
FONT_STYLE_ITALIC FontStyle = "italic"
|
||||||
|
|
||||||
SourceSansPro FontFamily = iota
|
SourceSansPro FontFamily = "SourceSansPro"
|
||||||
SourceCodePro FontFamily = iota
|
SourceCodePro FontFamily = "SourceCodePro"
|
||||||
|
HandDrawn FontFamily = "HandDrawn"
|
||||||
)
|
)
|
||||||
|
|
||||||
var FontSizes = []int{
|
var FontSizes = []int{
|
||||||
|
|
@ -61,6 +63,7 @@ var FontStyles = []FontStyle{
|
||||||
var FontFamilies = []FontFamily{
|
var FontFamilies = []FontFamily{
|
||||||
SourceSansPro,
|
SourceSansPro,
|
||||||
SourceCodePro,
|
SourceCodePro,
|
||||||
|
HandDrawn,
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed encoded/SourceSansPro-Regular.txt
|
//go:embed encoded/SourceSansPro-Regular.txt
|
||||||
|
|
@ -75,6 +78,12 @@ var sourceSansProItalicBase64 string
|
||||||
//go:embed encoded/SourceCodePro-Regular.txt
|
//go:embed encoded/SourceCodePro-Regular.txt
|
||||||
var sourceCodeProRegularBase64 string
|
var sourceCodeProRegularBase64 string
|
||||||
|
|
||||||
|
//go:embed encoded/ArchitectsDaughter-Regular.txt
|
||||||
|
var architectsDaughterRegularBase64 string
|
||||||
|
|
||||||
|
//go:embed encoded/FuzzyBubbles-Bold.txt
|
||||||
|
var fuzzyBubblesBoldBase64 string
|
||||||
|
|
||||||
//go:embed ttf/*
|
//go:embed ttf/*
|
||||||
var fontFacesFS embed.FS
|
var fontFacesFS embed.FS
|
||||||
|
|
||||||
|
|
@ -99,6 +108,19 @@ func init() {
|
||||||
Family: SourceCodePro,
|
Family: SourceCodePro,
|
||||||
Style: FONT_STYLE_REGULAR,
|
Style: FONT_STYLE_REGULAR,
|
||||||
}: sourceCodeProRegularBase64,
|
}: sourceCodeProRegularBase64,
|
||||||
|
{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_REGULAR,
|
||||||
|
}: architectsDaughterRegularBase64,
|
||||||
|
{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_ITALIC,
|
||||||
|
// This font has no italic, so just reuse regular
|
||||||
|
}: architectsDaughterRegularBase64,
|
||||||
|
{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_BOLD,
|
||||||
|
}: fuzzyBubblesBoldBase64,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range FontEncodings {
|
for k, v := range FontEncodings {
|
||||||
|
|
@ -138,4 +160,24 @@ func init() {
|
||||||
Family: SourceSansPro,
|
Family: SourceSansPro,
|
||||||
Style: FONT_STYLE_ITALIC,
|
Style: FONT_STYLE_ITALIC,
|
||||||
}] = b
|
}] = b
|
||||||
|
b, err = fontFacesFS.ReadFile("ttf/ArchitectsDaughter-Regular.ttf")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
FontFaces[Font{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_REGULAR,
|
||||||
|
}] = b
|
||||||
|
FontFaces[Font{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_ITALIC,
|
||||||
|
}] = b
|
||||||
|
b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
FontFaces[Font{
|
||||||
|
Family: HandDrawn,
|
||||||
|
Style: FONT_STYLE_BOLD,
|
||||||
|
}] = b
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
1
d2renderers/d2fonts/encoded/FuzzyBubbles-Bold.txt
Normal file
1
d2renderers/d2fonts/encoded/FuzzyBubbles-Bold.txt
Normal file
File diff suppressed because one or more lines are too long
BIN
d2renderers/d2fonts/ttf/ArchitectsDaughter-Regular.ttf
Normal file
BIN
d2renderers/d2fonts/ttf/ArchitectsDaughter-Regular.ttf
Normal file
Binary file not shown.
BIN
d2renderers/d2fonts/ttf/FuzzyBubbles-Bold.ttf
Normal file
BIN
d2renderers/d2fonts/ttf/FuzzyBubbles-Bold.ttf
Normal file
Binary file not shown.
1
d2renderers/d2sketch/fillpattern.svg
Normal file
1
d2renderers/d2sketch/fillpattern.svg
Normal file
File diff suppressed because one or more lines are too long
1673
d2renderers/d2sketch/rough.js
Normal file
1673
d2renderers/d2sketch/rough.js
Normal file
File diff suppressed because it is too large
Load diff
19
d2renderers/d2sketch/setup.js
Normal file
19
d2renderers/d2sketch/setup.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const root = {
|
||||||
|
ownerDocument: {
|
||||||
|
createElementNS: (ns, tagName) => {
|
||||||
|
const children = [];
|
||||||
|
const attrs = {};
|
||||||
|
const style = {};
|
||||||
|
return {
|
||||||
|
style,
|
||||||
|
tagName,
|
||||||
|
attrs,
|
||||||
|
setAttribute: (key, value) => (attrs[key] = value),
|
||||||
|
appendChild: (node) => children.push(node),
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rc = rough.svg(root, { seed: 1 });
|
||||||
|
let node;
|
||||||
227
d2renderers/d2sketch/sketch.go
Normal file
227
d2renderers/d2sketch/sketch.go
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
package d2sketch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/d2/d2target"
|
||||||
|
"oss.terrastruct.com/d2/lib/svg"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed fillpattern.svg
|
||||||
|
var fillPattern string
|
||||||
|
|
||||||
|
//go:embed rough.js
|
||||||
|
var roughJS string
|
||||||
|
|
||||||
|
//go:embed setup.js
|
||||||
|
var setupJS string
|
||||||
|
|
||||||
|
type Runner goja.Runtime
|
||||||
|
|
||||||
|
var baseRoughProps = `fillWeight: 2.0,
|
||||||
|
hachureGap: 16,
|
||||||
|
fillStyle: "solid",
|
||||||
|
bowing: 2,
|
||||||
|
seed: 1,`
|
||||||
|
|
||||||
|
func (r *Runner) run(js string) (goja.Value, error) {
|
||||||
|
vm := (*goja.Runtime)(r)
|
||||||
|
return vm.RunString(js)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitSketchVM() (*Runner, error) {
|
||||||
|
vm := goja.New()
|
||||||
|
if _, err := vm.RunString(roughJS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := vm.RunString(setupJS); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := Runner(*vm)
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefineFillPattern adds a reusable pattern that is overlayed on shapes with
|
||||||
|
// fill. This gives it a subtle streaky effect that subtly looks hand-drawn but
|
||||||
|
// not distractingly so.
|
||||||
|
func DefineFillPattern() string {
|
||||||
|
return fmt.Sprintf(`<defs>
|
||||||
|
<pattern id="streaks"
|
||||||
|
x="0" y="0" width="100" height="100"
|
||||||
|
patternUnits="userSpaceOnUse" >
|
||||||
|
%s
|
||||||
|
</pattern>
|
||||||
|
</defs>`, fillPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shapeStyle(shape d2target.Shape) string {
|
||||||
|
out := ""
|
||||||
|
|
||||||
|
out += fmt.Sprintf(`fill:%s;`, shape.Fill)
|
||||||
|
out += fmt.Sprintf(`stroke:%s;`, shape.Stroke)
|
||||||
|
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||||
|
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||||
|
if shape.StrokeDash != 0 {
|
||||||
|
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||||
|
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rect(r *Runner, shape d2target.Shape) (string, error) {
|
||||||
|
js := 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)
|
||||||
|
if _, err := r.run(js); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
paths, err := extractPaths(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output := ""
|
||||||
|
for _, p := range paths {
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||||
|
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<rect class="sketch-overlay" transform="translate(%d %d)" width="%d" height="%d" />`,
|
||||||
|
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",
|
||||||
|
stroke: "%s",
|
||||||
|
strokeWidth: %d,
|
||||||
|
%s
|
||||||
|
});`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||||
|
if _, err := r.run(js); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
paths, err := extractPaths(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output := ""
|
||||||
|
for _, p := range paths {
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<path class="shape" transform="translate(%d %d)" d="%s" style="%s" />`,
|
||||||
|
shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<ellipse class="sketch-overlay" transform="translate(%d %d)" rx="%d" ry="%d" />`,
|
||||||
|
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 := ""
|
||||||
|
for _, path := range paths {
|
||||||
|
js := fmt.Sprintf(`node = rc.path("%s", {
|
||||||
|
fill: "%s",
|
||||||
|
stroke: "%s",
|
||||||
|
strokeWidth: %d,
|
||||||
|
%s
|
||||||
|
});`, path, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps)
|
||||||
|
if _, err := r.run(js); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
sketchPaths, err := extractPaths(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, p := range sketchPaths {
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<path class="shape" d="%s" style="%s" />`,
|
||||||
|
p, shapeStyle(shape),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, p := range sketchPaths {
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<path class="sketch-overlay" d="%s" />`,
|
||||||
|
p,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectionStyle(connection d2target.Connection) string {
|
||||||
|
out := ""
|
||||||
|
|
||||||
|
out += fmt.Sprintf(`stroke:%s;`, connection.Stroke)
|
||||||
|
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||||
|
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||||
|
if connection.StrokeDash != 0 {
|
||||||
|
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||||
|
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
|
||||||
|
roughness := 1.0
|
||||||
|
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
|
||||||
|
if _, err := r.run(js); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
paths, err := extractPaths(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output := ""
|
||||||
|
for _, p := range paths {
|
||||||
|
output += fmt.Sprintf(
|
||||||
|
`<path class="connection" fill="none" d="%s" style="%s" %s/>`,
|
||||||
|
p, connectionStyle(connection), attrs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type attrs struct {
|
||||||
|
D string `json:"d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
Attrs attrs `json:"attrs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPaths(r *Runner) ([]string, error) {
|
||||||
|
val, err := r.run("JSON.stringify(node.children)")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodes []node
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(val.String()), &nodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
for _, n := range nodes {
|
||||||
|
paths = append(paths, n.Attrs.D)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
292
d2renderers/d2sketch/sketch_test.go
Normal file
292
d2renderers/d2sketch/sketch_test.go
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
package d2sketch_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"cdr.dev/slog"
|
||||||
|
|
||||||
|
tassert "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/util-go/assert"
|
||||||
|
"oss.terrastruct.com/util-go/diff"
|
||||||
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
|
||||||
|
"oss.terrastruct.com/d2/d2lib"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2svg"
|
||||||
|
"oss.terrastruct.com/d2/lib/log"
|
||||||
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSketch(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tcs := []testCase{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
script: `a -> b
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "child to child",
|
||||||
|
script: `winter.snow -> summer.sun
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "connection label",
|
||||||
|
script: `a -> b: hello
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chess",
|
||||||
|
script: `timeline mixer: "" {
|
||||||
|
explanation: |md
|
||||||
|
## **Timeline mixer**
|
||||||
|
- Inject ads, who-to-follow, onboarding
|
||||||
|
- Conversation module
|
||||||
|
- Cursoring,pagination
|
||||||
|
- Tweat deduplication
|
||||||
|
- Served data logging
|
||||||
|
|
|
||||||
|
}
|
||||||
|
People discovery: "People discovery \nservice"
|
||||||
|
admixer: Ad mixer {
|
||||||
|
fill: "#c1a2f3"
|
||||||
|
}
|
||||||
|
|
||||||
|
onboarding service: "Onboarding \nservice"
|
||||||
|
timeline mixer -> People discovery
|
||||||
|
timeline mixer -> onboarding service
|
||||||
|
timeline mixer -> admixer
|
||||||
|
container0: "" {
|
||||||
|
graphql
|
||||||
|
comment
|
||||||
|
tlsapi
|
||||||
|
}
|
||||||
|
container0.graphql: GraphQL\nFederated Strato Column {
|
||||||
|
shape: image
|
||||||
|
icon: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1200px-GraphQL_Logo.svg.png
|
||||||
|
}
|
||||||
|
container0.comment: |md
|
||||||
|
## Tweet/user content hydration, visibility filtering
|
||||||
|
|
|
||||||
|
container0.tlsapi: TLS-API (being deprecated)
|
||||||
|
container0.graphql -> timeline mixer
|
||||||
|
timeline mixer <- container0.tlsapi
|
||||||
|
twitter fe: "Twitter Frontend " {
|
||||||
|
icon: https://icons.terrastruct.com/social/013-twitter-1.svg
|
||||||
|
shape: image
|
||||||
|
}
|
||||||
|
twitter fe -> container0.graphql: iPhone web
|
||||||
|
twitter fe -> container0.tlsapi: HTTP Android
|
||||||
|
web: Web {
|
||||||
|
icon: https://icons.terrastruct.com/azure/Web%20Service%20Color/App%20Service%20Domains.svg
|
||||||
|
shape: image
|
||||||
|
}
|
||||||
|
|
||||||
|
Iphone: {
|
||||||
|
icon: 'https://ss7.vzw.com/is/image/VerizonWireless/apple-iphone-12-64gb-purple-53017-mjn13ll-a?$device-lg$'
|
||||||
|
shape: image
|
||||||
|
}
|
||||||
|
Android: {
|
||||||
|
icon: https://cdn4.iconfinder.com/data/icons/smart-phones-technologies/512/android-phone.png
|
||||||
|
shape: image
|
||||||
|
}
|
||||||
|
|
||||||
|
web -> twitter fe
|
||||||
|
timeline scorer: "Timeline\nScorer" {
|
||||||
|
fill: "#ffdef1"
|
||||||
|
}
|
||||||
|
home ranker: Home Ranker
|
||||||
|
|
||||||
|
timeline service: Timeline Service
|
||||||
|
timeline mixer -> timeline scorer: Thrift RPC
|
||||||
|
timeline mixer -> home ranker: {
|
||||||
|
style.stroke-dash: 4
|
||||||
|
style.stroke: "#000E3D"
|
||||||
|
}
|
||||||
|
timeline mixer -> timeline service
|
||||||
|
home mixer: Home mixer {
|
||||||
|
# fill: "#c1a2f3"
|
||||||
|
}
|
||||||
|
container0.graphql -> home mixer: {
|
||||||
|
style.stroke-dash: 4
|
||||||
|
style.stroke: "#000E3D"
|
||||||
|
}
|
||||||
|
home mixer -> timeline scorer
|
||||||
|
home mixer -> home ranker: {
|
||||||
|
style.stroke-dash: 4
|
||||||
|
style.stroke: "#000E3D"
|
||||||
|
}
|
||||||
|
home mixer -> timeline service
|
||||||
|
manhattan 2: Manhattan
|
||||||
|
gizmoduck: Gizmoduck
|
||||||
|
socialgraph: Social graph
|
||||||
|
tweetypie: Tweety Pie
|
||||||
|
home mixer -> manhattan 2
|
||||||
|
home mixer -> gizmoduck
|
||||||
|
home mixer -> socialgraph
|
||||||
|
home mixer -> tweetypie
|
||||||
|
Iphone -> twitter fe
|
||||||
|
Android -> twitter fe
|
||||||
|
prediction service2: Prediction Service {
|
||||||
|
shape: image
|
||||||
|
icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png
|
||||||
|
}
|
||||||
|
home scorer: Home Scorer {
|
||||||
|
fill: "#ffdef1"
|
||||||
|
}
|
||||||
|
manhattan: Manhattan
|
||||||
|
memcache: Memcache {
|
||||||
|
icon: https://d1q6f0aelx0por.cloudfront.net/product-logos/de041504-0ddb-43f6-b89e-fe04403cca8d-memcached.png
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch: Fetch {
|
||||||
|
multiple: true
|
||||||
|
shape: step
|
||||||
|
}
|
||||||
|
feature: Feature {
|
||||||
|
multiple: true
|
||||||
|
shape: step
|
||||||
|
}
|
||||||
|
scoring: Scoring {
|
||||||
|
multiple: true
|
||||||
|
shape: step
|
||||||
|
}
|
||||||
|
fetch -> feature
|
||||||
|
feature -> scoring
|
||||||
|
|
||||||
|
prediction service: Prediction Service {
|
||||||
|
shape: image
|
||||||
|
icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png
|
||||||
|
}
|
||||||
|
scoring -> prediction service
|
||||||
|
fetch -> container2.crmixer
|
||||||
|
|
||||||
|
home scorer -> manhattan: ""
|
||||||
|
|
||||||
|
home scorer -> memcache: ""
|
||||||
|
home scorer -> prediction service2
|
||||||
|
home ranker -> home scorer
|
||||||
|
home ranker -> container2.crmixer: Candidate Fetch
|
||||||
|
container2: "" {
|
||||||
|
style.stroke: "#000E3D"
|
||||||
|
style.fill: "#ffffff"
|
||||||
|
crmixer: CrMixer {
|
||||||
|
style.fill: "#F7F8FE"
|
||||||
|
}
|
||||||
|
earlybird: EarlyBird
|
||||||
|
utag: Utag
|
||||||
|
space: Space
|
||||||
|
communities: Communities
|
||||||
|
}
|
||||||
|
etc: ...etc
|
||||||
|
|
||||||
|
home scorer -> etc: Feature Hydration
|
||||||
|
|
||||||
|
feature -> manhattan
|
||||||
|
feature -> memcache
|
||||||
|
feature -> etc: Candidate sources
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all_shapes",
|
||||||
|
script: `
|
||||||
|
rectangle: {shape: "rectangle"}
|
||||||
|
square: {shape: "square"}
|
||||||
|
page: {shape: "page"}
|
||||||
|
parallelogram: {shape: "parallelogram"}
|
||||||
|
document: {shape: "document"}
|
||||||
|
cylinder: {shape: "cylinder"}
|
||||||
|
queue: {shape: "queue"}
|
||||||
|
package: {shape: "package"}
|
||||||
|
step: {shape: "step"}
|
||||||
|
callout: {shape: "callout"}
|
||||||
|
stored_data: {shape: "stored_data"}
|
||||||
|
person: {shape: "person"}
|
||||||
|
diamond: {shape: "diamond"}
|
||||||
|
oval: {shape: "oval"}
|
||||||
|
circle: {shape: "circle"}
|
||||||
|
hexagon: {shape: "hexagon"}
|
||||||
|
cloud: {shape: "cloud"}
|
||||||
|
|
||||||
|
rectangle -> square -> page
|
||||||
|
parallelogram -> document -> cylinder
|
||||||
|
queue -> package -> step
|
||||||
|
callout -> stored_data -> person
|
||||||
|
diamond -> oval -> circle
|
||||||
|
hexagon -> cloud
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runa(t, tcs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
script string
|
||||||
|
skip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runa(t *testing.T, tcs []testCase) {
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.skip {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
run(t, tc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(t *testing.T, tc testCase) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = log.WithTB(ctx, t, nil)
|
||||||
|
ctx = log.Leveled(ctx, slog.LevelDebug)
|
||||||
|
|
||||||
|
ruler, err := textmeasure.NewRuler()
|
||||||
|
if !tassert.Nil(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
|
||||||
|
Ruler: ruler,
|
||||||
|
ThemeID: 0,
|
||||||
|
Layout: d2dagrelayout.Layout,
|
||||||
|
FontFamily: go2.Pointer(d2fonts.HandDrawn),
|
||||||
|
})
|
||||||
|
if !tassert.Nil(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestSketch/"))
|
||||||
|
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
||||||
|
|
||||||
|
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||||
|
Pad: d2svg.DEFAULT_PADDING,
|
||||||
|
Sketch: true,
|
||||||
|
})
|
||||||
|
assert.Success(t, err)
|
||||||
|
err = os.MkdirAll(dataPath, 0755)
|
||||||
|
assert.Success(t, err)
|
||||||
|
err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600)
|
||||||
|
assert.Success(t, err)
|
||||||
|
defer os.Remove(pathGotSVG)
|
||||||
|
|
||||||
|
var xmlParsed interface{}
|
||||||
|
err = xml.Unmarshal(svgBytes, &xmlParsed)
|
||||||
|
assert.Success(t, err)
|
||||||
|
|
||||||
|
err = diff.Testdata(filepath.Join(dataPath, "sketch"), ".svg", svgBytes)
|
||||||
|
assert.Success(t, err)
|
||||||
|
}
|
||||||
43
d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
vendored
Normal file
43
d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 298 KiB |
43
d2renderers/d2sketch/testdata/basic/sketch.exp.svg
vendored
Normal file
43
d2renderers/d2sketch/testdata/basic/sketch.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 196 KiB |
827
d2renderers/d2sketch/testdata/chess/sketch.exp.svg
vendored
Normal file
827
d2renderers/d2sketch/testdata/chess/sketch.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 649 KiB |
50
d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg
vendored
Normal file
50
d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 382 KiB |
136
d2renderers/d2sketch/testdata/connection_label/board.exp.json
generated
vendored
Normal file
136
d2renderers/d2sketch/testdata/connection_label/board.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"fontFamily": "HandDrawn",
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"id": "a",
|
||||||
|
"type": "",
|
||||||
|
"pos": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 114,
|
||||||
|
"height": 126,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "#F7F8FE",
|
||||||
|
"stroke": "#0D32B2",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "a",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "#0A0F25",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 14,
|
||||||
|
"labelHeight": 26,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b",
|
||||||
|
"type": "",
|
||||||
|
"pos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 226
|
||||||
|
},
|
||||||
|
"width": 115,
|
||||||
|
"height": 126,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "#F7F8FE",
|
||||||
|
"stroke": "#0D32B2",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "b",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "#0A0F25",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 15,
|
||||||
|
"labelHeight": 26,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "(a -> b)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "none",
|
||||||
|
"srcLabel": "",
|
||||||
|
"dst": "b",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"dstLabel": "",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "#0D32B2",
|
||||||
|
"label": "hello",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "#676C7E",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 31,
|
||||||
|
"labelHeight": 23,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 57.5,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 57.5,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 57.5,
|
||||||
|
"y": 186
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 57.5,
|
||||||
|
"y": 226
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg
vendored
Normal file
50
d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 378 KiB |
|
|
@ -25,11 +25,13 @@ import (
|
||||||
"oss.terrastruct.com/d2/d2graph"
|
"oss.terrastruct.com/d2/d2graph"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2sketch"
|
||||||
"oss.terrastruct.com/d2/d2target"
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/lib/color"
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
"oss.terrastruct.com/d2/lib/shape"
|
"oss.terrastruct.com/d2/lib/shape"
|
||||||
|
"oss.terrastruct.com/d2/lib/svg"
|
||||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,9 +46,17 @@ var multipleOffset = geo.NewVector(10, -10)
|
||||||
//go:embed style.css
|
//go:embed style.css
|
||||||
var styleCSS string
|
var styleCSS string
|
||||||
|
|
||||||
|
//go:embed sketchstyle.css
|
||||||
|
var sketchStyleCSS string
|
||||||
|
|
||||||
//go:embed github-markdown.css
|
//go:embed github-markdown.css
|
||||||
var mdCSS string
|
var mdCSS string
|
||||||
|
|
||||||
|
type RenderOpts struct {
|
||||||
|
Pad int
|
||||||
|
Sketch bool
|
||||||
|
}
|
||||||
|
|
||||||
func setViewbox(writer io.Writer, diagram *d2target.Diagram, pad int) (width int, height int) {
|
func setViewbox(writer io.Writer, diagram *d2target.Diagram, pad int) (width int, height int) {
|
||||||
tl, br := diagram.BoundingBox()
|
tl, br := diagram.BoundingBox()
|
||||||
w := br.X - tl.X + pad*2
|
w := br.X - tl.X + pad*2
|
||||||
|
|
@ -346,7 +356,7 @@ func makeLabelMask(labelTL *geo.Point, width, height int) string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape) (labelMask string) {
|
func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, _ error) {
|
||||||
fmt.Fprintf(writer, `<g id="%s">`, escapeText(connection.ID))
|
fmt.Fprintf(writer, `<g id="%s">`, escapeText(connection.ID))
|
||||||
var markerStart string
|
var markerStart string
|
||||||
if connection.SrcArrow != d2target.NoArrowhead {
|
if connection.SrcArrow != d2target.NoArrowhead {
|
||||||
|
|
@ -413,13 +423,22 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(writer, `<path d="%s" class="connection" style="fill:none;%s" %s%smask="url(#%s)"/>`,
|
path := pathData(connection, idToShape)
|
||||||
pathData(connection, idToShape),
|
attrs := fmt.Sprintf(`%s%smask="url(#%s)"`,
|
||||||
connectionStyle(connection),
|
|
||||||
markerStart,
|
markerStart,
|
||||||
markerEnd,
|
markerEnd,
|
||||||
labelMaskID,
|
labelMaskID,
|
||||||
)
|
)
|
||||||
|
if sketchRunner != nil {
|
||||||
|
out, err := d2sketch.Connection(sketchRunner, connection, path, attrs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, out)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(writer, `<path d="%s" class="connection" style="fill:none;%s" %s/>`,
|
||||||
|
path, connectionStyle(connection), attrs)
|
||||||
|
}
|
||||||
|
|
||||||
if connection.Label != "" {
|
if connection.Label != "" {
|
||||||
fontClass := "text"
|
fontClass := "text"
|
||||||
|
|
@ -589,7 +608,7 @@ func render3dRect(targetShape d2target.Shape) string {
|
||||||
return borderMask + mainRect + renderedSides + renderedBorder
|
return borderMask + mainRect + renderedSides + renderedBorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, err error) {
|
func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, err error) {
|
||||||
fmt.Fprintf(writer, `<g id="%s">`, escapeText(targetShape.ID))
|
fmt.Fprintf(writer, `<g id="%s">`, escapeText(targetShape.ID))
|
||||||
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
|
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
|
||||||
width := float64(targetShape.Width)
|
width := float64(targetShape.Width)
|
||||||
|
|
@ -636,7 +655,15 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string,
|
||||||
if targetShape.Multiple {
|
if targetShape.Multiple {
|
||||||
fmt.Fprint(writer, renderOval(multipleTL, width, height, style))
|
fmt.Fprint(writer, renderOval(multipleTL, width, height, style))
|
||||||
}
|
}
|
||||||
fmt.Fprint(writer, renderOval(tl, width, height, style))
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
case d2target.ShapeImage:
|
case d2target.ShapeImage:
|
||||||
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||||
|
|
@ -652,8 +679,16 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string,
|
||||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||||
targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
|
targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
if sketchRunner != nil {
|
||||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
out, err := d2sketch.Rect(sketchRunner, targetShape)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, out)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||||
|
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case d2target.ShapeText, d2target.ShapeCode:
|
case d2target.ShapeText, d2target.ShapeCode:
|
||||||
default:
|
default:
|
||||||
|
|
@ -664,8 +699,16 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pathData := range s.GetSVGPathData() {
|
if sketchRunner != nil {
|
||||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
out, err := d2sketch.Paths(sketchRunner, targetShape, s.GetSVGPathData())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(writer, out)
|
||||||
|
} else {
|
||||||
|
for _, pathData := range s.GetSVGPathData() {
|
||||||
|
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,7 +884,7 @@ func shapeStyle(shape d2target.Shape) string {
|
||||||
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
out += fmt.Sprintf(`opacity:%f;`, shape.Opacity)
|
||||||
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth)
|
||||||
if shape.StrokeDash != 0 {
|
if shape.StrokeDash != 0 {
|
||||||
dashSize, gapSize := getStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash)
|
||||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -855,22 +898,14 @@ func connectionStyle(connection d2target.Connection) string {
|
||||||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||||
if connection.StrokeDash != 0 {
|
if connection.StrokeDash != 0 {
|
||||||
dashSize, gapSize := getStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash)
|
||||||
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStrokeDashAttributes(strokeWidth, dashGapSize float64) (float64, float64) {
|
func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||||
// as the stroke width gets thicker, the dash gap gets smaller
|
|
||||||
scale := math.Log10(-0.6*strokeWidth+10.6)*0.5 + 0.5
|
|
||||||
scaledDashSize := strokeWidth * dashGapSize
|
|
||||||
scaledGapSize := scale * scaledDashSize
|
|
||||||
return scaledDashSize, scaledGapSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func embedFonts(buf *bytes.Buffer) {
|
|
||||||
content := buf.String()
|
content := buf.String()
|
||||||
buf.WriteString(`<style type="text/css"><![CDATA[`)
|
buf.WriteString(`<style type="text/css"><![CDATA[`)
|
||||||
|
|
||||||
|
|
@ -889,7 +924,7 @@ func embedFonts(buf *bytes.Buffer) {
|
||||||
font-family: font-regular;
|
font-family: font-regular;
|
||||||
src: url("%s");
|
src: url("%s");
|
||||||
}`,
|
}`,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -910,7 +945,7 @@ func embedFonts(buf *bytes.Buffer) {
|
||||||
font-family: font-bold;
|
font-family: font-bold;
|
||||||
src: url("%s");
|
src: url("%s");
|
||||||
}`,
|
}`,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -931,7 +966,7 @@ func embedFonts(buf *bytes.Buffer) {
|
||||||
font-family: font-italic;
|
font-family: font-italic;
|
||||||
src: url("%s");
|
src: url("%s");
|
||||||
}`,
|
}`,
|
||||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_ITALIC)])
|
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC)])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -963,15 +998,32 @@ func embedFonts(buf *bytes.Buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO minify output at end
|
// TODO minify output at end
|
||||||
func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
||||||
|
var sketchRunner *d2sketch.Runner
|
||||||
|
pad := DEFAULT_PADDING
|
||||||
|
if opts != nil {
|
||||||
|
pad = opts.Pad
|
||||||
|
if opts.Sketch {
|
||||||
|
var err error
|
||||||
|
sketchRunner, err = d2sketch.InitSketchVM()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
w, h := setViewbox(buf, diagram, pad)
|
w, h := setViewbox(buf, diagram, pad)
|
||||||
|
|
||||||
|
styleCSS2 := ""
|
||||||
|
if sketchRunner != nil {
|
||||||
|
styleCSS2 = "\n" + sketchStyleCSS
|
||||||
|
}
|
||||||
buf.WriteString(fmt.Sprintf(`<style type="text/css">
|
buf.WriteString(fmt.Sprintf(`<style type="text/css">
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
%s
|
%s%s
|
||||||
]]>
|
]]>
|
||||||
</style>`, styleCSS))
|
</style>`, styleCSS, styleCSS2))
|
||||||
|
|
||||||
hasMarkdown := false
|
hasMarkdown := false
|
||||||
for _, s := range diagram.Shapes {
|
for _, s := range diagram.Shapes {
|
||||||
|
|
@ -983,6 +1035,9 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
||||||
if hasMarkdown {
|
if hasMarkdown {
|
||||||
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS)
|
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS)
|
||||||
}
|
}
|
||||||
|
if sketchRunner != nil {
|
||||||
|
fmt.Fprintf(buf, d2sketch.DefineFillPattern())
|
||||||
|
}
|
||||||
|
|
||||||
// only define shadow filter if a shape uses it
|
// only define shadow filter if a shape uses it
|
||||||
for _, s := range diagram.Shapes {
|
for _, s := range diagram.Shapes {
|
||||||
|
|
@ -1017,12 +1072,15 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
||||||
markers := map[string]struct{}{}
|
markers := map[string]struct{}{}
|
||||||
for _, obj := range allObjects {
|
for _, obj := range allObjects {
|
||||||
if c, is := obj.(d2target.Connection); is {
|
if c, is := obj.(d2target.Connection); is {
|
||||||
labelMask := drawConnection(buf, labelMaskID, c, markers, idToShape)
|
labelMask, err := drawConnection(buf, labelMaskID, c, markers, idToShape, sketchRunner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if labelMask != "" {
|
if labelMask != "" {
|
||||||
labelMasks = append(labelMasks, labelMask)
|
labelMasks = append(labelMasks, labelMask)
|
||||||
}
|
}
|
||||||
} else if s, is := obj.(d2target.Shape); is {
|
} else if s, is := obj.(d2target.Shape); is {
|
||||||
labelMask, err := drawShape(buf, s)
|
labelMask, err := drawShape(buf, s, sketchRunner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if labelMask != "" {
|
} else if labelMask != "" {
|
||||||
|
|
@ -1046,7 +1104,7 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
||||||
`</mask>`,
|
`</mask>`,
|
||||||
}, "\n"))
|
}, "\n"))
|
||||||
|
|
||||||
embedFonts(buf)
|
embedFonts(buf, diagram.FontFamily)
|
||||||
|
|
||||||
buf.WriteString(`</svg>`)
|
buf.WriteString(`</svg>`)
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
|
|
|
||||||
4
d2renderers/d2svg/sketchstyle.css
Normal file
4
d2renderers/d2svg/sketchstyle.css
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.sketch-overlay {
|
||||||
|
fill: url(#streaks);
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"oss.terrastruct.com/util-go/go2"
|
"oss.terrastruct.com/util-go/go2"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
"oss.terrastruct.com/d2/lib/label"
|
"oss.terrastruct.com/d2/lib/label"
|
||||||
|
|
@ -22,8 +23,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Diagram struct {
|
type Diagram struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
|
FontFamily *d2fonts.FontFamily `json:"fontFamily,omitempty"`
|
||||||
|
|
||||||
Shapes []Shape `json:"shapes"`
|
Shapes []Shape `json:"shapes"`
|
||||||
Connections []Connection `json:"connections"`
|
Connections []Connection `json:"connections"`
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ func main() {
|
||||||
Ruler: ruler,
|
Ruler: ruler,
|
||||||
ThemeID: d2themescatalog.GrapeSoda.ID,
|
ThemeID: d2themescatalog.GrapeSoda.ID,
|
||||||
})
|
})
|
||||||
out, _ := d2svg.Render(diagram, d2svg.DEFAULT_PADDING)
|
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||||
|
Pad: d2svg.DEFAULT_PADDING,
|
||||||
|
})
|
||||||
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
|
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
|
graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
|
||||||
ruler, _ := textmeasure.NewRuler()
|
ruler, _ := textmeasure.NewRuler()
|
||||||
_ = graph.SetDimensions(nil, ruler)
|
_ = graph.SetDimensions(nil, ruler, nil)
|
||||||
_ = d2dagrelayout.Layout(context.Background(), graph)
|
_ = d2dagrelayout.Layout(context.Background(), graph)
|
||||||
diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID)
|
diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID, nil)
|
||||||
out, _ := d2svg.Render(diagram, d2svg.DEFAULT_PADDING)
|
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||||
|
Pad: d2svg.DEFAULT_PADDING,
|
||||||
|
})
|
||||||
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
|
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,9 @@ func run(t *testing.T, tc testCase) {
|
||||||
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestE2E/"), layoutName)
|
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestE2E/"), layoutName)
|
||||||
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
||||||
|
|
||||||
svgBytes, err := d2svg.Render(diagram, d2svg.DEFAULT_PADDING)
|
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||||
|
Pad: d2svg.DEFAULT_PADDING,
|
||||||
|
})
|
||||||
assert.Success(t, err)
|
assert.Success(t, err)
|
||||||
err = os.MkdirAll(dataPath, 0755)
|
err = os.MkdirAll(dataPath, 0755)
|
||||||
assert.Success(t, err)
|
assert.Success(t, err)
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "\"ninety\\nnine\"",
|
"id": "\"ninety\\nnine\"",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/dagre_special_ids/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/dagre_special_ids/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "\"ninety\\nnine\"",
|
"id": "\"ninety\\nnine\"",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/empty_sequence/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/empty_sequence/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "A",
|
"id": "A",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/empty_sequence/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/empty_sequence/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "A",
|
"id": "A",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_name_crash/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_name_crash/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "foo",
|
"id": "foo",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "foo",
|
"id": "foo",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_no_message/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_no_message/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_no_message/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_no_message/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "b",
|
"id": "b",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sequence_diagram_span_cover/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sequence_diagram_span_cover/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "b",
|
"id": "b",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sql_table_overflow/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sql_table_overflow/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "table",
|
"id": "table",
|
||||||
|
|
|
||||||
1
e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json
generated
vendored
1
e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "table",
|
"id": "table",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/1_to_2/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/1_to_2/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/1_to_2/elk/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/1_to_2/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/basic/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/basic/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/basic/elk/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/basic/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/child_to_child/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/child_to_child/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/child_to_child/elk/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/child_to_child/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/connection_label/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/connection_label/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/connection_label/elk/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/connection_label/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/empty/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/empty/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [],
|
"shapes": [],
|
||||||
"connections": []
|
"connections": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
e2etests/testdata/sanity/empty/elk/board.exp.json
generated
vendored
1
e2etests/testdata/sanity/empty/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [],
|
"shapes": [],
|
||||||
"connections": []
|
"connections": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes_multiple/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes_multiple/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes_multiple/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes_multiple/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes_shadow/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes_shadow/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/all_shapes_shadow/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/all_shapes_shadow/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "rectangle",
|
"id": "rectangle",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/arrowhead_adjustment/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/arrowhead_adjustment/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "c",
|
"id": "c",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/arrowhead_adjustment/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/arrowhead_adjustment/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "c",
|
"id": "c",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/arrowhead_labels/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/arrowhead_labels/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/arrowhead_labels/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/arrowhead_labels/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/binary_tree/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/binary_tree/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/binary_tree/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/binary_tree/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/chaos1/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/chaos1/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aaa",
|
"id": "aaa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/chaos1/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/chaos1/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aaa",
|
"id": "aaa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/chaos2/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/chaos2/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aa",
|
"id": "aa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/chaos2/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/chaos2/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aa",
|
"id": "aa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/child_parent_edges/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/child_parent_edges/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/child_parent_edges/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/circular_dependency/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/circular_dependency/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/circular_dependency/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/circular_dependency/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/class/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/class/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "manager",
|
"id": "manager",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/class/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/class/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "manager",
|
"id": "manager",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/code_snippet/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/code_snippet/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "hey",
|
"id": "hey",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/code_snippet/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/code_snippet/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "hey",
|
"id": "hey",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/connected_container/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/connected_container/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/connected_container/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/connected_container/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/container_edges/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/container_edges/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/container_edges/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/container_edges/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/dense/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/dense/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/dense/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/dense/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/different_subgraphs/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/different_subgraphs/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "finally",
|
"id": "finally",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/different_subgraphs/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/different_subgraphs/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "finally",
|
"id": "finally",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/direction/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/direction/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "b",
|
"id": "b",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/direction/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/direction/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "b",
|
"id": "b",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/font_colors/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/font_colors/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "alpha",
|
"id": "alpha",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/font_colors/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/font_colors/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "alpha",
|
"id": "alpha",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/font_sizes/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/font_sizes/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "size XS",
|
"id": "size XS",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/font_sizes/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/font_sizes/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "size XS",
|
"id": "size XS",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "md",
|
"id": "md",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/giant_markdown_test/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/giant_markdown_test/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "md",
|
"id": "md",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/hr/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/hr/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "md",
|
"id": "md",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/hr/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/hr/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "md",
|
"id": "md",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/icon-label/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/icon-label/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "ww",
|
"id": "ww",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/icon-label/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/icon-label/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "ww",
|
"id": "ww",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/images/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/images/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/images/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/images/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/investigate/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/investigate/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aa",
|
"id": "aa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/investigate/elk/board.exp.json
generated
vendored
1
e2etests/testdata/stable/investigate/elk/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "aa",
|
"id": "aa",
|
||||||
|
|
|
||||||
1
e2etests/testdata/stable/large_arch/dagre/board.exp.json
generated
vendored
1
e2etests/testdata/stable/large_arch/dagre/board.exp.json
generated
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
"fontFamily": "SourceSansPro",
|
||||||
"shapes": [
|
"shapes": [
|
||||||
{
|
{
|
||||||
"id": "a",
|
"id": "a",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue