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 🚀
|
||||
|
||||
- `sketch` flag renders the diagram to look like it was sketched by hand. [#492](https://github.com/terrastruct/d2/pull/492)
|
||||
|
||||
#### Improvements 🧹
|
||||
|
||||
- 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
|
||||
.Lk https://oss.terrastruct.com/d2
|
||||
.Ns .
|
||||
.It Fl s , -sketch Ar false
|
||||
Renders the diagram to look like it was sketched by hand
|
||||
.Ns .
|
||||
.It Fl -pad Ar 100
|
||||
Pixels padded around the rendered diagram
|
||||
.Ns .
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func test(t *testing.T, textPath, text string) {
|
|||
ruler, err := textmeasure.NewRuler()
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = g.SetDimensions(nil, ruler)
|
||||
err = g.SetDimensions(nil, ruler, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = d2dagrelayout.Layout(ctx, g)
|
||||
|
|
@ -128,7 +128,7 @@ func test(t *testing.T, textPath, text string) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = d2exporter.Export(ctx, g, 0)
|
||||
_, err = d2exporter.Export(ctx, g, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,21 @@ import (
|
|||
"strconv"
|
||||
|
||||
"oss.terrastruct.com/d2/d2graph"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"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)
|
||||
|
||||
diagram := d2target.NewDiagram()
|
||||
if fontFamily == nil {
|
||||
defaultFont := d2fonts.SourceSansPro
|
||||
fontFamily = &defaultFont
|
||||
}
|
||||
diagram.FontFamily = fontFamily
|
||||
|
||||
diagram.Shapes = make([]d2target.Shape, len(g.Objects))
|
||||
for i := range g.Objects {
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ func run(t *testing.T, tc testCase) {
|
|||
ruler, err := textmeasure.NewRuler()
|
||||
assert.JSON(t, nil, err)
|
||||
|
||||
err = g.SetDimensions(nil, ruler)
|
||||
err = g.SetDimensions(nil, ruler, nil)
|
||||
assert.JSON(t, nil, err)
|
||||
|
||||
err = d2dagrelayout.Layout(ctx, g)
|
||||
|
|
@ -224,7 +224,7 @@ func run(t *testing.T, tc testCase) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := d2exporter.Export(ctx, g, tc.themeID)
|
||||
got, err := d2exporter.Export(ctx, g, tc.themeID, nil)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
return dims
|
||||
}
|
||||
|
|
@ -861,7 +861,11 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2
|
|||
} else if t.IsItalic {
|
||||
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)
|
||||
}
|
||||
|
|
@ -870,13 +874,13 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2
|
|||
}
|
||||
|
||||
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 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 {
|
||||
obj.Box = &geo.Box{}
|
||||
// TODO fix edge cases for unnamed class etc
|
||||
|
|
@ -905,7 +909,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
}
|
||||
innerLabelPadding = 0
|
||||
} else {
|
||||
dims = getTextDimensions(mtexts, ruler, obj.Text())
|
||||
dims = getTextDimensions(mtexts, ruler, obj.Text(), fontFamily)
|
||||
}
|
||||
if dims == nil {
|
||||
if obj.Attributes.Shape.Value == d2target.ShapeImage {
|
||||
|
|
@ -959,7 +963,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
maxWidth := dims.Width
|
||||
|
||||
for _, f := range obj.Class.Fields {
|
||||
fdims := getTextDimensions(mtexts, ruler, f.Text())
|
||||
fdims := getTextDimensions(mtexts, ruler, f.Text(), fontFamily)
|
||||
if fdims == nil {
|
||||
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 {
|
||||
mdims := getTextDimensions(mtexts, ruler, m.Text())
|
||||
mdims := getTextDimensions(mtexts, ruler, m.Text(), fontFamily)
|
||||
if mdims == nil {
|
||||
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 {
|
||||
// 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))
|
||||
}
|
||||
// Leave room for padding
|
||||
|
|
@ -1043,7 +1047,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
for _, label := range endpointLabels {
|
||||
t := edge.Text()
|
||||
t.Text = label
|
||||
dims := getTextDimensions(mtexts, ruler, t)
|
||||
dims := getTextDimensions(mtexts, ruler, t, fontFamily)
|
||||
edge.MinWidth += dims.Width
|
||||
// Some padding as it's not totally near the end
|
||||
edge.MinHeight += dims.Height + 5
|
||||
|
|
@ -1053,7 +1057,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
continue
|
||||
}
|
||||
|
||||
dims := getTextDimensions(mtexts, ruler, edge.Text())
|
||||
dims := getTextDimensions(mtexts, ruler, edge.Text(), fontFamily)
|
||||
if dims == nil {
|
||||
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/d2graph"
|
||||
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
)
|
||||
|
|
@ -20,7 +21,13 @@ type CompileOptions struct {
|
|||
Ruler *textmeasure.Ruler
|
||||
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) {
|
||||
|
|
@ -36,7 +43,7 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target
|
|||
}
|
||||
|
||||
if len(g.Objects) > 0 {
|
||||
err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler)
|
||||
err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler, opts.FontFamily)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// d2fonts holds fonts for renderings
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
|
|
@ -8,7 +9,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type FontFamily int
|
||||
type FontFamily string
|
||||
type FontStyle string
|
||||
|
||||
type Font struct {
|
||||
|
|
@ -38,8 +39,9 @@ const (
|
|||
FONT_STYLE_BOLD FontStyle = "bold"
|
||||
FONT_STYLE_ITALIC FontStyle = "italic"
|
||||
|
||||
SourceSansPro FontFamily = iota
|
||||
SourceCodePro FontFamily = iota
|
||||
SourceSansPro FontFamily = "SourceSansPro"
|
||||
SourceCodePro FontFamily = "SourceCodePro"
|
||||
HandDrawn FontFamily = "HandDrawn"
|
||||
)
|
||||
|
||||
var FontSizes = []int{
|
||||
|
|
@ -61,6 +63,7 @@ var FontStyles = []FontStyle{
|
|||
var FontFamilies = []FontFamily{
|
||||
SourceSansPro,
|
||||
SourceCodePro,
|
||||
HandDrawn,
|
||||
}
|
||||
|
||||
//go:embed encoded/SourceSansPro-Regular.txt
|
||||
|
|
@ -75,6 +78,12 @@ var sourceSansProItalicBase64 string
|
|||
//go:embed encoded/SourceCodePro-Regular.txt
|
||||
var sourceCodeProRegularBase64 string
|
||||
|
||||
//go:embed encoded/ArchitectsDaughter-Regular.txt
|
||||
var architectsDaughterRegularBase64 string
|
||||
|
||||
//go:embed encoded/FuzzyBubbles-Bold.txt
|
||||
var fuzzyBubblesBoldBase64 string
|
||||
|
||||
//go:embed ttf/*
|
||||
var fontFacesFS embed.FS
|
||||
|
||||
|
|
@ -99,6 +108,19 @@ func init() {
|
|||
Family: SourceCodePro,
|
||||
Style: FONT_STYLE_REGULAR,
|
||||
}: 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 {
|
||||
|
|
@ -138,4 +160,24 @@ func init() {
|
|||
Family: SourceSansPro,
|
||||
Style: FONT_STYLE_ITALIC,
|
||||
}] = 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/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2latex"
|
||||
"oss.terrastruct.com/d2/d2renderers/d2sketch"
|
||||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/lib/color"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
"oss.terrastruct.com/d2/lib/shape"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
)
|
||||
|
||||
|
|
@ -44,9 +46,17 @@ var multipleOffset = geo.NewVector(10, -10)
|
|||
//go:embed style.css
|
||||
var styleCSS string
|
||||
|
||||
//go:embed sketchstyle.css
|
||||
var sketchStyleCSS string
|
||||
|
||||
//go:embed github-markdown.css
|
||||
var mdCSS string
|
||||
|
||||
type RenderOpts struct {
|
||||
Pad int
|
||||
Sketch bool
|
||||
}
|
||||
|
||||
func setViewbox(writer io.Writer, diagram *d2target.Diagram, pad int) (width int, height int) {
|
||||
tl, br := diagram.BoundingBox()
|
||||
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))
|
||||
var markerStart string
|
||||
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)"/>`,
|
||||
pathData(connection, idToShape),
|
||||
connectionStyle(connection),
|
||||
path := pathData(connection, idToShape)
|
||||
attrs := fmt.Sprintf(`%s%smask="url(#%s)"`,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
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 != "" {
|
||||
fontClass := "text"
|
||||
|
|
@ -589,7 +608,7 @@ func render3dRect(targetShape d2target.Shape) string {
|
|||
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))
|
||||
tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y))
|
||||
width := float64(targetShape.Width)
|
||||
|
|
@ -636,7 +655,15 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string,
|
|||
if targetShape.Multiple {
|
||||
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:
|
||||
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" />`,
|
||||
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" />`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||
if sketchRunner != nil {
|
||||
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:
|
||||
default:
|
||||
|
|
@ -664,8 +699,16 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string,
|
|||
}
|
||||
}
|
||||
|
||||
for _, pathData := range s.GetSVGPathData() {
|
||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||
if sketchRunner != nil {
|
||||
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(`stroke-width:%d;`, shape.StrokeWidth)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -855,22 +898,14 @@ func connectionStyle(connection d2target.Connection) string {
|
|||
out += fmt.Sprintf(`opacity:%f;`, connection.Opacity)
|
||||
out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth)
|
||||
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)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func getStrokeDashAttributes(strokeWidth, dashGapSize float64) (float64, float64) {
|
||||
// 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) {
|
||||
func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) {
|
||||
content := buf.String()
|
||||
buf.WriteString(`<style type="text/css"><![CDATA[`)
|
||||
|
||||
|
|
@ -889,7 +924,7 @@ func embedFonts(buf *bytes.Buffer) {
|
|||
font-family: font-regular;
|
||||
src: url("%s");
|
||||
}`,
|
||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -910,7 +945,7 @@ func embedFonts(buf *bytes.Buffer) {
|
|||
font-family: font-bold;
|
||||
src: url("%s");
|
||||
}`,
|
||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -931,7 +966,7 @@ func embedFonts(buf *bytes.Buffer) {
|
|||
font-family: font-italic;
|
||||
src: url("%s");
|
||||
}`,
|
||||
d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_ITALIC)])
|
||||
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC)])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -963,15 +998,32 @@ func embedFonts(buf *bytes.Buffer) {
|
|||
}
|
||||
|
||||
// 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{}
|
||||
w, h := setViewbox(buf, diagram, pad)
|
||||
|
||||
styleCSS2 := ""
|
||||
if sketchRunner != nil {
|
||||
styleCSS2 = "\n" + sketchStyleCSS
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(`<style type="text/css">
|
||||
<![CDATA[
|
||||
%s
|
||||
%s%s
|
||||
]]>
|
||||
</style>`, styleCSS))
|
||||
</style>`, styleCSS, styleCSS2))
|
||||
|
||||
hasMarkdown := false
|
||||
for _, s := range diagram.Shapes {
|
||||
|
|
@ -983,6 +1035,9 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
|||
if hasMarkdown {
|
||||
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
|
||||
for _, s := range diagram.Shapes {
|
||||
|
|
@ -1017,12 +1072,15 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
|||
markers := map[string]struct{}{}
|
||||
for _, obj := range allObjects {
|
||||
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 != "" {
|
||||
labelMasks = append(labelMasks, labelMask)
|
||||
}
|
||||
} else if s, is := obj.(d2target.Shape); is {
|
||||
labelMask, err := drawShape(buf, s)
|
||||
labelMask, err := drawShape(buf, s, sketchRunner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if labelMask != "" {
|
||||
|
|
@ -1046,7 +1104,7 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) {
|
|||
`</mask>`,
|
||||
}, "\n"))
|
||||
|
||||
embedFonts(buf)
|
||||
embedFonts(buf, diagram.FontFamily)
|
||||
|
||||
buf.WriteString(`</svg>`)
|
||||
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/d2/d2renderers/d2fonts"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/label"
|
||||
|
|
@ -22,8 +23,9 @@ const (
|
|||
)
|
||||
|
||||
type Diagram struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
FontFamily *d2fonts.FontFamily `json:"fontFamily,omitempty"`
|
||||
|
||||
Shapes []Shape `json:"shapes"`
|
||||
Connections []Connection `json:"connections"`
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ func main() {
|
|||
Ruler: ruler,
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ import (
|
|||
func main() {
|
||||
graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
|
||||
ruler, _ := textmeasure.NewRuler()
|
||||
_ = graph.SetDimensions(nil, ruler)
|
||||
_ = graph.SetDimensions(nil, ruler, nil)
|
||||
_ = d2dagrelayout.Layout(context.Background(), graph)
|
||||
diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID)
|
||||
out, _ := d2svg.Render(diagram, d2svg.DEFAULT_PADDING)
|
||||
diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID, nil)
|
||||
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
Pad: d2svg.DEFAULT_PADDING,
|
||||
})
|
||||
_ = 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)
|
||||
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)
|
||||
err = os.MkdirAll(dataPath, 0755)
|
||||
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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [],
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [],
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"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": "",
|
||||
"fontFamily": "SourceSansPro",
|
||||
"shapes": [
|
||||
{
|
||||
"id": "a",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue