Merge branch 'master' into 1421/bottom-scope-special-boards

This commit is contained in:
Bernard Xie 2023-06-19 17:47:23 -07:00
commit a7d3bb0f91
No known key found for this signature in database
GPG key ID: 3C3E0036CE0F892C
1115 changed files with 29832 additions and 18891 deletions

View file

@ -228,7 +228,7 @@ let us know and we'll be happy to include it here!
- **Mongo to D2**: [https://github.com/novuhq/mongo-to-D2](https://github.com/novuhq/mongo-to-D2)
- **Pandoc filter**: [https://github.com/ram02z/d2-filter](https://github.com/ram02z/d2-filter)
- **Logseq-D2**: [https://github.com/b-yp/logseq-d2](https://github.com/b-yp/logseq-d2)
- **ent2d2**: [https://github.com/tmc/ent2d2](https://github.com/b-yp/logseq-d2)
- **ent2d2**: [https://github.com/tmc/ent2d2](https://github.com/tmc/ent2d2)
### Misc

View file

@ -1,5 +1,7 @@
#### Features 🚀
- Configure timeout value with D2_TIMEOUT env var. [#1392](https://github.com/terrastruct/d2/pull/1392)
#### Improvements 🧹
- Use shape specific sizing for grid containers [#1294](https://github.com/terrastruct/d2/pull/1294)
@ -8,15 +10,12 @@
- Watch mode browser uses an error favicon to easily indicate compiler errors. Thanks @sinyo-matu ! [#1240](https://github.com/terrastruct/d2/pull/1240)
- Improves grid layout performance when there are many similarly sized shapes. [#1315](https://github.com/terrastruct/d2/pull/1315)
- Connections and labels now are adjusted for shapes with `3d` or `multiple`. [#1340](https://github.com/terrastruct/d2/pull/1340)
- Display version on CLI help invocation [#1400](https://github.com/terrastruct/d2/pull/1400)
- Improved readability of connection labels when they overlap another connection. [#447](https://github.com/terrastruct/d2/pull/447)
- Error message when `shape` is given a composite [#1415](https://github.com/terrastruct/d2/pull/1415)
- The autoformatter moves board declarations to the bottom of its scope. [#1424](https://github.com/terrastruct/d2/pull/1424)
#### Bugfixes ⛑️
- Shadow is cut off when `--pad` is 0. Thank you @LeonardsonCC ! [#1326](https://github.com/terrastruct/d2/pull/1326)
- Fixes grid layout overwriting label placements for nested objects. [#1345](https://github.com/terrastruct/d2/pull/1345)
- Fixes fonts not rendering correctly on certain platforms. Thanks @mikeday for identifying the solution. [#1356](https://github.com/terrastruct/d2/pull/1356)
- Fixes folders not rendering in animations (`--animate-interval`) [#1357](https://github.com/terrastruct/d2/pull/1357)
- Fixes panic using reserved keywords as containers [#1358](https://github.com/terrastruct/d2/pull/1358)
- When multiple classes are applied changing different attributes of arrowheads, they are
all applied instead of only the last one [#1362](https://github.com/terrastruct/d2/pull/1362)
- Prevent empty block strings [#1364](https://github.com/terrastruct/d2/pull/1364)
- Fixes edge case in compiler using dots in quotes [#1401](https://github.com/terrastruct/d2/pull/1401)
- Fixes grid label font size for TALA [#1412](https://github.com/terrastruct/d2/pull/1412)

View file

@ -0,0 +1,46 @@
There are three important features that were in the initial design of D2 that have not been done and hold it back from 1.0: globs, imports, and vars. This release brings imports.
Imports open up a world of possibilities and works beautifully to modularize diagrams. See the new docs to try it out today.
As usual, many improvements and bug fixes accompany this release. D2 0.5 produces more legible diagrams by masking obstructions (e.g. arrow going through a label), has better error messages to guide you, is faster at certain tasks, and addresses many issues brought by community bug reports.
#### Features 🚀
- D2 files have the ability to import from other D2 files. See [docs](https://d2lang.com/tour/imports). [#1371](https://github.com/terrastruct/d2/pull/1371)
- `sql_table` alternatively takes an array of constraints instead of being limited to a single one. Thanks @satoqz ! [#1245](https://github.com/terrastruct/d2/pull/1245)
#### Improvements 🧹
- Use shape-specific sizing for grid containers [#1294](https://github.com/terrastruct/d2/pull/1294)
- Grid diagrams support nested shapes or grid diagrams [#1309](https://github.com/terrastruct/d2/pull/1309)
- `grid-gap`, `vertical-gap`, and `horizontal-gap` apply to padding on grid diagrams [#1309](https://github.com/terrastruct/d2/pull/1309)
- Watch mode browser uses an error favicon to easily indicate compiler errors. Thanks @sinyo-matu ! [#1240](https://github.com/terrastruct/d2/pull/1240)
- Grid layout performance improved when there are many similarly sized shapes [#1315](https://github.com/terrastruct/d2/pull/1315)
- Connections and labels are adjusted for shapes with `3d` or `multiple` [#1340](https://github.com/terrastruct/d2/pull/1340)
- Constraints in `sql_table` render even if they have no matching abbreviation [#1372](https://github.com/terrastruct/d2/pull/1372)
- Constraints in `sql_table` sheds their excessive letter-spacing and is padded from the end consistently [#1372](https://github.com/terrastruct/d2/pull/1372)
- Duplicate image URLs in icons are only fetched once [#1373](https://github.com/terrastruct/d2/pull/1373)
- In watch mode, images are cached by default across compiles. Can be disabled with flag `--img-cache=0`. [#1373](https://github.com/terrastruct/d2/pull/1373)
- Common invalid array separator `,` usage in class arrays returns a helpful error message [#1376](https://github.com/terrastruct/d2/pull/1376)
- Invalid `constraint` usage is met with an error message, preventing a common mistake of omitting `shape: sql_table` [#1379](https://github.com/terrastruct/d2/pull/1379)
- Connections no longer obscure outside labels [#1381](https://github.com/terrastruct/d2/pull/1381)
- Container connections in `dagre` are more balanced [#1384](https://github.com/terrastruct/d2/pull/1384)
- Connections that go through shape labels are now masked translucently [#1383](https://github.com/terrastruct/d2/pull/1383)
#### Bugfixes ⛑️
- Shadow is no longer cut off when `--pad` is 0. Thank you @LeonardsonCC ! [#1326](https://github.com/terrastruct/d2/pull/1326)
- Fixes grid layout overwriting label placements for nested objects [#1345](https://github.com/terrastruct/d2/pull/1345)
- Fixes fonts not rendering correctly on certain platforms. Thanks @mikeday for identifying the solution. [#1356](https://github.com/terrastruct/d2/pull/1356)
- Fixes folders not rendering in animations (`--animate-interval`) [#1357](https://github.com/terrastruct/d2/pull/1357)
- Fixes panic using reserved keywords as containers [#1358](https://github.com/terrastruct/d2/pull/1358)
- When multiple classes are change different attributes of arrowheads, they are
all applied instead of only the last one [#1362](https://github.com/terrastruct/d2/pull/1362)
- Prevent empty block strings [#1364](https://github.com/terrastruct/d2/pull/1364)
- Fixes `dagre` mis-aligning a nested shape's connection [#1370](https://github.com/terrastruct/d2/pull/1370)
- Fixes a bug in grids sometimes putting a shape on the next row/column [#1380](https://github.com/terrastruct/d2/pull/1380)
#### Breaking changes
- `@xyz` is now reserved as a pattern for imports. If you previously had a key that started like that, it must either be renamed or quoted like `"@xyz"`.
- Likewise with `...@xyz` (spread operator import)

View file

@ -0,0 +1,5 @@
This is a hotfix to 0.5.0, imports weren't working on Windows.
#### Improvements 🧹
- Improves compiler's tooltip URL check. [#1390](https://github.com/terrastruct/d2/pull/1390)

View file

@ -103,27 +103,41 @@ Set the diagram layout engine to the passed string. For a list of available opti
.Ar layout
.Ns .
.It Fl b , -bundle Ar true
Bundle all assets and layers into the output svg.
Bundle all assets and layers into the output svg
.Ns .
.It Fl -force-appendix Ar false
An appendix for tooltips and links is added to PNG exports since they are not interactive. Setting this to true adds an appendix to SVG exports as well
.Ns .
.It Fl d , -debug
Print debug logs.
Print debug logs
.Ns .
.It Fl -img-cache Ar true
In watch mode, images used in icons are cached for subsequent compilations. This should be disabled if images might change
.Ns .
.It Fl -timeout Ar 120
The maximum number of seconds that D2 runs for before timing out and exiting. When rendering a large diagram, it is recommended to increase this value
.Ns .
.It Fl h , -help
Print usage information and exit.
Print usage information and exit
.Ns .
.It Fl v , -version
Print version information and exit.
Print version information and exit
.Ns .
.El
.Sh SUBCOMMANDS
.Bl -tag -width Fl
.It Ar layout
Lists available layout engine options with short help.
Lists available layout engine options with short help
.Ns .
.It Ar layout Op Ar name
Display long help for a particular layout engine, including its configuration options.
Display long help for a particular layout engine, including its configuration options
.Ns .
.It Ar themes
Lists available themes.
Lists available themes
.Ns .
.It Ar fmt Ar file.d2 ...
Format all passed files.
Format all passed files
.Ns .
.El
.Sh SEE ALSO
.Xr d2plugin-tala 1

View file

@ -28,6 +28,7 @@ import (
"errors"
"fmt"
"math/big"
"path"
"strconv"
"strings"
"unicode"
@ -63,6 +64,7 @@ var _ Node = &DoubleQuotedString{}
var _ Node = &SingleQuotedString{}
var _ Node = &BlockString{}
var _ Node = &Substitution{}
var _ Node = &Import{}
var _ Node = &Array{}
var _ Node = &Map{}
@ -179,6 +181,10 @@ func (p Position) String() string {
return fmt.Sprintf("%d:%d", p.Line+1, p.Column+1)
}
func (p Position) Debug() string {
return fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)
}
// See docs on Range.
func (p Position) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)), nil
@ -256,6 +262,13 @@ func (p Position) Subtract(r rune, byUTF16 bool) Position {
return p
}
func (p Position) AdvanceString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Advance(r, byUTF16)
}
return p
}
func (p Position) SubtractString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Subtract(r, byUTF16)
@ -277,6 +290,7 @@ var _ MapNode = &Comment{}
var _ MapNode = &BlockComment{}
var _ MapNode = &Key{}
var _ MapNode = &Substitution{}
var _ MapNode = &Import{}
// ArrayNode is implemented by nodes that may be children of Arrays.
type ArrayNode interface {
@ -288,6 +302,7 @@ type ArrayNode interface {
var _ ArrayNode = &Comment{}
var _ ArrayNode = &BlockComment{}
var _ ArrayNode = &Substitution{}
var _ ArrayNode = &Import{}
// Value is implemented by nodes that may be values of a key.
type Value interface {
@ -334,6 +349,7 @@ func (s *DoubleQuotedString) node() {}
func (s *SingleQuotedString) node() {}
func (s *BlockString) node() {}
func (s *Substitution) node() {}
func (i *Import) node() {}
func (a *Array) node() {}
func (m *Map) node() {}
func (k *Key) node() {}
@ -351,6 +367,7 @@ func (s *DoubleQuotedString) Type() string { return "double quoted string" }
func (s *SingleQuotedString) Type() string { return "single quoted string" }
func (s *BlockString) Type() string { return s.Tag + " block string" }
func (s *Substitution) Type() string { return "substitution" }
func (i *Import) Type() string { return "import" }
func (a *Array) Type() string { return "array" }
func (m *Map) Type() string { return "map" }
func (k *Key) Type() string { return "map key" }
@ -368,6 +385,7 @@ func (s *DoubleQuotedString) GetRange() Range { return s.Range }
func (s *SingleQuotedString) GetRange() Range { return s.Range }
func (s *BlockString) GetRange() Range { return s.Range }
func (s *Substitution) GetRange() Range { return s.Range }
func (i *Import) GetRange() Range { return i.Range }
func (a *Array) GetRange() Range { return a.Range }
func (m *Map) GetRange() Range { return m.Range }
func (k *Key) GetRange() Range { return k.Range }
@ -379,6 +397,7 @@ func (c *Comment) mapNode() {}
func (c *BlockComment) mapNode() {}
func (k *Key) mapNode() {}
func (s *Substitution) mapNode() {}
func (i *Import) mapNode() {}
func (c *Comment) arrayNode() {}
func (c *BlockComment) arrayNode() {}
@ -390,6 +409,7 @@ func (s *DoubleQuotedString) arrayNode() {}
func (s *SingleQuotedString) arrayNode() {}
func (s *BlockString) arrayNode() {}
func (s *Substitution) arrayNode() {}
func (i *Import) arrayNode() {}
func (a *Array) arrayNode() {}
func (m *Map) arrayNode() {}
@ -402,6 +422,7 @@ func (s *SingleQuotedString) value() {}
func (s *BlockString) value() {}
func (a *Array) value() {}
func (m *Map) value() {}
func (i *Import) value() {}
func (n *Null) scalar() {}
func (b *Boolean) scalar() {}
@ -722,11 +743,20 @@ type Substitution struct {
Path []*StringBox `json:"path"`
}
type Import struct {
Range Range `json:"range"`
Spread bool `json:"spread"`
Pre string `json:"pre"`
Path []*StringBox `json:"path"`
}
// MapNodeBox is used to box MapNode for JSON persistence.
type MapNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
MapKey *Key `json:"map_key,omitempty"`
}
@ -739,6 +769,8 @@ func MakeMapNodeBox(n MapNode) MapNodeBox {
box.BlockComment = n
case *Substitution:
box.Substitution = n
case *Import:
box.Import = n
case *Key:
box.MapKey = n
}
@ -753,6 +785,8 @@ func (mb MapNodeBox) Unbox() MapNode {
return mb.BlockComment
case mb.Substitution != nil:
return mb.Substitution
case mb.Import != nil:
return mb.Import
case mb.MapKey != nil:
return mb.MapKey
default:
@ -777,6 +811,7 @@ type ArrayNodeBox struct {
Comment *Comment `json:"comment,omitempty"`
BlockComment *BlockComment `json:"block_comment,omitempty"`
Substitution *Substitution `json:"substitution,omitempty"`
Import *Import `json:"import,omitempty"`
Null *Null `json:"null,omitempty"`
Boolean *Boolean `json:"boolean,omitempty"`
Number *Number `json:"number,omitempty"`
@ -797,6 +832,8 @@ func MakeArrayNodeBox(an ArrayNode) ArrayNodeBox {
ab.BlockComment = an
case *Substitution:
ab.Substitution = an
case *Import:
ab.Import = an
case *Null:
ab.Null = an
case *Boolean:
@ -827,6 +864,8 @@ func (ab ArrayNodeBox) Unbox() ArrayNode {
return ab.BlockComment
case ab.Substitution != nil:
return ab.Substitution
case ab.Import != nil:
return ab.Import
case ab.Null != nil:
return ab.Null
case ab.Boolean != nil:
@ -861,6 +900,7 @@ type ValueBox struct {
BlockString *BlockString `json:"block_string,omitempty"`
Array *Array `json:"array,omitempty"`
Map *Map `json:"map,omitempty"`
Import *Import `json:"import,omitempty"`
}
func (vb ValueBox) Unbox() Value {
@ -883,6 +923,8 @@ func (vb ValueBox) Unbox() Value {
return vb.Array
case vb.Map != nil:
return vb.Map
case vb.Import != nil:
return vb.Import
default:
return nil
}
@ -909,6 +951,8 @@ func MakeValueBox(v Value) ValueBox {
vb.Array = v
case *Map:
vb.Map = v
case *Import:
vb.Import = v
}
return vb
}
@ -1002,8 +1046,8 @@ type InterpolationBox struct {
// & is only special if it begins a key.
// - is only special if followed by another - in a key.
// ' " and | are only special if they begin an unquoted key or value.
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$'})
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})
// RawString returns s in a AST String node that can format s in the most aesthetically
// pleasing way.
@ -1052,8 +1096,33 @@ func RawString(s string, inKey bool) String {
return FlatUnquotedString(s)
}
func RawStringBox(s string, inKey bool) *StringBox {
return MakeValueBox(RawString(s, inKey)).StringBox()
}
func hasSurroundingWhitespace(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r2, _ := utf8.DecodeLastRuneInString(s)
return unicode.IsSpace(r) || unicode.IsSpace(r2)
}
func (s *Substitution) IDA() (ida []string) {
for _, el := range s.Path {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}
func (i *Import) IDA() (ida []string) {
for _, el := range i.Path[1:] {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}
func (i *Import) PathWithPre() string {
if len(i.Path) == 0 {
return ""
}
return path.Join(i.Pre, i.Path[0].Unbox().ScalarString())
}

View file

@ -13,10 +13,12 @@ import (
"oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/version"
)
func help(ms *xmain.State) {
fmt.Fprintf(ms.Stdout, `Usage:
fmt.Fprintf(ms.Stdout, `%[1]s %[2]s
Usage:
%[1]s [--watch=false] [--theme=0] file.d2 [file.svg | file.png]
%[1]s layout [name]
%[1]s fmt file.d2 ...
@ -29,7 +31,7 @@ Use - to have d2 read from stdin or write to stdout.
See man d2 for more detailed docs.
Flags:
%s
%[3]s
Subcommands:
%[1]s layout - Lists available layout engine options with short help
@ -40,7 +42,7 @@ Subcommands:
See more docs and the source code at https://oss.terrastruct.com/d2.
Hosted icons at https://icons.terrastruct.com.
Playground runner at https://play.d2lang.com.
`, filepath.Base(ms.Name), ms.Opts.Defaults())
`, filepath.Base(ms.Name), version.Version, ms.Opts.Defaults())
}
func layoutCmd(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error {

View file

@ -37,6 +37,7 @@ import (
"oss.terrastruct.com/d2/lib/png"
"oss.terrastruct.com/d2/lib/pptx"
"oss.terrastruct.com/d2/lib/textmeasure"
timelib "oss.terrastruct.com/d2/lib/time"
"oss.terrastruct.com/d2/lib/version"
"oss.terrastruct.com/d2/lib/xgif"
@ -67,6 +68,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
if err != nil {
return err
}
imgCacheFlag, err := ms.Opts.Bool("IMG_CACHE", "img-cache", "", true, "in watch mode, images used in icons are cached for subsequent compilations. This should be disabled if images might change.")
if err != nil {
return err
}
layoutFlag := ms.Opts.String("D2_LAYOUT", "layout", "l", "dagre", `the layout engine used`)
themeFlag, err := ms.Opts.Int64("D2_THEME", "theme", "t", 0, "the diagram theme ID")
if err != nil {
@ -84,6 +89,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
if err != nil {
return err
}
timeoutFlag, err := ms.Opts.Int64("D2_TIMEOUT", "timeout", "", 120, "the maximum number of seconds that D2 runs for before timing out and exiting. When rendering a large diagram, it is recommended to increase this value")
if err != nil {
return err
}
versionFlag, err := ms.Opts.Bool("", "version", "v", false, "get the version")
if err != nil {
return err
@ -150,9 +160,15 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
if *debugFlag {
ms.Env.Setenv("DEBUG", "1")
}
if *imgCacheFlag {
ms.Env.Setenv("IMG_CACHE", "1")
}
if *browserFlag != "" {
ms.Env.Setenv("BROWSER", *browserFlag)
}
if timeoutFlag != nil {
os.Setenv("D2_TIMEOUT", fmt.Sprintf("%d", *timeoutFlag))
}
var inputPath string
var outputPath string
@ -291,7 +307,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return w.run()
}
ctx, cancel := context.WithTimeout(ctx, time.Minute*2)
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
defer cancel()
_, written, err := compile(ctx, ms, plugin, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
@ -322,6 +338,7 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
FontFamily: fontFamily,
InputPath: inputPath,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)

View file

@ -403,7 +403,7 @@ func (w *watcher) goServe() error {
// TODO: Add standard debug/profiling routes
m.HandleFunc("/", w.handleRoot)
m.Handle("/static/", http.StripPrefix("/static", w.staticFileServer))
m.Handle("/watch", xhttp.HandlerFuncAdapter{w.ms.Log, w.handleWatch})
m.Handle("/watch", xhttp.HandlerFuncAdapter{Log: w.ms.Log, Func: w.handleWatch})
s := xhttp.NewServer(w.ms.Log.Warn, xhttp.Log(w.ms.Log, m))
w.goFunc(func(ctx context.Context) error {

View file

@ -4,6 +4,7 @@ import (
"encoding/xml"
"fmt"
"io"
"io/fs"
"net/url"
"strconv"
"strings"
@ -21,21 +22,27 @@ import (
type CompileOptions struct {
UTF16 bool
// FS is the file system used for resolving imports in the d2 text.
// It should correspond to the root path.
FS fs.FS
}
func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) {
if opts == nil {
opts = &CompileOptions{}
}
ast, err := d2parser.Parse(path, r, &d2parser.ParseOptions{
ast, err := d2parser.Parse(p, r, &d2parser.ParseOptions{
UTF16: opts.UTF16,
})
if err != nil {
return nil, err
}
ir, err := d2ir.Compile(ast)
ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
UTF16: opts.UTF16,
FS: opts.FS,
})
if err != nil {
return nil, err
}
@ -50,7 +57,9 @@ func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph
}
func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
c := &compiler{}
c := &compiler{
err: &d2parser.ParseError{},
}
g := d2graph.NewGraph()
g.AST = ast
@ -101,7 +110,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
}
g2 := d2graph.NewGraph()
g2.Parent = g
g2.AST = g.AST
g2.AST = f.Map().AST().(*d2ast.Map)
c.compileBoard(g2, f.Map())
g2.Name = f.Name
switch fieldName {
@ -116,7 +125,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
}
type compiler struct {
err d2parser.ParseError
err *d2parser.ParseError
}
func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
@ -147,12 +156,31 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {
classMap := m.GetClassMap(className)
if classMap != nil {
c.compileMap(obj, classMap)
} else {
if strings.Contains(className, ",") {
split := strings.Split(className, ",")
allFound := true
for _, maybeClassName := range split {
maybeClassName = strings.TrimSpace(maybeClassName)
if m.GetClassMap(maybeClassName) == nil {
allFound = false
break
}
}
if allFound {
c.errorf(class.LastRef().AST(), `class "%s" not found. Did you mean to use ";" to separate array items?`, className)
}
}
}
}
}
shape := m.GetField("shape")
if shape != nil {
c.compileField(obj, shape)
if shape.Composite != nil {
c.errorf(shape.LastPrimaryKey(), "reserved field shape does not accept composite")
} else {
c.compileField(obj, shape)
}
}
for _, f := range m.Fields {
if f.Name == "shape" {
@ -249,17 +277,19 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
obj.Map = fr.Context.Key.Value.Map
}
}
scopeObjIDA := d2ir.BoardIDA(fr.Context.ScopeMap)
scopeObj := obj.Graph.Root.EnsureChildIDVal(scopeObjIDA)
obj.References = append(obj.References, d2graph.Reference{
r := d2graph.Reference{
Key: fr.KeyPath,
KeyPathIndex: fr.KeyPathIndex(),
MapKey: fr.Context.Key,
MapKeyEdgeIndex: fr.Context.EdgeIndex(),
Scope: fr.Context.Scope,
ScopeObj: scopeObj,
})
}
if fr.Context.ScopeMap != nil {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(fr.Context.ScopeMap))
r.ScopeObj = obj.Graph.Root.EnsureChild(scopeObjIDA)
}
obj.References = append(obj.References, r)
}
}
@ -310,8 +340,27 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
if f.Primary() == nil {
if f.Composite != nil && !strings.EqualFold(f.Name, "class") {
c.errorf(f.LastPrimaryKey(), "reserved field %v does not accept composite", f.Name)
if f.Composite != nil {
switch f.Name {
case "class":
if arr, ok := f.Composite.(*d2ir.Array); ok {
for _, class := range arr.Values {
if scalar, ok := class.(*d2ir.Scalar); ok {
attrs.Classes = append(attrs.Classes, scalar.Value.ScalarString())
}
}
}
case "constraint":
if arr, ok := f.Composite.(*d2ir.Array); ok {
for _, constraint := range arr.Values {
if scalar, ok := constraint.(*d2ir.Scalar); ok {
attrs.Constraint = append(attrs.Constraint, scalar.Value.ScalarString())
}
}
}
default:
c.errorf(f.LastPrimaryKey(), "reserved field %v does not accept composite", f.Name)
}
}
return
}
@ -412,8 +461,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
c.errorf(f.LastPrimaryKey(), "constraint value must be a string")
return
}
attrs.Constraint.Value = scalar.ScalarString()
attrs.Constraint.MapKey = f.LastPrimaryKey()
attrs.Constraint = append(attrs.Constraint, scalar.ScalarString())
case "grid-rows":
v, err := strconv.Atoi(scalar.ScalarString())
if err != nil {
@ -480,23 +528,13 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
attrs.HorizontalGap.Value = scalar.ScalarString()
attrs.HorizontalGap.MapKey = f.LastPrimaryKey()
case "class":
if f.Primary() != nil {
attrs.Classes = append(attrs.Classes, scalar.ScalarString())
} else if f.Composite != nil {
if arr, ok := f.Composite.(*d2ir.Array); ok {
for _, class := range arr.Values {
if scalar, ok := class.(*d2ir.Scalar); ok {
attrs.Classes = append(attrs.Classes, scalar.Value.ScalarString())
}
}
}
}
attrs.Classes = append(attrs.Classes, scalar.ScalarString())
case "classes":
}
if attrs.Link != nil && attrs.Tooltip != nil {
_, err := url.ParseRequestURI(attrs.Tooltip.Value)
if err == nil {
u, err := url.ParseRequestURI(attrs.Tooltip.Value)
if err == nil && u.Host != "" {
c.errorf(scalar, "Tooltip cannot be set to URL when link is also set (for security)")
}
}
@ -594,15 +632,17 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
edge.Label.MapKey = e.LastPrimaryKey()
for _, er := range e.References {
scopeObjIDA := d2ir.BoardIDA(er.Context.ScopeMap)
scopeObj := edge.Src.Graph.Root.EnsureChildIDVal(scopeObjIDA)
edge.References = append(edge.References, d2graph.EdgeReference{
r := d2graph.EdgeReference{
Edge: er.Context.Edge,
MapKey: er.Context.Key,
MapKeyEdgeIndex: er.Context.EdgeIndex(),
Scope: er.Context.Scope,
ScopeObj: scopeObj,
})
}
if er.Context.ScopeMap != nil {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(er.Context.ScopeMap))
r.ScopeObj = edge.Src.Graph.Root.EnsureChild(scopeObjIDA)
}
edge.References = append(edge.References, r)
}
}
@ -784,11 +824,9 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) {
typ = ""
}
d2Col := d2target.SQLColumn{
Name: d2target.Text{Label: col.IDVal},
Type: d2target.Text{Label: typ},
}
if col.Constraint.Value != "" {
d2Col.Constraint = col.Constraint.Value
Name: d2target.Text{Label: col.IDVal},
Type: d2target.Text{Label: typ},
Constraint: col.Constraint,
}
obj.SQLTable.Columns = append(obj.SQLTable.Columns, d2Col)
}
@ -848,6 +886,10 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
if !in && arrowheadIn {
c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Shape.Value))
}
case "constraint":
if obj.Shape.Value != d2target.ShapeSQLTable {
c.errorf(f.LastPrimaryKey(), `"constraint" keyword can only be used in "sql_table" shapes`)
}
}
return
}

View file

@ -315,6 +315,20 @@ containers: {
`,
expErr: `d2/testdata/d2compiler/TestCompile/image_children_Steps.d2:4:3: steps is only allowed at a board root`,
},
{
name: "name-with-dot-underscore",
text: `A: {
_.C
}
"D.E": {
_.C
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, 3, len(g.Objects))
},
},
{
name: "stroke-width",
@ -1505,6 +1519,11 @@ x -> y: {
text: `x: {link: https://not-google.com; tooltip: https://google.com}`,
expErr: `d2/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.d2:1:44: Tooltip cannot be set to URL when link is also set (for security)`,
},
{
name: "url_link_non_url_tooltip_ok",
text: `x: {link: https://not-google.com; tooltip: note: url.ParseRequestURI might see this as a URL}`,
expErr: ``,
},
{
name: "url_link_and_not_url_tooltip_concurrently",
text: `x: {link: https://google.com; tooltip: hello world}`,
@ -2117,9 +2136,7 @@ ok: {
label: bar
constraint: BIZ
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
assert.String(t, "bar", g.Objects[0].Label.Value)
},
expErr: `d2/testdata/d2compiler/TestCompile/constraint_label.d2:3:3: "constraint" keyword can only be used in "sql_table" shapes`,
},
{
name: "invalid_direction",
@ -2170,13 +2187,17 @@ ok: {
},
},
{
name: "sql-panic",
text: `test {
shape: sql_table
test_id: varchar(64) {constraint: [primary_key, foreign_key]}
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/sql-panic.d2:3:27: reserved field constraint does not accept composite`,
name: "sql-constraints",
text: `x: {
shape: sql_table
a: int {constraint: primary_key}
b: int {constraint: [primary_key; foreign_key]}
}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
table := g.Objects[0].SQLTable
tassert.Equal(t, []string{"primary_key"}, table.Columns[0].Constraint)
tassert.Equal(t, []string{"primary_key", "foreign_key"}, table.Columns[1].Constraint)
},
},
{
name: "wrong_column_index",
@ -2478,6 +2499,23 @@ nostar -> 1star: { class: [path; path2] }
tassert.Equal(t, "2", g.Edges[0].Style.StrokeWidth.Value)
},
},
{
name: "comma-array-class",
text: `classes: {
dragon_ball: {
label: ""
shape: circle
style.fill: orange
}
path: {
label: "then"
style.stroke-width: 4
}
}
nostar: { class: [dragon_ball, path] }`,
expErr: `d2/testdata/d2compiler/TestCompile/comma-array-class.d2:12:11: class "dragon_ball, path" not found. Did you mean to use ";" to separate array items?`,
},
{
name: "reordered-classes",
text: `classes: {
@ -2576,6 +2614,15 @@ object: {
`,
expErr: `d2/testdata/d2compiler/TestCompile/classes-internal-edge.d2:8:3: classes cannot contain an edge`,
},
{
name: "reserved-composite",
text: `shape: sequence_diagram {
alice -> bob: What does it mean\nto be well-adjusted?
bob -> alice: The ability to play bridge or\ngolf as if they were games.
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/reserved-composite.d2:1:1: reserved field shape does not accept composite`,
},
}
for _, tc := range testCases {

View file

@ -1,6 +1,7 @@
package d2format
import (
"path"
"strconv"
"strings"
@ -62,6 +63,8 @@ func (p *printer) node(n d2ast.Node) {
p.blockString(n)
case *d2ast.Substitution:
p.substitution(n)
case *d2ast.Import:
p._import(n)
case *d2ast.Array:
p.array(n)
case *d2ast.Map:
@ -203,6 +206,25 @@ func (p *printer) substitution(s *d2ast.Substitution) {
p.sb.WriteByte('}')
}
func (p *printer) _import(i *d2ast.Import) {
if i.Spread {
p.sb.WriteString("...")
}
p.sb.WriteString("@")
pre := path.Clean(i.Pre)
if pre != "." {
p.sb.WriteString(pre)
p.sb.WriteRune('/')
}
if len(i.Path) > 0 {
i2 := *i
i2.Path = append([]*d2ast.StringBox{}, i.Path...)
i2.Path[0] = d2ast.RawStringBox(path.Clean(i.Path[0].Unbox().ScalarString()), true)
i = &i2
}
p.path(i.Path)
}
func (p *printer) array(a *d2ast.Array) {
p.sb.WriteByte('[')
if !a.Range.OneLine() {

View file

@ -617,6 +617,46 @@ y
x <= y
`,
exp: `x <- = y
`,
},
{
name: "import/1",
in: `
x: @file.d2
`,
exp: `x: @file
`,
},
{
name: "import/2",
in: `
x: @file."d2"
`,
exp: `x: @file."d2"
`,
},
{
name: "import/3",
in: `
x: @./file
`,
exp: `x: @file
`,
},
{
name: "import/4",
in: `
x: @../file
`,
exp: `x: @../file
`,
},
{
name: "import/4",
in: `
x: @"x/../file"
`,
exp: `x: @file
`,
},
{

View file

@ -132,8 +132,8 @@ type Attributes struct {
// TODO: default to ShapeRectangle instead of empty string
Shape Scalar `json:"shape"`
Direction Scalar `json:"direction"`
Constraint Scalar `json:"constraint"`
Direction Scalar `json:"direction"`
Constraint []string `json:"constraint"`
GridRows *Scalar `json:"gridRows,omitempty"`
GridColumns *Scalar `json:"gridColumns,omitempty"`
@ -584,7 +584,8 @@ func (obj *Object) Text() *d2target.MText {
}
if obj.OuterSequenceDiagram() == nil {
if obj.IsContainer() && obj.Shape.Value != "text" {
// Note: during grid layout when children are temporarily removed `IsContainer` is false
if (obj.IsContainer() || obj.IsGridDiagram()) && obj.Shape.Value != "text" {
fontSize = obj.Level().LabelSize()
}
} else {
@ -671,38 +672,6 @@ func (obj *Object) HasChild(ids []string) (*Object, bool) {
return child, true
}
// Keep in sync with EnsureChild.
func (obj *Object) EnsureChildIDVal(ids []string) *Object {
if len(ids) == 0 {
return obj
}
if len(ids) == 1 && ids[0] != "style" {
_, ok := ReservedKeywords[ids[0]]
if ok {
return obj
}
}
id := ids[0]
ids = ids[1:]
var child *Object
for _, ch2 := range obj.ChildrenArray {
if ch2.IDVal == id {
child = ch2
break
}
}
if child == nil {
child = obj.newObject(id)
}
if len(ids) >= 1 {
return child.EnsureChildIDVal(ids)
}
return child
}
func (obj *Object) HasEdge(mk *d2ast.Key) (*Edge, bool) {
ea, ok := obj.FindEdges(mk)
if !ok {
@ -1005,7 +974,7 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
case d2target.ShapeSQLTable:
maxNameWidth := 0
maxTypeWidth := 0
constraintWidth := 0
maxConstraintWidth := 0
colFontSize := d2fonts.FONT_SIZE_L
if obj.Style.FontSize != nil {
@ -1032,21 +1001,24 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
}
c.Type.LabelWidth = typeDims.Width
c.Type.LabelHeight = typeDims.Height
if maxTypeWidth < typeDims.Width {
maxTypeWidth = typeDims.Width
}
maxTypeWidth = go2.Max(maxTypeWidth, typeDims.Width)
if c.Constraint != "" {
// covers UNQ constraint with padding
constraintWidth = 60
if l := len(c.Constraint); l > 0 {
constraintDims := GetTextDimensions(mtexts, ruler, ctexts[2], fontFamily)
if constraintDims == nil {
return nil, fmt.Errorf("dimensions for sql_table constraint %#v not found", ctexts[2].Text)
}
maxConstraintWidth = go2.Max(maxConstraintWidth, constraintDims.Width)
}
}
// The rows get padded a little due to header font being larger than row font
dims.Height = go2.Max(12, labelDims.Height*(len(obj.SQLTable.Columns)+1))
headerWidth := d2target.HeaderPadding + labelDims.Width + d2target.HeaderPadding
rowsWidth := d2target.NamePadding + maxNameWidth + d2target.TypePadding + maxTypeWidth + d2target.TypePadding + constraintWidth
rowsWidth := d2target.NamePadding + maxNameWidth + d2target.TypePadding + maxTypeWidth + d2target.TypePadding + maxConstraintWidth
if maxConstraintWidth != 0 {
rowsWidth += d2target.ConstraintPadding
}
dims.Width = go2.Max(12, go2.Max(headerWidth, rowsWidth))
}
@ -1608,6 +1580,24 @@ func (g *Graph) Texts() []*d2target.MText {
}
}
for _, board := range g.Layers {
for _, t := range board.Texts() {
texts = appendTextDedup(texts, t)
}
}
for _, board := range g.Scenarios {
for _, t := range board.Texts() {
texts = appendTextDedup(texts, t)
}
}
for _, board := range g.Steps {
for _, t := range board.Texts() {
texts = appendTextDedup(texts, t)
}
}
return texts
}

View file

@ -9,6 +9,8 @@ import (
"oss.terrastruct.com/d2/lib/shape"
)
const MIN_SEGMENT_LEN = 10
func (obj *Object) MoveWithDescendants(dx, dy float64) {
obj.TopLeft.X += dx
obj.TopLeft.Y += dy
@ -127,11 +129,23 @@ func (obj *Object) ShiftDescendants(dx, dy float64) {
p.Y += dy
}
} else if isSrc {
e.Route[0].X += dx
e.Route[0].Y += dy
if dx == 0 {
e.ShiftStart(dy, false)
} else if dy == 0 {
e.ShiftStart(dx, true)
} else {
e.Route[0].X += dx
e.Route[0].Y += dy
}
} else if isDst {
e.Route[len(e.Route)-1].X += dx
e.Route[len(e.Route)-1].Y += dy
if dx == 0 {
e.ShiftEnd(dy, false)
} else if dy == 0 {
e.ShiftEnd(dx, true)
} else {
e.Route[len(e.Route)-1].X += dx
e.Route[len(e.Route)-1].Y += dy
}
}
if isSrc || isDst {
@ -141,6 +155,118 @@ func (obj *Object) ShiftDescendants(dx, dy float64) {
})
}
// ShiftStart moves the starting point of the route by delta either horizontally or vertically
// if subsequent points are in line with the movement, they will be removed (unless it is the last point)
// start end
// . ├────┼────┼───┼────┼───┤ before
// . ├──dx──►
// . ├──┼───┼────┼───┤ after
func (edge *Edge) ShiftStart(delta float64, isHorizontal bool) {
position := func(p *geo.Point) float64 {
if isHorizontal {
return p.X
}
return p.Y
}
start := edge.Route[0]
next := edge.Route[1]
isIncreasing := position(start) < position(next)
if isHorizontal {
start.X += delta
} else {
start.Y += delta
}
if isIncreasing == (delta < 0) {
// nothing more to do when moving away from the next point
return
}
isAligned := func(p *geo.Point) bool {
if isHorizontal {
return p.Y == start.Y
}
return p.X == start.X
}
isPastStart := func(p *geo.Point) bool {
if delta > 0 {
return position(p) < position(start)
} else {
return position(p) > position(start)
}
}
needsRemoval := false
toRemove := make([]bool, len(edge.Route))
for i := 1; i < len(edge.Route)-1; i++ {
if !isAligned(edge.Route[i]) {
break
}
if isPastStart(edge.Route[i]) {
toRemove[i] = true
needsRemoval = true
}
}
if needsRemoval {
edge.Route = geo.RemovePoints(edge.Route, toRemove)
}
}
// ShiftEnd moves the ending point of the route by delta either horizontally or vertically
// if prior points are in line with the movement, they will be removed (unless it is the first point)
func (edge *Edge) ShiftEnd(delta float64, isHorizontal bool) {
position := func(p *geo.Point) float64 {
if isHorizontal {
return p.X
}
return p.Y
}
end := edge.Route[len(edge.Route)-1]
prev := edge.Route[len(edge.Route)-2]
isIncreasing := position(prev) < position(end)
if isHorizontal {
end.X += delta
} else {
end.Y += delta
}
if isIncreasing == (delta > 0) {
// nothing more to do when moving away from the next point
return
}
isAligned := func(p *geo.Point) bool {
if isHorizontal {
return p.Y == end.Y
}
return p.X == end.X
}
isPastEnd := func(p *geo.Point) bool {
if delta > 0 {
return position(p) < position(end)
} else {
return position(p) > position(end)
}
}
needsRemoval := false
toRemove := make([]bool, len(edge.Route))
for i := len(edge.Route) - 2; i > 0; i-- {
if !isAligned(edge.Route[i]) {
break
}
if isPastEnd(edge.Route[i]) {
toRemove[i] = true
needsRemoval = true
}
}
if needsRemoval {
edge.Route = geo.RemovePoints(edge.Route, toRemove)
}
}
// GetModifierElementAdjustments returns width/height adjustments to account for shapes with 3d or multiple
func (obj *Object) GetModifierElementAdjustments() (dx, dy float64) {
if obj.Is3D() {
@ -189,3 +315,72 @@ func (obj *Object) GetLabelTopLeft() *geo.Point {
)
return labelTL
}
func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (newStart, newEnd int) {
srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape()
// if an edge runs into an outside label, stop the edge at the label instead
overlapsOutsideLabel := false
if edge.Src.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
labelPosition := label.Position(*edge.Src.LabelPosition)
if labelPosition.IsOutside() {
labelWidth := float64(edge.Src.LabelDimensions.Width)
labelHeight := float64(edge.Src.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Src.Box, label.PADDING, labelWidth, labelHeight)
startingSegment := geo.Segment{Start: points[startIndex+1], End: points[startIndex]}
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING
if intersections := labelBox.Intersections(startingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true
// move starting segment to label intersection point
points[startIndex] = intersections[0]
startingSegment.End = intersections[0]
// if the segment becomes too short, just merge it with the next segment
if startIndex < len(points) && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex]
startIndex++
}
}
}
}
if !overlapsOutsideLabel {
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1])
}
overlapsOutsideLabel = false
if edge.Dst.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
labelPosition := label.Position(*edge.Dst.LabelPosition)
if labelPosition.IsOutside() {
labelWidth := float64(edge.Dst.LabelDimensions.Width)
labelHeight := float64(edge.Dst.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight)
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING
if intersections := labelBox.Intersections(endingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true
// move ending segment to label intersection point
points[endIndex] = intersections[0]
endingSegment.End = intersections[0]
// if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > 0 && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
}
}
}
if !overlapsOutsideLabel {
points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1])
}
return startIndex, endIndex
}

View file

@ -1,6 +1,7 @@
package d2ir
import (
"io/fs"
"strings"
"oss.terrastruct.com/d2/d2ast"
@ -9,18 +10,46 @@ import (
)
type compiler struct {
err d2parser.ParseError
err *d2parser.ParseError
fs fs.FS
// importStack is used to detect cyclic imports.
importStack []string
// importCache enables reuse of files imported multiple times.
importCache map[string]*Map
utf16 bool
}
type CompileOptions struct {
UTF16 bool
// Pass nil to disable imports.
FS fs.FS
}
func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
c.err.Errors = append(c.err.Errors, d2parser.Errorf(n, f, v...).(d2ast.Error))
}
func Compile(ast *d2ast.Map) (*Map, error) {
c := &compiler{}
func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) {
if opts == nil {
opts = &CompileOptions{}
}
c := &compiler{
err: &d2parser.ParseError{},
fs: opts.FS,
importCache: make(map[string]*Map),
utf16: opts.UTF16,
}
m := &Map{}
m.initRoot()
m.parent.(*Field).References[0].Context.Scope = ast
c.pushImportStack(&d2ast.Import{
Path: []*d2ast.StringBox{d2ast.RawStringBox(ast.GetRange().Path, true)},
})
defer c.popImportStack()
c.compileMap(m, ast)
c.compileClasses(m)
if !c.err.Empty() {
@ -85,6 +114,25 @@ func (c *compiler) compileMap(dst *Map, ast *d2ast.Map) {
Scope: ast,
ScopeMap: dst,
})
case n.Import != nil:
impn, ok := c._import(n.Import)
if !ok {
continue
}
if impn.Map() == nil {
c.errorf(n.Import, "cannot spread import non map into map")
continue
}
OverlayMap(dst, impn.Map())
if impnf, ok := impn.(*Field); ok {
if impnf.Primary_ != nil {
dstf := ParentField(dst)
if dstf != nil {
dstf.Primary_ = impnf.Primary_
}
}
}
case n.Substitution != nil:
panic("TODO")
}
@ -145,6 +193,46 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
case BoardScenario, BoardStep:
c.compileClasses(f.Map())
}
} else if refctx.Key.Value.Import != nil {
n, ok := c._import(refctx.Key.Value.Import)
if !ok {
return
}
switch n := n.(type) {
case *Field:
if n.Primary_ != nil {
f.Primary_ = n.Primary_.Copy(f).(*Scalar)
}
if n.Composite != nil {
f.Composite = n.Composite.Copy(f).(Composite)
}
case *Map:
f.Composite = &Map{
parent: f,
}
switch NodeBoardKind(f) {
case BoardScenario:
c.overlay(ParentBoard(f).Map(), f)
case BoardStep:
stepsMap := ParentMap(f)
for i := range stepsMap.Fields {
if stepsMap.Fields[i] == f {
if i == 0 {
c.overlay(ParentBoard(f).Map(), f)
} else {
c.overlay(stepsMap.Fields[i-1].Map(), f)
}
break
}
}
}
OverlayMap(f.Map(), n)
c.updateLinks(f.Map())
switch NodeBoardKind(f) {
case BoardScenario, BoardStep:
c.compileClasses(f.Map())
}
}
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
// If the link is a board, we need to transform it into an absolute path.
if f.Name == "link" {
@ -157,6 +245,24 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
}
}
func (c *compiler) updateLinks(m *Map) {
for _, f := range m.Fields {
if f.Name == "link" {
bida := BoardIDA(f)
aida := IDA(f)
if len(bida) != len(aida) {
prependIDA := aida[:len(aida)-len(bida)]
kp := d2ast.MakeKeyPath(prependIDA)
s := d2format.Format(kp) + strings.TrimPrefix(f.Primary_.Value.ScalarString(), "root")
f.Primary_.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString(s)).ScalarBox().Unbox()
}
}
if f.Map() != nil {
c.updateLinks(f.Map())
}
}
}
func (c *compiler) compileLink(refctx *RefContext) {
val := refctx.Key.Value.ScalarBox().Unbox().ScalarString()
link, err := d2parser.ParseKey(val)
@ -216,7 +322,7 @@ func (c *compiler) compileLink(refctx *RefContext) {
// Create the absolute path by appending scope path with value specified
scopeIDA = append(scopeIDA, linkIDA...)
kp := d2ast.MakeKeyPath(scopeIDA)
refctx.Key.Value = d2ast.MakeValueBox(d2ast.RawString(d2format.Format(kp), true))
refctx.Key.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString(d2format.Format(kp)))
}
func (c *compiler) compileEdges(refctx *RefContext) {
@ -330,6 +436,34 @@ func (c *compiler) compileArray(dst *Array, a *d2ast.Array) {
parent: dst,
Value: v,
}
case *d2ast.Import:
n, ok := c._import(v)
if !ok {
continue
}
switch n := n.(type) {
case *Field:
if v.Spread {
a, ok := n.Composite.(*Array)
if !ok {
c.errorf(v, "can only spread import array into array")
continue
}
dst.Values = append(dst.Values, a.Values...)
continue
}
if n.Composite != nil {
irv = n.Composite
} else {
irv = n.Primary_
}
case *Map:
if v.Spread {
c.errorf(v, "can only spread import array into array")
continue
}
irv = n
}
case *d2ast.Substitution:
// panic("TODO")
}

View file

@ -9,6 +9,7 @@ import (
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/mapfs"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir"
@ -24,6 +25,7 @@ func TestCompile(t *testing.T) {
t.Run("layers", testCompileLayers)
t.Run("scenarios", testCompileScenarios)
t.Run("steps", testCompileSteps)
t.Run("imports", testCompileImports)
}
type testCase struct {
@ -45,10 +47,26 @@ func compile(t testing.TB, text string) (*d2ir.Map, error) {
t.Helper()
d2Path := fmt.Sprintf("%v.d2", t.Name())
ast, err := d2parser.Parse(d2Path, strings.NewReader(text), nil)
assert.Success(t, err)
return compileFS(t, d2Path, map[string]string{d2Path: text})
}
m, err := d2ir.Compile(ast)
func compileFS(t testing.TB, path string, mfs map[string]string) (*d2ir.Map, error) {
t.Helper()
ast, err := d2parser.Parse(path, strings.NewReader(mfs[path]), nil)
if err != nil {
return nil, err
}
fs, err := mapfs.New(mfs)
assert.Success(t, err)
t.Cleanup(func() {
err = fs.Close()
assert.Success(t, err)
})
m, err := d2ir.Compile(ast, &d2ir.CompileOptions{
FS: fs,
})
if err != nil {
return nil, err
}

View file

@ -25,7 +25,7 @@ type Node interface {
Map() *Map
Equal(n2 Node) bool
ast() d2ast.Node
AST() d2ast.Node
fmt.Stringer
LastRef() Reference
@ -100,11 +100,11 @@ func (n *Map) value() {}
func (n *Array) composite() {}
func (n *Map) composite() {}
func (n *Scalar) String() string { return d2format.Format(n.ast()) }
func (n *Field) String() string { return d2format.Format(n.ast()) }
func (n *Edge) String() string { return d2format.Format(n.ast()) }
func (n *Array) String() string { return d2format.Format(n.ast()) }
func (n *Map) String() string { return d2format.Format(n.ast()) }
func (n *Scalar) String() string { return d2format.Format(n.AST()) }
func (n *Field) String() string { return d2format.Format(n.AST()) }
func (n *Edge) String() string { return d2format.Format(n.AST()) }
func (n *Array) String() string { return d2format.Format(n.AST()) }
func (n *Map) String() string { return d2format.Format(n.AST()) }
func (n *Scalar) LastRef() Reference { return parentRef(n) }
func (n *Map) LastRef() Reference { return parentRef(n) }
@ -855,11 +855,11 @@ func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) {
return e, nil
}
func (s *Scalar) ast() d2ast.Node {
func (s *Scalar) AST() d2ast.Node {
return s.Value
}
func (f *Field) ast() d2ast.Node {
func (f *Field) AST() d2ast.Node {
k := &d2ast.Key{
Key: &d2ast.KeyPath{
Path: []*d2ast.StringBox{
@ -869,16 +869,16 @@ func (f *Field) ast() d2ast.Node {
}
if f.Primary_ != nil {
k.Primary = d2ast.MakeValueBox(f.Primary_.ast().(d2ast.Value)).ScalarBox()
k.Primary = d2ast.MakeValueBox(f.Primary_.AST().(d2ast.Value)).ScalarBox()
}
if f.Composite != nil {
k.Value = d2ast.MakeValueBox(f.Composite.ast().(d2ast.Value))
k.Value = d2ast.MakeValueBox(f.Composite.AST().(d2ast.Value))
}
return k
}
func (e *Edge) ast() d2ast.Node {
func (e *Edge) AST() d2ast.Node {
astEdge := &d2ast.Edge{}
astEdge.Src = d2ast.MakeKeyPath(e.ID.SrcPath)
@ -895,27 +895,27 @@ func (e *Edge) ast() d2ast.Node {
}
if e.Primary_ != nil {
k.Primary = d2ast.MakeValueBox(e.Primary_.ast().(d2ast.Value)).ScalarBox()
k.Primary = d2ast.MakeValueBox(e.Primary_.AST().(d2ast.Value)).ScalarBox()
}
if e.Map_ != nil {
k.Value = d2ast.MakeValueBox(e.Map_.ast().(*d2ast.Map))
k.Value = d2ast.MakeValueBox(e.Map_.AST().(*d2ast.Map))
}
return k
}
func (a *Array) ast() d2ast.Node {
func (a *Array) AST() d2ast.Node {
if a == nil {
return nil
}
astArray := &d2ast.Array{}
for _, av := range a.Values {
astArray.Nodes = append(astArray.Nodes, d2ast.MakeArrayNodeBox(av.ast().(d2ast.ArrayNode)))
astArray.Nodes = append(astArray.Nodes, d2ast.MakeArrayNodeBox(av.AST().(d2ast.ArrayNode)))
}
return astArray
}
func (m *Map) ast() d2ast.Node {
func (m *Map) AST() d2ast.Node {
if m == nil {
return nil
}
@ -926,10 +926,10 @@ func (m *Map) ast() d2ast.Node {
astMap.Range = d2ast.MakeRange(",1:0:0-2:0:0")
}
for _, f := range m.Fields {
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.ast().(d2ast.MapNode)))
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.AST().(d2ast.MapNode)))
}
for _, e := range m.Edges {
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(e.ast().(d2ast.MapNode)))
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(e.AST().(d2ast.MapNode)))
}
return astMap
}

145
d2ir/import.go Normal file
View file

@ -0,0 +1,145 @@
package d2ir
import (
"bufio"
"io/fs"
"os"
"path"
"strings"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2parser"
)
func (c *compiler) pushImportStack(imp *d2ast.Import) (string, bool) {
impPath := imp.PathWithPre()
if impPath == "" && imp.Range.Path != "" {
c.errorf(imp, "imports must specify a path to import")
return "", false
}
if len(c.importStack) > 0 {
if path.IsAbs(impPath) {
c.errorf(imp, "import paths must be relative")
return "", false
}
if path.Ext(impPath) != ".d2" {
impPath += ".d2"
}
// Imports are always relative to the importing file.
impPath = path.Join(path.Dir(c.importStack[len(c.importStack)-1]), impPath)
}
for i, p := range c.importStack {
if impPath == p {
c.errorf(imp, "detected cyclic import chain: %s", formatCyclicChain(c.importStack[i:]))
return "", false
}
}
c.importStack = append(c.importStack, impPath)
return impPath, true
}
func (c *compiler) popImportStack() {
c.importStack = c.importStack[:len(c.importStack)-1]
}
func formatCyclicChain(cyclicChain []string) string {
var b strings.Builder
for _, p := range cyclicChain {
b.WriteString(p)
b.WriteString(" -> ")
}
b.WriteString(cyclicChain[0])
return b.String()
}
// Returns either *Map or *Field.
func (c *compiler) _import(imp *d2ast.Import) (Node, bool) {
ir, ok := c.__import(imp)
if !ok {
return nil, false
}
nilScopeMap(ir)
if len(imp.IDA()) > 0 {
f := ir.GetField(imp.IDA()...)
if f == nil {
c.errorf(imp, "import key %q doesn't exist inside import", imp.IDA())
return nil, false
}
return f, true
}
return ir, true
}
func (c *compiler) __import(imp *d2ast.Import) (*Map, bool) {
impPath, ok := c.pushImportStack(imp)
if !ok {
return nil, false
}
defer c.popImportStack()
ir, ok := c.importCache[impPath]
if ok {
return ir, true
}
var f fs.File
var err error
if c.fs == nil {
f, err = os.Open(impPath)
} else {
f, err = c.fs.Open(impPath)
}
if err != nil {
c.errorf(imp, "failed to import %q: %v", impPath, err)
return nil, false
}
defer f.Close()
ast, err := d2parser.Parse(impPath, bufio.NewReader(f), &d2parser.ParseOptions{
UTF16: c.utf16,
ParseError: c.err,
})
if err != nil {
return nil, false
}
ir = &Map{}
ir.initRoot()
ir.parent.(*Field).References[0].Context.Scope = ast
c.compileMap(ir, ast)
c.importCache[impPath] = ir
return ir, true
}
func nilScopeMap(n Node) {
switch n := n.(type) {
case *Map:
for _, f := range n.Fields {
nilScopeMap(f)
}
for _, e := range n.Edges {
nilScopeMap(e)
}
case *Edge:
for _, r := range n.References {
r.Context.ScopeMap = nil
}
if n.Map() != nil {
nilScopeMap(n.Map())
}
case *Field:
for _, r := range n.References {
r.Context.ScopeMap = nil
}
if n.Map() != nil {
nilScopeMap(n.Map())
}
}
}

214
d2ir/import_test.go Normal file
View file

@ -0,0 +1,214 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/d2/d2ir"
)
func testCompileImports(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "value",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "x: @x.d2",
"x.d2": `shape: circle
label: meow`,
})
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 2, 0, nil, "x")
assertQuery(t, m, 0, 0, "circle", "x.shape")
assertQuery(t, m, 0, 0, "meow", "x.label")
},
},
{
name: "nested/map",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "x: @x.y",
"x.d2": `y: {
shape: circle
label: meow
}`,
})
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 2, 0, nil, "x")
assertQuery(t, m, 0, 0, "circle", "x.shape")
assertQuery(t, m, 0, 0, "meow", "x.label")
},
},
{
name: "nested/array",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "x: @x.y",
"x.d2": `y: [1, 2]`,
})
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "")
x := assertQuery(t, m, 0, 0, nil, "x")
xf, ok := x.(*d2ir.Field)
assert.True(t, ok)
assert.Equal(t, `[1, 2]`, xf.Composite.String())
},
},
{
name: "nested/scalar",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "x: @x.y",
"x.d2": `y: meow`,
})
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "")
assertQuery(t, m, 0, 0, "meow", "x")
},
},
{
name: "boards",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": `x.link: layers.x; layers: { x: @x }`,
"x.d2": `y.link: layers.y; layers: { y: @y }`,
"y.d2": `meow`,
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, "root.layers.x", "x.link")
assertQuery(t, m, 0, 0, "root.layers.x.layers.y", "layers.x.y.link")
},
},
{
name: "steps-inheritence",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": `z; steps: { 1: @x; 2: @y }; scenarios: { x: @x; y: @y }`,
"x.d2": `a`,
"y.d2": `b`,
})
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "scenarios.x")
assertQuery(t, m, 2, 0, nil, "scenarios.y")
assertQuery(t, m, 2, 0, nil, "steps.1")
assertQuery(t, m, 3, 0, nil, "steps.2")
},
},
{
name: "spread",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.d2",
"x.d2": "x: wowa",
})
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "")
assertQuery(t, m, 0, 0, "wowa", "x")
},
},
{
name: "nested/spread",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.y",
"x.d2": "y: { jon; jan }",
})
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, nil, "jan")
assertQuery(t, m, 0, 0, nil, "jon")
},
},
{
name: "nested/spread_primary",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "q: { ...@x.y }",
"x.d2": "y: meow { jon; jan }",
})
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 2, 0, "meow", "q")
assertQuery(t, m, 0, 0, nil, "q.jan")
assertQuery(t, m, 0, 0, nil, "q.jon")
},
},
}
runa(t, tca)
t.Run("errors", func(t *testing.T) {
tca := []testCase{
{
name: "not_exist",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.d2",
})
assert.ErrorString(t, err, `index.d2:1:1: failed to import "x.d2": open x.d2: no such file or directory`)
},
},
{
name: "escape",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@'./../x.d2'",
})
assert.ErrorString(t, err, `index.d2:1:1: failed to import "../x.d2": stat ../x.d2: invalid argument`)
},
},
{
name: "absolute",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@/x.d2",
})
assert.ErrorString(t, err, `index.d2:1:1: import paths must be relative`)
},
},
{
name: "parse",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.d2",
"x.d2": "x<><><<>q",
})
assert.ErrorString(t, err, `x.d2:1:1: connection missing destination
x.d2:1:4: connection missing source
x.d2:1:4: connection missing destination
x.d2:1:6: connection missing source
x.d2:1:6: connection missing destination
x.d2:1:7: connection missing source`)
},
},
{
name: "cyclic",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x",
"x.d2": "...@y",
"y.d2": "...@q",
"q.d2": "...@x",
})
assert.ErrorString(t, err, `q.d2:1:1: detected cyclic import chain: x.d2 -> y.d2 -> q.d2 -> x.d2`)
},
},
{
name: "spread_non_map",
run: func(t testing.TB) {
_, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@x.y",
"x.d2": "y: meow",
})
assert.ErrorString(t, err, `index.d2:1:1: cannot spread import non map into map`)
},
},
}
runa(t, tca)
})
}

View file

@ -21,7 +21,6 @@ import (
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/label"
"oss.terrastruct.com/d2/lib/log"
"oss.terrastruct.com/d2/lib/shape"
)
//go:embed setup.js
@ -31,9 +30,8 @@ var setupJS string
var dagreJS string
const (
MIN_SEGMENT_LEN = 10
MIN_RANK_SEP = 60
EDGE_LABEL_GAP = 20
MIN_RANK_SEP = 60
EDGE_LABEL_GAP = 20
)
type ConfigurableOpts struct {
@ -384,7 +382,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if isHorizontal && e.Src.Parent != g.Root && e.Dst.Parent != g.Root {
moveWholeEdge = true
} else {
e.Route[0].Y += stepSize
e.ShiftStart(stepSize, false)
}
}
}
@ -393,7 +391,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if isHorizontal && e.Dst.Parent != g.Root && e.Src.Parent != g.Root {
moveWholeEdge = true
} else {
e.Route[len(e.Route)-1].Y += stepSize
e.ShiftEnd(stepSize, false)
}
}
}
@ -433,7 +431,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
// to fix this, we try extending the previous segment into the shape instead of having a very short segment
if !start.Equals(points[0]) && startIndex+2 < len(points) {
newStartingSegment := *geo.NewSegment(start, points[startIndex+1])
if newStartingSegment.Length() < MIN_SEGMENT_LEN {
if newStartingSegment.Length() < d2graph.MIN_SEGMENT_LEN {
// we don't want a very short segment right next to the source because it will mess up the arrowhead
// instead we want to extend the next segment into the shape border if possible
nextStart := points[startIndex+1]
@ -442,30 +440,32 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
// Note: in other direction to extend towards source
nextSegment := *geo.NewSegment(nextStart, nextEnd)
v := nextSegment.ToVector()
extendedStart := nextEnd.ToVector().Add(v.AddLength(MIN_SEGMENT_LEN)).ToPoint()
extendedStart := nextEnd.ToVector().Add(v.AddLength(d2graph.MIN_SEGMENT_LEN)).ToPoint()
extended := *geo.NewSegment(nextEnd, extendedStart)
if intersections := edge.Src.Box.Intersections(extended); len(intersections) > 0 {
start = intersections[0]
startIndex += 1
startIndex++
points[startIndex] = intersections[0]
start = points[startIndex]
}
}
}
if !end.Equals(points[len(points)-1]) && endIndex-2 >= 0 {
newEndingSegment := *geo.NewSegment(end, points[endIndex-1])
if newEndingSegment.Length() < MIN_SEGMENT_LEN {
if newEndingSegment.Length() < d2graph.MIN_SEGMENT_LEN {
// extend the prev segment into the shape border if possible
prevStart := points[endIndex-2]
prevEnd := points[endIndex-1]
prevSegment := *geo.NewSegment(prevStart, prevEnd)
v := prevSegment.ToVector()
extendedEnd := prevStart.ToVector().Add(v.AddLength(MIN_SEGMENT_LEN)).ToPoint()
extendedEnd := prevStart.ToVector().Add(v.AddLength(d2graph.MIN_SEGMENT_LEN)).ToPoint()
extended := *geo.NewSegment(prevStart, extendedEnd)
if intersections := edge.Dst.Box.Intersections(extended); len(intersections) > 0 {
end = intersections[0]
endIndex -= 1
endIndex--
points[endIndex] = intersections[0]
end = points[endIndex]
}
}
}
@ -489,41 +489,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape()
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, start, points[startIndex+1])
// if an edge to a container runs into its label, stop the edge at the label instead
overlapsContainerLabel := false
if edge.Dst.IsContainer() && edge.Dst.Label.Value != "" && !dstShape.Is(shape.TEXT_TYPE) {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
labelWidth := float64(edge.Dst.LabelDimensions.Width)
labelHeight := float64(edge.Dst.LabelDimensions.Height)
labelTL := label.Position(*edge.Dst.LabelPosition).
GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight)
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING
if intersections := labelBox.Intersections(endingSegment); len(intersections) > 0 {
overlapsContainerLabel = true
// move ending segment to label intersection point
points[endIndex] = intersections[0]
endingSegment.End = intersections[0]
// if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > 0 && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
}
}
if !overlapsContainerLabel {
points[endIndex] = shape.TraceToShapeBorder(dstShape, end, points[endIndex-1])
}
startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex)
points = points[startIndex : endIndex+1]
// build a curved path from the dagre route
@ -578,22 +544,7 @@ func getEdgeEndpoints(g *d2graph.Graph, edge *d2graph.Edge) (*d2graph.Object, *d
}
dst := edge.Dst
for len(dst.Children) > 0 && dst.Class == nil && dst.SQLTable == nil {
dst = dst.ChildrenArray[0]
// We want to get the top node of destinations
for _, child := range dst.ChildrenArray {
isHead := true
for _, e := range g.Edges {
if inContainer(e.Src, child) != nil && inContainer(e.Dst, dst) != nil {
isHead = false
break
}
}
if isHead {
dst = child
break
}
}
dst = getLongestEdgeChainHead(g, dst)
}
if edge.SrcArrow && !edge.DstArrow {
// for `b <- a`, edge.Edge is `a -> b` and we expect this routing result
@ -641,8 +592,73 @@ func generateAddEdgeLine(fromID, toID, edgeID string, width, height int) string
return fmt.Sprintf("g.setEdge({v:`%s`, w:`%s`, name:`%s`}, { width:%d, height:%d, labelpos: `c` });\n", escapeID(fromID), escapeID(toID), escapeID(edgeID), width, height)
}
// getLongestEdgeChainHead finds the longest chain in a container and gets its head
// If there are multiple chains of the same length, get the head closest to the center
func getLongestEdgeChainHead(g *d2graph.Graph, container *d2graph.Object) *d2graph.Object {
rank := make(map[*d2graph.Object]int)
chainLength := make(map[*d2graph.Object]int)
for _, obj := range container.ChildrenArray {
isHead := true
for _, e := range g.Edges {
if inContainer(e.Src, container) != nil && inContainer(e.Dst, obj) != nil {
isHead = false
break
}
}
if !isHead {
continue
}
rank[obj] = 1
chainLength[obj] = 1
// BFS
queue := []*d2graph.Object{obj}
visited := make(map[*d2graph.Object]struct{})
for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]
if _, ok := visited[curr]; ok {
continue
}
visited[curr] = struct{}{}
for _, e := range g.Edges {
child := inContainer(e.Dst, container)
if child == curr {
continue
}
if child != nil && inContainer(e.Src, curr) != nil {
if rank[curr]+1 > rank[child] {
rank[child] = rank[curr] + 1
chainLength[obj] = go2.Max(chainLength[obj], rank[child])
}
queue = append(queue, child)
}
}
}
}
max := int(math.MinInt32)
for _, obj := range container.ChildrenArray {
if chainLength[obj] > max {
max = chainLength[obj]
}
}
var heads []*d2graph.Object
for i, obj := range container.ChildrenArray {
if rank[obj] == 1 && chainLength[obj] == max {
heads = append(heads, container.ChildrenArray[i])
}
}
if len(heads) > 0 {
return heads[int(math.Floor(float64(len(heads))/2.0))]
}
return container.ChildrenArray[0]
}
// getLongestEdgeChainTail gets the node at the end of the longest edge chain, because that will be the end of the container
// and is what external connections should connect with
// and is what external connections should connect with.
// If there are multiple of same length, get the one closest to the middle
func getLongestEdgeChainTail(g *d2graph.Graph, container *d2graph.Object) *d2graph.Object {
rank := make(map[*d2graph.Object]int)
@ -681,14 +697,20 @@ func getLongestEdgeChainTail(g *d2graph.Graph, container *d2graph.Object) *d2gra
}
}
max := int(math.MinInt32)
var tail *d2graph.Object
for _, obj := range container.ChildrenArray {
if rank[obj] >= max {
if rank[obj] > max {
max = rank[obj]
tail = obj
}
}
return tail
var tails []*d2graph.Object
for i, obj := range container.ChildrenArray {
if rank[obj] == max {
tails = append(tails, container.ChildrenArray[i])
}
}
return tails[int(math.Floor(float64(len(tails))/2.0))]
}
func inContainer(obj, container *d2graph.Object) *d2graph.Object {

View file

@ -501,12 +501,8 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape()
// trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1])
points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1])
startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex)
points = points[startIndex : endIndex+1]
if edge.Label.Value != "" {
edge.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))

View file

@ -590,7 +590,7 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
nCuts = gd.rows - 1
}
if nCuts == 0 {
return genLayout(gd.objects, nil)
return GenLayout(gd.objects, nil)
}
var bestLayout [][]*d2graph.Object
@ -692,7 +692,7 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
// . A │ B │ C D E └────────────┘
// of these divisions, find the layout with rows closest to the targetSize
tryDivision := func(division []int) bool {
layout := genLayout(gd.objects, division)
layout := GenLayout(gd.objects, division)
dist := getDistToTarget(layout, targetSize, float64(gd.horizontalGap), float64(gd.verticalGap), columns)
if dist < bestDist {
bestLayout = layout
@ -786,8 +786,10 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
size = o.Width
}
if rowSize == 0 {
// if a single object meets the target size, end the row here
if size > targetSize-debt {
fastDivision = append(fastDivision, i-1)
// cut row with just this object
fastDivision = append(fastDivision, i)
// we build up a debt of distance past the target size across rows
newDebt := size - targetSize
debt += newDebt
@ -797,7 +799,11 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
continue
}
// debt is paid by decreasing threshold to start new row and ending below targetSize
if rowSize+(gap+size)/2. > targetSize-debt {
if rowSize+gap+(size)/2. > targetSize-debt {
// start a new row before this object since it is mostly past the target size
// . size
// ├...row─┼gap┼───┼───┤
// ├──targetSize──┤ (debt=0)
fastDivision = append(fastDivision, i-1)
newDebt := rowSize - targetSize
debt += newDebt
@ -807,7 +813,7 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
}
}
if len(fastDivision) == nCuts {
layout = genLayout(gd.objects, fastDivision)
layout = GenLayout(gd.objects, fastDivision)
}
return layout
@ -885,7 +891,9 @@ func iterDivisions(objects []*d2graph.Object, nCuts int, f iterDivision, check c
}
// generate a grid of objects from the given cut indices
func genLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
// each cut index applies after the object at that index
// e.g. [0 1 2 3 4 5 6 7] with cutIndices [0, 2, 6] => [[0], [1, 2], [3,4,5,6], [7]]
func GenLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
layout := make([][]*d2graph.Object, len(cutIndices)+1)
objIndex := 0
for i := 0; i <= len(cutIndices); i++ {
@ -916,6 +924,13 @@ func getDistToTarget(layout [][]*d2graph.Object, targetSize float64, horizontalG
rowSize += o.Width + horizontalGap
}
}
if len(row) > 0 {
if columns {
rowSize -= verticalGap
} else {
rowSize -= horizontalGap
}
}
totalDelta += math.Abs(rowSize - targetSize)
}
return totalDelta

View file

@ -0,0 +1,72 @@
package d2grid_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2grid"
)
func TestGenLayout(t *testing.T) {
objects := []*d2graph.Object{
{ID: "1"},
{ID: "2"},
{ID: "3"},
{ID: "4"},
{ID: "5"},
{ID: "6"},
{ID: "7"},
{ID: "8"},
}
var cutIndices []int
var layout [][]*d2graph.Object
cutIndices = []int{0}
layout = d2grid.GenLayout(objects, cutIndices)
fmt.Printf("layout %v\n", len(layout))
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 1, len(layout[0]), "expected first row to be 1 object")
assert.Equalf(t, 7, len(layout[1]), "expected second row to be 7 objects")
assert.Equalf(t, objects[0].ID, layout[0][0].ID, "expected first object to be 1")
cutIndices = []int{6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 7, len(layout[0]), "expected first row to be 7 objects")
assert.Equalf(t, 1, len(layout[1]), "expected second row to be 1 object")
cutIndices = []int{0, 6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
assert.Equalf(t, 1, len(layout[0]), "expected first row to be 1 objects")
assert.Equalf(t, 6, len(layout[1]), "expected second row to be 6 objects")
assert.Equalf(t, 1, len(layout[2]), "expected second row to be 1 object")
cutIndices = []int{1, 5}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
assert.Equalf(t, 2, len(layout[0]), "expected first row to be 2 objects")
assert.Equalf(t, 4, len(layout[1]), "expected second row to be 6 objects")
assert.Equalf(t, 2, len(layout[2]), "expected second row to be 2 object")
cutIndices = []int{5}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 6, len(layout[0]), "expected first row to be 6 objects")
assert.Equalf(t, 2, len(layout[1]), "expected second row to be 2 object")
cutIndices = []int{1}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 2, len(layout[0]), "expected first row to be 2 object")
assert.Equalf(t, 6, len(layout[1]), "expected second row to be 6 objects")
cutIndices = []int{0, 1, 2, 3, 4, 5, 6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
for i := range layout {
assert.Equalf(t, 1, len(layout[i]), "expected row %d to be 1 object", i)
}
}

View file

@ -3,6 +3,7 @@ package d2lib
import (
"context"
"errors"
"io/fs"
"os"
"strings"
@ -20,6 +21,7 @@ import (
type CompileOptions struct {
UTF16 bool
FS fs.FS
MeasuredTexts []*d2target.MText
Ruler *textmeasure.Ruler
Layout func(context.Context, *d2graph.Graph) error
@ -31,6 +33,8 @@ type CompileOptions struct {
// - pre-measured (web setting)
// TODO maybe some will want to configure code font too, but that's much lower priority
FontFamily *d2fonts.FontFamily
InputPath string
}
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) {
@ -38,8 +42,9 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target
opts = &CompileOptions{}
}
g, err := d2compiler.Compile("", strings.NewReader(input), &d2compiler.CompileOptions{
g, err := d2compiler.Compile(opts.InputPath, strings.NewReader(input), &d2compiler.CompileOptions{
UTF16: opts.UTF16,
FS: opts.FS,
})
if err != nil {
return nil, nil, err

View file

@ -347,7 +347,7 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error {
}
}
ir, err := d2ir.Compile(g.AST)
ir, err := d2ir.Compile(g.AST, nil)
if err != nil {
return err
}

View file

@ -15,7 +15,8 @@ import (
)
type ParseOptions struct {
UTF16 bool
UTF16 bool
ParseError *ParseError
}
// Parse parses a .d2 Map in r.
@ -42,6 +43,10 @@ func Parse(path string, r io.RuneReader, opts *ParseOptions) (*d2ast.Map, error)
reader: r,
utf16: opts.UTF16,
err: opts.ParseError,
}
if p.err == nil {
p.err = &ParseError{}
}
m := p.parseMap(true)
@ -54,6 +59,7 @@ func Parse(path string, r io.RuneReader, opts *ParseOptions) (*d2ast.Map, error)
func ParseKey(key string) (*d2ast.KeyPath, error) {
p := &parser{
reader: strings.NewReader(key),
err: &ParseError{},
}
k := p.parseKey()
@ -69,6 +75,7 @@ func ParseKey(key string) (*d2ast.KeyPath, error) {
func ParseMapKey(mapKey string) (*d2ast.Key, error) {
p := &parser{
reader: strings.NewReader(mapKey),
err: &ParseError{},
}
mk := p.parseMapKey()
@ -84,6 +91,7 @@ func ParseMapKey(mapKey string) (*d2ast.Key, error) {
func ParseValue(value string) (d2ast.Value, error) {
p := &parser{
reader: strings.NewReader(value),
err: &ParseError{},
}
v := p.parseValue()
@ -117,18 +125,16 @@ type parser struct {
lookaheadPos d2ast.Position
ioerr bool
err ParseError
err *ParseError
inEdgeGroup bool
depth int
}
// TODO: remove ioerr, just sort (with Append) should be fine but filter non ast errors in API
// TODO: rename to Error and make existing Error a private type errorWithRange
type ParseError struct {
IOError *d2ast.Error `json:"ioerr"`
Errors []d2ast.Error `json:"errs"`
Errors []d2ast.Error `json:"errs"`
}
func Errorf(n d2ast.Node, f string, v ...interface{}) error {
@ -140,17 +146,17 @@ func Errorf(n d2ast.Node, f string, v ...interface{}) error {
}
}
func (pe ParseError) Empty() bool {
return pe.IOError == nil && len(pe.Errors) == 0
func (pe *ParseError) Empty() bool {
if pe == nil {
return true
}
return len(pe.Errors) == 0
}
func (pe ParseError) Error() string {
func (pe *ParseError) Error() string {
var sb strings.Builder
if pe.IOError != nil {
sb.WriteString(pe.IOError.Error())
}
for i, err := range pe.Errors {
if pe.IOError != nil || i > 0 {
if i > 0 {
sb.WriteByte('\n')
}
sb.WriteString(err.Error())
@ -191,14 +197,14 @@ func (p *parser) _readRune() (r rune, eof bool) {
if err != nil {
p.ioerr = true
if err != io.EOF {
p.err.IOError = &d2ast.Error{
p.err.Errors = append(p.err.Errors, d2ast.Error{
Range: d2ast.Range{
Path: p.path,
Start: p.readerPos,
End: p.readerPos,
},
Message: fmt.Sprintf("io error: %v", err),
}
})
}
p.rewind()
return 0, true
@ -355,7 +361,7 @@ func (p *parser) parseMap(isFileMap bool) *d2ast.Map {
Start: p.pos,
},
}
defer m.Range.End.From(&p.readerPos)
defer m.Range.End.From(&p.pos)
if !isFileMap {
m.Range.Start = m.Range.Start.Subtract('{', p.utf16)
@ -448,17 +454,30 @@ func (p *parser) parseMapNode(r rune) d2ast.MapNodeBox {
box.BlockComment = p.parseBlockComment()
return box
case '.':
s, eof := p.peekn(3)
s, eof := p.peekn(2)
if eof {
break
}
if s != "..$" {
if s != ".." {
p.rewind()
break
}
p.commit()
box.Substitution = p.parseSubstitution(true)
return box
r, eof := p.peek()
if eof {
break
}
if r == '$' {
p.commit()
box.Substitution = p.parseSubstitution(true)
return box
}
if r == '@' {
p.commit()
box.Import = p.parseImport(true)
return box
}
p.rewind()
break
}
p.replay(r)
@ -614,7 +633,7 @@ func (p *parser) parseMapKey() (mk *d2ast.Key) {
}
}()
// Check for ampersand.
// Check for ampersand/@.
r, eof := p.peek()
if eof {
return mk
@ -948,6 +967,9 @@ func (p *parser) parseKey() (k *d2ast.KeyPath) {
if s == nil {
return k
}
if sb.UnquotedString != nil && strings.HasPrefix(s.ScalarString(), "@") {
p.errorf(s.GetRange().Start, s.GetRange().End, "%s is not a valid import, did you mean ...%[2]s?", s.ScalarString())
}
if len(k.Path) == 0 {
k.Range.Start = s.GetRange().Start
@ -1024,6 +1046,14 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
s.Value = append(s.Value, d2ast.InterpolationBox{String: &sv, StringRaw: &rawv})
}()
_s, eof := p.peekn(4)
p.rewind()
if !eof {
if _s == "...@" {
p.errorf(p.pos, p.pos.AdvanceString("...@", p.utf16), "unquoted strings cannot begin with ...@ as that's import spread syntax")
}
}
for {
r, eof := p.peek()
if eof {
@ -1502,17 +1532,30 @@ func (p *parser) parseArrayNode(r rune) d2ast.ArrayNodeBox {
box.BlockComment = p.parseBlockComment()
return box
case '.':
s, eof := p.peekn(3)
s, eof := p.peekn(2)
if eof {
break
}
if s != "..$" {
if s != ".." {
p.rewind()
break
}
p.commit()
box.Substitution = p.parseSubstitution(true)
return box
r, eof := p.peek()
if eof {
break
}
if r == '$' {
p.commit()
box.Substitution = p.parseSubstitution(true)
return box
}
if r == '@' {
p.commit()
box.Import = p.parseImport(true)
return box
}
p.rewind()
break
}
p.replay(r)
@ -1529,6 +1572,7 @@ func (p *parser) parseArrayNode(r rune) d2ast.ArrayNodeBox {
box.BlockString = vbox.BlockString
box.Array = vbox.Array
box.Map = vbox.Map
box.Import = vbox.Import
return box
}
@ -1549,6 +1593,9 @@ func (p *parser) parseValue() d2ast.ValueBox {
case '{':
box.Map = p.parseMap(false)
return box
case '@':
box.Import = p.parseImport(false)
return box
}
p.replay(r)
@ -1659,6 +1706,46 @@ func (p *parser) parseSubstitution(spread bool) *d2ast.Substitution {
return subst
}
func (p *parser) parseImport(spread bool) *d2ast.Import {
imp := &d2ast.Import{
Range: d2ast.Range{
Path: p.path,
Start: p.pos.SubtractString("$", p.utf16),
},
Spread: spread,
}
defer imp.Range.End.From(&p.pos)
if imp.Spread {
imp.Range.Start = imp.Range.Start.SubtractString("...", p.utf16)
}
var pre strings.Builder
for {
r, eof := p.peek()
if eof {
break
}
if r != '.' && r != '/' {
p.rewind()
break
}
pre.WriteRune(r)
p.commit()
}
imp.Pre = pre.String()
k := p.parseKey()
if k == nil {
return imp
}
if k.Path[0].UnquotedString != nil && len(k.Path) > 1 && k.Path[1].UnquotedString != nil && k.Path[1].Unbox().ScalarString() == "d2" {
k.Path = append(k.Path[:1], k.Path[2:]...)
}
imp.Path = k.Path
return imp
}
// func marshalKey(k *d2ast.Key) string {
// var sb strings.Builder
// for i, s := range k.Path {

View file

@ -13,20 +13,19 @@ import (
"oss.terrastruct.com/d2/d2parser"
)
type testCase struct {
name string
text string
assert func(t testing.TB, ast *d2ast.Map, err error)
}
// TODO: next step for parser is writing as many tests and grouping them nicely
// TODO: add assertions
// to layout *all* expected behavior.
func TestParse(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
text string
assert func(t testing.TB, ast *d2ast.Map, err error)
// exp is in testdata/d2parser/TestParse/${name}.json
}{
var testCases = []testCase{
{
name: "empty",
text: ``,
@ -394,7 +393,99 @@ c-
},
}
for _, tc := range testCases {
t.Run("import", testImport)
runa(t, testCases)
}
func testImport(t *testing.T) {
t.Parallel()
tca := []testCase{
{
text: "x: @file",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.Equal(t, "file", ast.Nodes[0].MapKey.Value.Import.Path[0].Unbox().ScalarString())
},
},
{
text: "x: @file.d2",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.Equal(t, "file", ast.Nodes[0].MapKey.Value.Import.Path[0].Unbox().ScalarString())
},
},
{
text: "...@file.d2",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.True(t, ast.Nodes[0].Import.Spread)
assert.Equal(t, "file", ast.Nodes[0].Import.Path[0].Unbox().ScalarString())
},
},
{
text: "x: [...@file.d2]",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
imp := ast.Nodes[0].MapKey.Value.Array.Nodes[0].Import
assert.True(t, imp.Spread)
assert.Equal(t, "file", imp.Path[0].Unbox().ScalarString())
},
},
{
text: "...@\"file\".d2",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.True(t, ast.Nodes[0].Import.Spread)
assert.Equal(t, "file", ast.Nodes[0].Import.Path[0].Unbox().ScalarString())
assert.Equal(t, "d2", ast.Nodes[0].Import.Path[1].Unbox().ScalarString())
},
},
{
text: "...@file.\"d2\"",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.True(t, ast.Nodes[0].Import.Spread)
assert.Equal(t, "file", ast.Nodes[0].Import.Path[0].Unbox().ScalarString())
assert.Equal(t, "d2", ast.Nodes[0].Import.Path[1].Unbox().ScalarString())
},
},
{
text: "...@../file",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.True(t, ast.Nodes[0].Import.Spread)
assert.Equal(t, "../file", ast.Nodes[0].Import.PathWithPre())
},
},
{
text: "@file",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.ErrorString(t, err, "d2/testdata/d2parser/TestParse/import/#07.d2:1:1: @file is not a valid import, did you mean ...@file?")
},
},
{
text: "...@./../.././file",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.Success(t, err)
assert.True(t, ast.Nodes[0].Import.Spread)
assert.Equal(t, "../../file", ast.Nodes[0].Import.PathWithPre())
},
},
{
text: "meow: ...@file",
assert: func(t testing.TB, ast *d2ast.Map, err error) {
assert.ErrorString(t, err, "d2/testdata/d2parser/TestParse/import/#09.d2:1:7: unquoted strings cannot begin with ...@ as that's import spread syntax")
},
},
}
runa(t, tca)
}
func runa(t *testing.T, tca []testCase) {
for _, tc := range tca {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

View file

@ -15,6 +15,7 @@ import (
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2graph"
timelib "oss.terrastruct.com/d2/lib/time"
)
// execPlugin uses the binary at pathname with the plugin protocol to implement
@ -147,7 +148,7 @@ func (p *execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) {
}
func (p *execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
defer cancel()
graphBytes, err := d2graph.SerializeGraph(g)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 129 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 126 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 117 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3945613123 .text-mono {
font-family: "d2-3945613123-font-mono";
}

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3945613123 .text-mono {
font-family: "d2-3945613123-font-mono";
}

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 398 284"><svg id="d2-svg" class="d2-124739280" width="398" height="284" viewBox="-101 -102 398 284"><rect x="-101.000000" y="-102.000000" width="398.000000" height="284.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 398 284"><svg id="d2-svg" class="d2-124739280" width="398" height="284" viewBox="-101 -102 398 284"><rect x="-101.000000" y="-102.000000" width="398.000000" height="284.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-124739280 .text-bold {
font-family: "d2-124739280-font-bold";
}
@ -136,5 +136,6 @@
<rect x="129" y="-5" width="66" height="84" fill="white"></rect>
<path d="M141,10L156,3L182,3L195,37L182,72L167,79L141,79L129,44L141,10L167,10L180,44L167,79M167,10L182,3M180,44L195,37M167,79L182,72" style="stroke-width:2;;stroke:#000;fill:none;opacity:1;"/></mask></defs><polygon x="129.000000" y="10.000000" mask="url(#border-mask-y)" points="141,10 167,10 180,44 167,79 141,79 129,44" stroke="none" class=" fill-N5" style="stroke-width:2;" /><polygon x="129.000000" y="10.000000" mask="url(#border-mask-y)" points="141,10 167,10 180,44 167,79 141,79 129,44" class="dots-overlay" style="stroke-width:2;" /><polygon mask="url(#border-mask-y)" points="156,3 182,3 195,37 182,72 167,79 180,44 167,10 141,10" class=" fill-N4" style="stroke-width:2;" /><path d="M141,10 L156,3 L182,3 L195,37 L182,72 L167,79 L141,79 L129,44 L141,10 L167,10 L180,44 L167,79 M167,10 L182,3 M180,44 L195,37 M167,79 L182,72" fill="none" class=" stroke-B1" style="stroke-width:2;" /></g><text x="154.500000" y="50.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><mask id="d2-124739280" maskUnits="userSpaceOnUse" x="-101" y="-102" width="398" height="284">
<rect x="-101" y="-102" width="398" height="284" fill="white"></rect>
<rect x="22.500000" y="37.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="150.000000" y="34.000000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 167 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 493 KiB

After

Width:  |  Height:  |  Size: 493 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 119 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 204 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 204 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 676 KiB

After

Width:  |  Height:  |  Size: 677 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-916646398" width="304" height="407" viewBox="-101 -118 304 407"><rect x="-101.000000" y="-118.000000" width="304" height="407" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-916646398" width="304" height="407" viewBox="-101 -118 304 407"><rect x="-101.000000" y="-118.000000" width="304" height="407" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
@ -94,7 +94,7 @@
.d2-916646398 .color-AB4{color:#EDF0FD;}
.d2-916646398 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="root.layers.x" xlink:href="root.layers.x"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(69 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g></g></a><mask id="d2-916646398" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="143.000000" y1="117.000000" y2="117.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="67" width="104" height="100%"><g transform="translate(0 167)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="172" style="font-size: 16px;">root &gt; x</text></g>
<style type="text/css"><![CDATA[
.text {

Before

Width:  |  Height:  |  Size: 657 KiB

After

Width:  |  Height:  |  Size: 657 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2692295619" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2692295619" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
@ -94,7 +94,8 @@
.d2-2692295619 .color-AB4{color:#EDF0FD;}
.d2-2692295619 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="https://d2lang.com" xlink:href="https://d2lang.com"><g id="x"><g class="shape" ><rect x="17.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="59.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(86 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g></g></a><a href="https://terrastruct.com" xlink:href="https://terrastruct.com"><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="118.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="59.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text><g transform="translate(102 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">2</text></g><title>Gee, I feel kind of LIGHT in the head now,&#xA;knowing I can&#39;t make my satellite dish PAYMENTS!</title><g transform="translate(70 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">3</text></g></g></a><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 59.000000 68.000000 C 59.000000 106.000000 59.000000 126.000000 59.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-2692295619)" /></g><mask id="d2-2692295619" maskUnits="userSpaceOnUse" x="-101" y="-118" width="337" height="451">
<rect x="-101" y="-118" width="337" height="451" fill="white"></rect>
<rect x="55.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="54.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="423.000000" y1="283.000000" y2="283.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="233" width="137" height="100%"><g transform="translate(0 333)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="338" style="font-size: 16px;">https://d2lang.com</text>
<g transform="translate(0 385)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">2</text></g><text class="text" x="48" y="390" style="font-size: 16px;"><tspan x="48.000000" dy="0.000000">Gee, I feel kind of LIGHT in the head now,</tspan><tspan x="48.000000" dy="18.500000">knowing I can&#39;t make my satellite dish PAYMENTS!</tspan></text>
<g transform="translate(0 442)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">3</text></g><text class="text" x="48" y="447" style="font-size: 16px;">https://terrastruct.com</text></g>

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 662 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2055261676" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2055261676" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
@ -94,7 +94,8 @@
.d2-2055261676 .color-AB4{color:#45475A;}
.d2-2055261676 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><a href="https://d2lang.com" xlink:href="https://d2lang.com"><g id="x"><g class="shape" ><rect x="17.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="59.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(86 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g></g></a><a href="https://fosny.eu" xlink:href="https://fosny.eu"><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="118.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="59.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text><g transform="translate(102 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">2</text></g><title>Gee, I feel kind of LIGHT in the head now,&#xA;knowing I can&#39;t make my satellite dish PAYMENTS!</title><g transform="translate(70 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">3</text></g></g></a><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 59.000000 68.000000 C 59.000000 106.000000 59.000000 126.000000 59.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-2055261676)" /></g><mask id="d2-2055261676" maskUnits="userSpaceOnUse" x="-101" y="-118" width="337" height="451">
<rect x="-101" y="-118" width="337" height="451" fill="white"></rect>
<rect x="55.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="54.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="423.000000" y1="283.000000" y2="283.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="233" width="137" height="100%"><g transform="translate(0 333)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="338" style="font-size: 16px;">https://d2lang.com</text>
<g transform="translate(0 385)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">2</text></g><text class="text" x="48" y="390" style="font-size: 16px;"><tspan x="48.000000" dy="0.000000">Gee, I feel kind of LIGHT in the head now,</tspan><tspan x="48.000000" dy="18.500000">knowing I can&#39;t make my satellite dish PAYMENTS!</tspan></text>
<g transform="translate(0 442)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">3</text></g><text class="text" x="48" y="447" style="font-size: 16px;">https://fosny.eu</text></g>

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 662 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-820103664" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" fill="PaleVioletRed" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-820103664" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" fill="PaleVioletRed" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
@ -94,7 +94,8 @@
.d2-820103664 .color-AB4{color:#EDF0FD;}
.d2-820103664 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(70 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g><title>Total abstinence is easier than perfect moderation</title></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="86.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text><g transform="translate(70 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">2</text></g><title>Gee, I feel kind of LIGHT in the head now,&#xA;knowing I can&#39;t make my satellite dish PAYMENTS!</title></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 43.000000 68.000000 C 43.000000 106.000000 43.000000 126.000000 43.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-820103664)" /></g><mask id="d2-820103664" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="451">
<rect x="-101" y="-118" width="305" height="451" fill="white"></rect>
<rect x="39.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="38.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="424.000000" y1="283.000000" y2="283.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="233" width="105" height="100%"><g transform="translate(0 333)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="338" style="font-size: 16px;">Total abstinence is easier than perfect moderation</text>
<g transform="translate(0 385)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">2</text></g><text class="text" x="48" y="390" style="font-size: 16px;"><tspan x="48.000000" dy="0.000000">Gee, I feel kind of LIGHT in the head now,</tspan><tspan x="48.000000" dy="18.500000">knowing I can&#39;t make my satellite dish PAYMENTS!</tspan></text></g>
<style type="text/css"><![CDATA[

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-820103664" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-820103664" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
@ -94,7 +94,8 @@
.d2-820103664 .color-AB4{color:#EDF0FD;}
.d2-820103664 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(70 -16)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">1</text></g><title>Total abstinence is easier than perfect moderation</title></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="86.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text><g transform="translate(70 150)" class="appendix-icon"><circle cx="16" cy="16" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="21" style="font-size: 16px;text-anchor:middle;">2</text></g><title>Gee, I feel kind of LIGHT in the head now,&#xA;knowing I can&#39;t make my satellite dish PAYMENTS!</title></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 43.000000 68.000000 C 43.000000 106.000000 43.000000 126.000000 43.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-820103664)" /></g><mask id="d2-820103664" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="451">
<rect x="-101" y="-118" width="305" height="451" fill="white"></rect>
<rect x="39.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="38.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask><line x1="-41.000000" x2="424.000000" y1="283.000000" y2="283.000000" class=" stroke-B2" /><g class="appendix" x="-1" y="233" width="105" height="100%"><g transform="translate(0 333)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">1</text></g><text class="text" x="48" y="338" style="font-size: 16px;">Total abstinence is easier than perfect moderation</text>
<g transform="translate(0 385)" class="appendix-icon"><circle cx="16" cy="0" r="16" fill="white" stroke="#DEE1EB" /><text class="text-bold" x="16" y="5" style="font-size: 16px;text-anchor:middle;">2</text></g><text class="text" x="48" y="390" style="font-size: 16px;"><tspan x="48.000000" dy="0.000000">Gee, I feel kind of LIGHT in the head now,</tspan><tspan x="48.000000" dy="18.500000">knowing I can&#39;t make my satellite dish PAYMENTS!</tspan></text></g>
<style type="text/css"><![CDATA[

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -456,11 +456,16 @@ func pathData(connection d2target.Connection, srcAdj, dstAdj *geo.Point) string
return strings.Join(path, " ")
}
func makeLabelMask(labelTL *geo.Point, width, height int) string {
return fmt.Sprintf(`<rect x="%f" y="%f" width="%d" height="%d" fill="black"></rect>`,
func makeLabelMask(labelTL *geo.Point, width, height int, opacity float64) string {
fill := "black"
if opacity != 1 {
fill = fmt.Sprintf("rgba(0,0,0,%.2f)", opacity)
}
return fmt.Sprintf(`<rect x="%f" y="%f" width="%d" height="%d" fill="%s"></rect>`,
labelTL.X, labelTL.Y,
width,
height,
fill,
)
}
@ -510,7 +515,9 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
labelTL.Y = math.Round(labelTL.Y)
if label.Position(connection.LabelPosition).IsOnEdge() {
labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight)
labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 1)
} else {
labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 0.75)
}
}
@ -1206,6 +1213,7 @@ func drawShape(writer io.Writer, diagramHash string, targetShape d2target.Shape,
float64(targetShape.LabelWidth),
float64(targetShape.LabelHeight),
)
labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight, 0.75)
fontClass := "text"
if targetShape.FontFamily == "mono" {
@ -1324,7 +1332,7 @@ func drawShape(writer io.Writer, diagramHash string, targetShape d2target.Shape,
textEl.Content = RenderText(targetShape.Label, textEl.X, float64(targetShape.LabelHeight))
fmt.Fprint(writer, textEl.Render())
if targetShape.Blend {
labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING)
labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING, 1)
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 434"><svg id="d2-svg" class="d2-3655258321" width="257" height="434" viewBox="-101 -101 257 434"><rect x="-101.000000" y="-101.000000" width="257.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 434"><svg id="d2-svg" class="d2-3655258321" width="257" height="434" viewBox="-101 -101 257 434"><rect x="-101.000000" y="-101.000000" width="257.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3655258321 .text-bold {
font-family: "d2-3655258321-font-bold";
}
@ -91,5 +91,6 @@
.d2-3655258321 .color-AB4{color:#45475A;}
.d2-3655258321 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g id="a"><g class="shape" ><rect x="1.000000" y="0.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="28.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">a</text></g><g id="b"><g class="shape" ><rect x="0.000000" y="166.000000" width="55.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">b</text></g><g id="(a -&gt; b)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.500000 68.000000 C 27.500000 106.000000 27.500000 126.000000 27.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-3655258321)" /></g><mask id="d2-3655258321" maskUnits="userSpaceOnUse" x="-101" y="-101" width="257" height="434">
<rect x="-101" y="-101" width="257" height="434" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="10" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 370 633"><svg id="d2-svg" class="d2-1784090246" width="370" height="633" viewBox="-101 -100 370 633"><rect x="-101.000000" y="-100.000000" width="370.000000" height="633.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 370 633"><svg id="d2-svg" class="d2-1784090246" width="370" height="633" viewBox="-101 -100 370 633"><rect x="-101.000000" y="-100.000000" width="370.000000" height="633.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1784090246 .text {
font-family: "d2-1784090246-font-regular";
}
@ -98,5 +98,8 @@
.d2-1784090246 .color-AB4{color:#45475A;}
.d2-1784090246 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g id="winter"><g class="shape" ><rect x="0.000000" y="44.000000" width="168.000000" height="122.000000" class=" stroke-B1 fill-B4" style="stroke-width:2;" /></g><text x="84.000000" y="28.000000" class="text fill-N1" style="text-anchor:middle;font-size:28px">winter</text></g><g id="summer"><g class="shape" ><rect x="7.000000" y="310.000000" width="155.000000" height="122.000000" class=" stroke-B1 fill-B4" style="stroke-width:2;" /></g><text x="84.500000" y="294.000000" class="text fill-N1" style="text-anchor:middle;font-size:28px">summer</text></g><g id="winter.snow"><g class="shape" ><rect x="40.000000" y="72.000000" width="88.000000" height="66.000000" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="84.000000" y="110.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">snow</text></g><g id="summer.sun"><g class="shape" ><rect x="47.000000" y="338.000000" width="75.000000" height="66.000000" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="84.500000" y="376.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">sun</text></g><g id="(winter.snow -&gt; summer.sun)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 84.000000 140.000000 C 84.000000 160.399994 84.000000 176.000000 84.000000 191.000000 C 84.000000 206.000000 84.000000 280.399994 84.000000 334.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1784090246)" /></g><mask id="d2-1784090246" maskUnits="userSpaceOnUse" x="-101" y="-100" width="370" height="633">
<rect x="-101" y="-100" width="370" height="633" fill="white"></rect>
<rect x="43.000000" y="0.000000" width="82" height="39" fill="rgba(0,0,0,0.75)"></rect>
<rect x="33.000000" y="266.000000" width="103" height="39" fill="rgba(0,0,0,0.75)"></rect>
<rect x="62.500000" y="94.500000" width="43" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="69.500000" y="360.500000" width="30" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 624 570"><svg id="d2-svg" class="d2-3945613123" width="624" height="570" viewBox="-101 -101 624 570"><rect x="-101.000000" y="-101.000000" width="624.000000" height="570.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3945613123 .text-mono {
font-family: "d2-3945613123-font-mono";
}

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 937 410"><svg id="d2-svg" class="d2-82882857" width="937" height="410" viewBox="-101 -101 937 410"><rect x="-101.000000" y="-101.000000" width="937.000000" height="410.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 937 410"><svg id="d2-svg" class="d2-82882857" width="937" height="410" viewBox="-101 -101 937 410"><rect x="-101.000000" y="-101.000000" width="937.000000" height="410.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-82882857 .text {
font-family: "d2-82882857-font-regular";
}
@ -857,5 +857,7 @@
</tspan></text><text class="text-mono" x="0" y="3.000000em" xml:space="preserve"><tspan fill="#fab387"></tspan><tspan fill="#cdd6f4">}</tspan></text></g></g></g><g id="text"><g class="shape" ></g><g><foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" x="352.000000" y="23.000000" width="383" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="md"><p>Five is a sufficiently close approximation to infinity.</p>
</div></foreignObject></g></g><g id="unknown"><g class="shape" ></g><g transform="translate(0.000000 170.000000)" class="light-code"><rect width="425.000000" height="38.000000" class="shape stroke-N1" style="fill:#ffffff" /><g transform="translate(6 6)"><text class="text-mono" x="0" y="1.000000em" xml:space="preserve">Don't&#160;hit&#160;me!!&#160;&#160;I'm&#160;in&#160;the&#160;Twilight&#160;Zone!!!</text></g></g><g transform="translate(0.000000 170.000000)" class="dark-code"><rect width="425.000000" height="38.000000" class="shape stroke-N1" style="fill:#1e1e2e" /><g transform="translate(6 6)"><text class="text-mono" x="0" y="1.000000em" xml:space="preserve"><tspan fill="#fab387">Don't&#160;hit&#160;me!!&#160;&#160;I'm&#160;in&#160;the&#160;Twilight&#160;Zone!!!</tspan></text></g></g></g><g id="(code -- unknown)[0]"><path d="M 212.500000 72.000000 C 212.500000 110.000000 212.500000 130.000000 212.500000 168.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" mask="url(#d2-82882857)" /></g><mask id="d2-82882857" maskUnits="userSpaceOnUse" x="-101" y="-101" width="937" height="410">
<rect x="-101" y="-101" width="937" height="410" fill="white"></rect>
<rect x="133.000000" y="0.000000" width="154" height="65" fill="rgba(0,0,0,0.75)"></rect>
<rect x="352.000000" y="23.000000" width="383" height="24" fill="rgba(0,0,0,0.75)"></rect>
<rect x="0.000000" y="170.000000" width="420" height="33" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 457"><svg id="d2-svg" class="d2-1320785676" width="257" height="457" viewBox="-101 -101 257 457"><rect x="-101.000000" y="-101.000000" width="257.000000" height="457.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 457"><svg id="d2-svg" class="d2-1320785676" width="257" height="457" viewBox="-101 -101 257 457"><rect x="-101.000000" y="-101.000000" width="257.000000" height="457.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1320785676 .text-bold {
font-family: "d2-1320785676-font-bold";
}
@ -98,5 +98,7 @@
.d2-1320785676 .color-AB4{color:#45475A;}
.d2-1320785676 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g id="a"><g class="shape" ><rect x="1.000000" y="0.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="28.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">a</text></g><g id="b"><g class="shape" ><rect x="0.000000" y="189.000000" width="55.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="227.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">b</text></g><g id="(a -&gt; b)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.500000 68.000000 C 27.500000 115.199997 27.500000 139.899994 27.500000 185.500000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1320785676)" /><text x="27.500000" y="132.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">hello</text></g><mask id="d2-1320785676" maskUnits="userSpaceOnUse" x="-101" y="-101" width="257" height="457">
<rect x="-101" y="-101" width="257" height="457" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="211.500000" width="10" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="12.000000" y="116.000000" width="31" height="23" fill="black"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 758 268"><svg id="d2-svg" class="d2-2779170942" width="758" height="268" viewBox="-101 -101 758 268"><rect x="-101.000000" y="-101.000000" width="758.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 758 268"><svg id="d2-svg" class="d2-2779170942" width="758" height="268" viewBox="-101 -101 758 268"><rect x="-101.000000" y="-101.000000" width="758.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2779170942 .text-bold {
font-family: "d2-2779170942-font-bold";
}
@ -91,5 +91,8 @@
.d2-2779170942 .color-AB4{color:#45475A;}
.d2-2779170942 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g id="bright"><g class="shape" ><rect x="0.000000" y="0.000000" width="94.000000" height="66.000000" stroke="#000" fill="#fff" style="stroke-width:2;" /></g><text x="47.000000" y="38.500000" fill="#000" class="text-bold" style="text-anchor:middle;font-size:16px">bright</text></g><g id="normal"><g class="shape" ><rect x="154.000000" y="0.000000" width="101.000000" height="66.000000" stroke="#000" fill="#ccc" style="stroke-width:2;" /></g><text x="204.500000" y="38.500000" fill="#000" class="text-bold" style="text-anchor:middle;font-size:16px">normal</text></g><g id="dark"><g class="shape" ><rect x="315.000000" y="0.000000" width="82.000000" height="66.000000" stroke="#000" fill="#555" style="stroke-width:2;" /></g><text x="356.000000" y="38.500000" fill="#fff" class="text-bold" style="text-anchor:middle;font-size:16px">dark</text></g><g id="darker"><g class="shape" ><rect x="457.000000" y="0.000000" width="99.000000" height="66.000000" stroke="#000" fill="#000" style="stroke-width:2;" /></g><text x="506.500000" y="38.500000" fill="#fff" class="text-bold" style="text-anchor:middle;font-size:16px">darker</text></g><mask id="d2-2779170942" maskUnits="userSpaceOnUse" x="-101" y="-101" width="758" height="268">
<rect x="-101" y="-101" width="758" height="268" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="49" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="176.500000" y="22.500000" width="56" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="337.500000" y="22.500000" width="37" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="479.500000" y="22.500000" width="54" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -74,18 +74,12 @@ func tableHeader(diagramHash string, shape d2target.Shape, box *geo.Box, text st
return str
}
func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth float64) string {
func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth, longestTypeWidth float64) string {
// Row is made up of name, type, and constraint
// e.g. | diagram int FK |
nameTL := label.InsideMiddleLeft.GetPointOnBox(
box,
d2target.NamePadding,
box.Width,
fontSize,
)
constraintTR := label.InsideMiddleRight.GetPointOnBox(
box,
d2target.TypePadding,
0,
fontSize,
)
@ -99,15 +93,14 @@ func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraint
textEl.Content = svg.EscapeText(nameText)
out := textEl.Render()
textEl.X = nameTL.X + longestNameWidth + 2*d2target.NamePadding
textEl.X += longestNameWidth + d2target.TypePadding
textEl.Fill = shape.NeutralAccentColor
textEl.Content = svg.EscapeText(typeText)
out += textEl.Render()
textEl.X = constraintTR.X
textEl.Y = constraintTR.Y + fontSize*3/4
textEl.X = box.TopLeft.X + (box.Width - d2target.NamePadding)
textEl.Fill = shape.SecondaryAccentColor
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", fontSize)
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", fontSize)
textEl.Content = constraintText
out += textEl.Render()
@ -144,15 +137,17 @@ func drawTable(writer io.Writer, diagramHash string, targetShape d2target.Shape)
)
var longestNameWidth int
var longestTypeWidth int
for _, f := range targetShape.Columns {
longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth)
longestTypeWidth = go2.Max(longestTypeWidth, f.Type.LabelWidth)
}
rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight)
rowBox.TopLeft.Y += headerBox.Height
for idx, f := range targetShape.Columns {
fmt.Fprint(writer,
tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth)),
tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth), float64(longestTypeWidth)),
)
rowBox.TopLeft.Y += rowHeight

View file

@ -1,9 +1,12 @@
package d2target
import "strings"
const (
NamePadding = 10
TypePadding = 20
HeaderPadding = 10
NamePadding = 10
TypePadding = 20
ConstraintPadding = 20
HeaderPadding = 10
// Setting table font size sets it for columns
// The header needs to be a little larger for visual hierarchy
@ -15,10 +18,10 @@ type SQLTable struct {
}
type SQLColumn struct {
Name Text `json:"name"`
Type Text `json:"type"`
Constraint string `json:"constraint"`
Reference string `json:"reference"`
Name Text `json:"name"`
Type Text `json:"type"`
Constraint []string `json:"constraint"`
Reference string `json:"reference"`
}
func (c SQLColumn) Texts(fontSize int) []*MText {
@ -37,18 +40,31 @@ func (c SQLColumn) Texts(fontSize int) []*MText {
IsItalic: false,
Shape: "sql_table",
},
{
Text: c.ConstraintAbbr(),
FontSize: fontSize,
IsBold: false,
IsItalic: false,
Shape: "sql_table",
},
}
}
func (c SQLColumn) ConstraintAbbr() string {
switch c.Constraint {
case "primary_key":
return "PK"
case "foreign_key":
return "FK"
case "unique":
return "UNQ"
default:
return ""
constraints := make([]string, len(c.Constraint))
for i, constraint := range c.Constraint {
switch constraint {
case "primary_key":
constraint = "PK"
case "foreign_key":
constraint = "FK"
case "unique":
constraint = "UNQ"
}
constraints[i] = constraint
}
return strings.Join(constraints, ", ")
}

View file

@ -9,13 +9,14 @@ import (
"testing"
"time"
"oss.terrastruct.com/d2/d2cli"
"oss.terrastruct.com/d2/lib/pptx"
"oss.terrastruct.com/d2/lib/xgif"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/util-go/xos"
"oss.terrastruct.com/d2/d2cli"
"oss.terrastruct.com/d2/lib/pptx"
"oss.terrastruct.com/d2/lib/xgif"
)
func TestCLI_E2E(t *testing.T) {
@ -390,6 +391,64 @@ steps: {
assert.Testdata(t, ".svg", svg)
},
},
{
name: "import",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `x: @x; y: @y; ...@p`)
writeFile(t, dir, "x.d2", `shape: circle`)
writeFile(t, dir, "y.d2", `shape: square`)
writeFile(t, dir, "p.d2", `x -> y`)
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "import_spread_nested",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `...@x.y`)
writeFile(t, dir, "x.d2", `y: { jon; jan }`)
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "chain_import",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `...@x`)
writeFile(t, dir, "x.d2", `...@y`)
writeFile(t, dir, "y.d2", `meow`)
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "board_import",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `x.link: layers.x; layers: { x: @x }`)
writeFile(t, dir, "x.d2", `y.link: layers.y; layers: { y: @y }`)
writeFile(t, dir, "y.d2", `meow`)
err := runTestMain(t, ctx, dir, env, filepath.Join(dir, "hello-world.d2"))
assert.Success(t, err)
t.Run("hello-world-x-y", func(t *testing.T) {
svg := readFile(t, dir, "hello-world/x/y.svg")
assert.Testdata(t, ".svg", svg)
})
t.Run("hello-world-x", func(t *testing.T) {
svg := readFile(t, dir, "hello-world/x/index.svg")
assert.Testdata(t, ".svg", svg)
})
t.Run("hello-world", func(t *testing.T) {
svg := readFile(t, dir, "hello-world/index.svg")
assert.Testdata(t, ".svg", svg)
})
},
},
}
ctx := context.Background()

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-855222762 .text-bold {
font-family: "d2-855222762-font-bold";
}
@ -91,5 +91,6 @@
.d2-855222762 .color-AB4{color:#EDF0FD;}
.d2-855222762 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-855222762)" /></g><mask id="d2-855222762" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 514 665"><svg id="d2-svg" width="514" height="665" viewBox="-206 -166 514 665"><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 514 665"><svg id="d2-svg" width="514" height="665" viewBox="-206 -166 514 665"><style type="text/css"><![CDATA[
.d2-508224771 .text {
font-family: "d2-508224771-font-regular";
}
@ -871,14 +871,20 @@
}
}]]></style><g style="animation: d2Transition-d2-508224771-0 5600ms infinite" class="d2-508224771" width="412" height="247" viewBox="-206 -166 412 247"><rect x="-206.000000" y="-166.000000" width="412.000000" height="247.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="0.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-508224771" maskUnits="userSpaceOnUse" x="-206" y="-166" width="412" height="247">
<rect x="-206" y="-166" width="412" height="247" fill="white"></rect>
<rect x="-105.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-1 5600ms infinite" class="d2-508224771" width="412" height="333" viewBox="-131 -166 412 333"><rect x="-131.000000" y="-166.000000" width="412.000000" height="333.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><mask id="d2-3302893893" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="333">
<rect x="-131" y="-166" width="412" height="333" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="-30.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-2 5600ms infinite" class="d2-508224771" width="412" height="499" viewBox="-131 -166 412 499"><rect x="-131.000000" y="-166.000000" width="412.000000" height="499.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="0.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="15.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="75.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="75.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 75.000000 68.000000 C 75.000000 106.000000 75.000000 126.000000 75.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-2454480105)" /></g><mask id="d2-2454480105" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="499">
<rect x="-131" y="-166" width="412" height="499" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="37.500000" y="188.500000" width="75" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="-30.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-508224771-3 5600ms infinite" class="d2-508224771" width="412" height="665" viewBox="-104 -166 412 665"><rect x="-104.000000" y="-166.000000" width="412.000000" height="665.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="Approach road"><g class="shape" ><rect x="27.000000" y="0.000000" width="150.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Approach road</text></g><g id="Cross road"><g class="shape" ><rect x="42.000000" y="166.000000" width="120.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="102.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cross road</text></g><g id="Make you wonder why"><g class="shape" ><rect x="0.000000" y="332.000000" width="203.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="101.500000" y="370.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Make you wonder why</text></g><g id="&#34;Chicken&#39;s plan&#34;"><g class="shape" ></g><text x="102.000000" y="-30.000000" class="text fill-N1" style="text-anchor:middle;font-size:35px">Chicken&#39;s plan</text></g><g id="(Approach road -&gt; Cross road)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 101.500000 68.000000 C 101.500000 106.000000 101.500000 126.000000 101.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-682060979)" /></g><g id="(Cross road -&gt; Make you wonder why)[0]"><path d="M 101.500000 234.000000 C 101.500000 272.000000 101.500000 292.000000 101.500000 328.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-682060979)" /></g><mask id="d2-682060979" maskUnits="userSpaceOnUse" x="-104" y="-166" width="412" height="665">
<rect x="-104" y="-166" width="412" height="665" fill="white"></rect>
<rect x="49.500000" y="22.500000" width="105" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="64.500000" y="188.500000" width="75" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="354.500000" width="158" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="-3.000000" y="-65.000000" width="210" height="45" fill="rgba(0,0,0,0.75)"></rect>
</mask></g></svg></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-685498927" width="290" height="268" viewBox="-101 -101 290 268"><rect x="-101.000000" y="-101.000000" width="290.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-685498927 .text-bold {
font-family: "d2-685498927-font-bold";
}
@font-face {
font-family: d2-685498927-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-685498927 .fill-N1{fill:#0A0F25;}
.d2-685498927 .fill-N2{fill:#676C7E;}
.d2-685498927 .fill-N3{fill:#9499AB;}
.d2-685498927 .fill-N4{fill:#CFD2DD;}
.d2-685498927 .fill-N5{fill:#DEE1EB;}
.d2-685498927 .fill-N6{fill:#EEF1F8;}
.d2-685498927 .fill-N7{fill:#FFFFFF;}
.d2-685498927 .fill-B1{fill:#0D32B2;}
.d2-685498927 .fill-B2{fill:#0D32B2;}
.d2-685498927 .fill-B3{fill:#E3E9FD;}
.d2-685498927 .fill-B4{fill:#E3E9FD;}
.d2-685498927 .fill-B5{fill:#EDF0FD;}
.d2-685498927 .fill-B6{fill:#F7F8FE;}
.d2-685498927 .fill-AA2{fill:#4A6FF3;}
.d2-685498927 .fill-AA4{fill:#EDF0FD;}
.d2-685498927 .fill-AA5{fill:#F7F8FE;}
.d2-685498927 .fill-AB4{fill:#EDF0FD;}
.d2-685498927 .fill-AB5{fill:#F7F8FE;}
.d2-685498927 .stroke-N1{stroke:#0A0F25;}
.d2-685498927 .stroke-N2{stroke:#676C7E;}
.d2-685498927 .stroke-N3{stroke:#9499AB;}
.d2-685498927 .stroke-N4{stroke:#CFD2DD;}
.d2-685498927 .stroke-N5{stroke:#DEE1EB;}
.d2-685498927 .stroke-N6{stroke:#EEF1F8;}
.d2-685498927 .stroke-N7{stroke:#FFFFFF;}
.d2-685498927 .stroke-B1{stroke:#0D32B2;}
.d2-685498927 .stroke-B2{stroke:#0D32B2;}
.d2-685498927 .stroke-B3{stroke:#E3E9FD;}
.d2-685498927 .stroke-B4{stroke:#E3E9FD;}
.d2-685498927 .stroke-B5{stroke:#EDF0FD;}
.d2-685498927 .stroke-B6{stroke:#F7F8FE;}
.d2-685498927 .stroke-AA2{stroke:#4A6FF3;}
.d2-685498927 .stroke-AA4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AA5{stroke:#F7F8FE;}
.d2-685498927 .stroke-AB4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AB5{stroke:#F7F8FE;}
.d2-685498927 .background-color-N1{background-color:#0A0F25;}
.d2-685498927 .background-color-N2{background-color:#676C7E;}
.d2-685498927 .background-color-N3{background-color:#9499AB;}
.d2-685498927 .background-color-N4{background-color:#CFD2DD;}
.d2-685498927 .background-color-N5{background-color:#DEE1EB;}
.d2-685498927 .background-color-N6{background-color:#EEF1F8;}
.d2-685498927 .background-color-N7{background-color:#FFFFFF;}
.d2-685498927 .background-color-B1{background-color:#0D32B2;}
.d2-685498927 .background-color-B2{background-color:#0D32B2;}
.d2-685498927 .background-color-B3{background-color:#E3E9FD;}
.d2-685498927 .background-color-B4{background-color:#E3E9FD;}
.d2-685498927 .background-color-B5{background-color:#EDF0FD;}
.d2-685498927 .background-color-B6{background-color:#F7F8FE;}
.d2-685498927 .background-color-AA2{background-color:#4A6FF3;}
.d2-685498927 .background-color-AA4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AA5{background-color:#F7F8FE;}
.d2-685498927 .background-color-AB4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AB5{background-color:#F7F8FE;}
.d2-685498927 .color-N1{color:#0A0F25;}
.d2-685498927 .color-N2{color:#676C7E;}
.d2-685498927 .color-N3{color:#9499AB;}
.d2-685498927 .color-N4{color:#CFD2DD;}
.d2-685498927 .color-N5{color:#DEE1EB;}
.d2-685498927 .color-N6{color:#EEF1F8;}
.d2-685498927 .color-N7{color:#FFFFFF;}
.d2-685498927 .color-B1{color:#0D32B2;}
.d2-685498927 .color-B2{color:#0D32B2;}
.d2-685498927 .color-B3{color:#E3E9FD;}
.d2-685498927 .color-B4{color:#E3E9FD;}
.d2-685498927 .color-B5{color:#EDF0FD;}
.d2-685498927 .color-B6{color:#F7F8FE;}
.d2-685498927 .color-AA2{color:#4A6FF3;}
.d2-685498927 .color-AA4{color:#EDF0FD;}
.d2-685498927 .color-AA5{color:#F7F8FE;}
.d2-685498927 .color-AB4{color:#EDF0FD;}
.d2-685498927 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="meow"><g class="shape" ><rect x="0.000000" y="0.000000" width="88.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="44.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">meow</text></g><mask id="d2-685498927" maskUnits="userSpaceOnUse" x="-101" y="-101" width="290" height="268">
<rect x="-101" y="-101" width="290" height="268" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="43" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-2353227294" width="305" height="285" viewBox="-101 -118 305 285"><rect x="-101.000000" y="-118.000000" width="305.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
.d2-2353227294 .text-bold {
font-family: "d2-2353227294-font-bold";
}
@font-face {
font-family: d2-2353227294-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-2353227294 .fill-N1{fill:#0A0F25;}
.d2-2353227294 .fill-N2{fill:#676C7E;}
.d2-2353227294 .fill-N3{fill:#9499AB;}
.d2-2353227294 .fill-N4{fill:#CFD2DD;}
.d2-2353227294 .fill-N5{fill:#DEE1EB;}
.d2-2353227294 .fill-N6{fill:#EEF1F8;}
.d2-2353227294 .fill-N7{fill:#FFFFFF;}
.d2-2353227294 .fill-B1{fill:#0D32B2;}
.d2-2353227294 .fill-B2{fill:#0D32B2;}
.d2-2353227294 .fill-B3{fill:#E3E9FD;}
.d2-2353227294 .fill-B4{fill:#E3E9FD;}
.d2-2353227294 .fill-B5{fill:#EDF0FD;}
.d2-2353227294 .fill-B6{fill:#F7F8FE;}
.d2-2353227294 .fill-AA2{fill:#4A6FF3;}
.d2-2353227294 .fill-AA4{fill:#EDF0FD;}
.d2-2353227294 .fill-AA5{fill:#F7F8FE;}
.d2-2353227294 .fill-AB4{fill:#EDF0FD;}
.d2-2353227294 .fill-AB5{fill:#F7F8FE;}
.d2-2353227294 .stroke-N1{stroke:#0A0F25;}
.d2-2353227294 .stroke-N2{stroke:#676C7E;}
.d2-2353227294 .stroke-N3{stroke:#9499AB;}
.d2-2353227294 .stroke-N4{stroke:#CFD2DD;}
.d2-2353227294 .stroke-N5{stroke:#DEE1EB;}
.d2-2353227294 .stroke-N6{stroke:#EEF1F8;}
.d2-2353227294 .stroke-N7{stroke:#FFFFFF;}
.d2-2353227294 .stroke-B1{stroke:#0D32B2;}
.d2-2353227294 .stroke-B2{stroke:#0D32B2;}
.d2-2353227294 .stroke-B3{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B4{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B5{stroke:#EDF0FD;}
.d2-2353227294 .stroke-B6{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AA2{stroke:#4A6FF3;}
.d2-2353227294 .stroke-AA4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AA5{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AB4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AB5{stroke:#F7F8FE;}
.d2-2353227294 .background-color-N1{background-color:#0A0F25;}
.d2-2353227294 .background-color-N2{background-color:#676C7E;}
.d2-2353227294 .background-color-N3{background-color:#9499AB;}
.d2-2353227294 .background-color-N4{background-color:#CFD2DD;}
.d2-2353227294 .background-color-N5{background-color:#DEE1EB;}
.d2-2353227294 .background-color-N6{background-color:#EEF1F8;}
.d2-2353227294 .background-color-N7{background-color:#FFFFFF;}
.d2-2353227294 .background-color-B1{background-color:#0D32B2;}
.d2-2353227294 .background-color-B2{background-color:#0D32B2;}
.d2-2353227294 .background-color-B3{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B4{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B5{background-color:#EDF0FD;}
.d2-2353227294 .background-color-B6{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AA2{background-color:#4A6FF3;}
.d2-2353227294 .background-color-AA4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AA5{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AB4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AB5{background-color:#F7F8FE;}
.d2-2353227294 .color-N1{color:#0A0F25;}
.d2-2353227294 .color-N2{color:#676C7E;}
.d2-2353227294 .color-N3{color:#9499AB;}
.d2-2353227294 .color-N4{color:#CFD2DD;}
.d2-2353227294 .color-N5{color:#DEE1EB;}
.d2-2353227294 .color-N6{color:#EEF1F8;}
.d2-2353227294 .color-N7{color:#FFFFFF;}
.d2-2353227294 .color-B1{color:#0D32B2;}
.d2-2353227294 .color-B2{color:#0D32B2;}
.d2-2353227294 .color-B3{color:#E3E9FD;}
.d2-2353227294 .color-B4{color:#E3E9FD;}
.d2-2353227294 .color-B5{color:#EDF0FD;}
.d2-2353227294 .color-B6{color:#F7F8FE;}
.d2-2353227294 .color-AA2{color:#4A6FF3;}
.d2-2353227294 .color-AA4{color:#EDF0FD;}
.d2-2353227294 .color-AA5{color:#F7F8FE;}
.d2-2353227294 .color-AB4{color:#EDF0FD;}
.d2-2353227294 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="y.svg" xlink:href="y.svg"><g id="y"><g class="shape" ><rect x="0.000000" y="0.000000" width="86.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="43.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text><g transform="translate(70 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3440_35088111)">
<path d="M16 31.1109C24.3456 31.1109 31.1111 24.3454 31.1111 15.9998C31.1111 7.65415 24.3456 0.888672 16 0.888672C7.65436 0.888672 0.888885 7.65415 0.888885 15.9998C0.888885 24.3454 7.65436 31.1109 16 31.1109Z" fill="white" stroke="#DEE1EB"/>
<path d="M14.3909 16.7965C14.7364 17.2584 15.1772 17.6406 15.6834 17.9171C16.1896 18.1938 16.7494 18.3582 17.3248 18.3993C17.9001 18.4405 18.4777 18.3575 19.0181 18.1559C19.5586 17.9543 20.0492 17.6389 20.4571 17.2309L22.8708 14.8173C23.6036 14.0586 24.0089 13.0425 23.9998 11.9877C23.9906 10.933 23.5676 9.92404 22.8217 9.17821C22.0759 8.43237 21.067 8.00931 20.0123 8.00015C18.9575 7.99098 17.9413 8.39644 17.1827 9.1292L15.7988 10.505" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.609 15.1874C17.2635 14.7255 16.8227 14.3433 16.3165 14.0667C15.8103 13.7902 15.2505 13.6257 14.6752 13.5845C14.0998 13.5433 13.5223 13.6263 12.9819 13.8279C12.4414 14.0295 11.9506 14.345 11.5428 14.753L9.1292 17.1666C8.39644 17.9252 7.99098 18.9414 8.00015 19.9962C8.00931 21.0509 8.43237 22.0598 9.17821 22.8056C9.92405 23.5515 10.933 23.9745 11.9877 23.9837C13.0425 23.9928 14.0586 23.5875 14.8173 22.8547L16.193 21.4788" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_3440_35088111">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>
</g></g></a><mask id="d2-2353227294" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="285">
<rect x="-101" y="-118" width="305" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-2067460405" width="304" height="285" viewBox="-101 -118 304 285"><rect x="-101.000000" y="-118.000000" width="304.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
}
.d2-2067460405 .text-bold {
font-family: "d2-2067460405-font-bold";
}
@font-face {
font-family: d2-2067460405-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-2067460405 .fill-N1{fill:#0A0F25;}
.d2-2067460405 .fill-N2{fill:#676C7E;}
.d2-2067460405 .fill-N3{fill:#9499AB;}
.d2-2067460405 .fill-N4{fill:#CFD2DD;}
.d2-2067460405 .fill-N5{fill:#DEE1EB;}
.d2-2067460405 .fill-N6{fill:#EEF1F8;}
.d2-2067460405 .fill-N7{fill:#FFFFFF;}
.d2-2067460405 .fill-B1{fill:#0D32B2;}
.d2-2067460405 .fill-B2{fill:#0D32B2;}
.d2-2067460405 .fill-B3{fill:#E3E9FD;}
.d2-2067460405 .fill-B4{fill:#E3E9FD;}
.d2-2067460405 .fill-B5{fill:#EDF0FD;}
.d2-2067460405 .fill-B6{fill:#F7F8FE;}
.d2-2067460405 .fill-AA2{fill:#4A6FF3;}
.d2-2067460405 .fill-AA4{fill:#EDF0FD;}
.d2-2067460405 .fill-AA5{fill:#F7F8FE;}
.d2-2067460405 .fill-AB4{fill:#EDF0FD;}
.d2-2067460405 .fill-AB5{fill:#F7F8FE;}
.d2-2067460405 .stroke-N1{stroke:#0A0F25;}
.d2-2067460405 .stroke-N2{stroke:#676C7E;}
.d2-2067460405 .stroke-N3{stroke:#9499AB;}
.d2-2067460405 .stroke-N4{stroke:#CFD2DD;}
.d2-2067460405 .stroke-N5{stroke:#DEE1EB;}
.d2-2067460405 .stroke-N6{stroke:#EEF1F8;}
.d2-2067460405 .stroke-N7{stroke:#FFFFFF;}
.d2-2067460405 .stroke-B1{stroke:#0D32B2;}
.d2-2067460405 .stroke-B2{stroke:#0D32B2;}
.d2-2067460405 .stroke-B3{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B4{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B5{stroke:#EDF0FD;}
.d2-2067460405 .stroke-B6{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AA2{stroke:#4A6FF3;}
.d2-2067460405 .stroke-AA4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AA5{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AB4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AB5{stroke:#F7F8FE;}
.d2-2067460405 .background-color-N1{background-color:#0A0F25;}
.d2-2067460405 .background-color-N2{background-color:#676C7E;}
.d2-2067460405 .background-color-N3{background-color:#9499AB;}
.d2-2067460405 .background-color-N4{background-color:#CFD2DD;}
.d2-2067460405 .background-color-N5{background-color:#DEE1EB;}
.d2-2067460405 .background-color-N6{background-color:#EEF1F8;}
.d2-2067460405 .background-color-N7{background-color:#FFFFFF;}
.d2-2067460405 .background-color-B1{background-color:#0D32B2;}
.d2-2067460405 .background-color-B2{background-color:#0D32B2;}
.d2-2067460405 .background-color-B3{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B4{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B5{background-color:#EDF0FD;}
.d2-2067460405 .background-color-B6{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AA2{background-color:#4A6FF3;}
.d2-2067460405 .background-color-AA4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AA5{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AB4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AB5{background-color:#F7F8FE;}
.d2-2067460405 .color-N1{color:#0A0F25;}
.d2-2067460405 .color-N2{color:#676C7E;}
.d2-2067460405 .color-N3{color:#9499AB;}
.d2-2067460405 .color-N4{color:#CFD2DD;}
.d2-2067460405 .color-N5{color:#DEE1EB;}
.d2-2067460405 .color-N6{color:#EEF1F8;}
.d2-2067460405 .color-N7{color:#FFFFFF;}
.d2-2067460405 .color-B1{color:#0D32B2;}
.d2-2067460405 .color-B2{color:#0D32B2;}
.d2-2067460405 .color-B3{color:#E3E9FD;}
.d2-2067460405 .color-B4{color:#E3E9FD;}
.d2-2067460405 .color-B5{color:#EDF0FD;}
.d2-2067460405 .color-B6{color:#F7F8FE;}
.d2-2067460405 .color-AA2{color:#4A6FF3;}
.d2-2067460405 .color-AA4{color:#EDF0FD;}
.d2-2067460405 .color-AA5{color:#F7F8FE;}
.d2-2067460405 .color-AB4{color:#EDF0FD;}
.d2-2067460405 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><a href="x/index.svg" xlink:href="x/index.svg"><g id="x"><g class="shape" ><rect x="0.000000" y="0.000000" width="85.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="42.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text><g transform="translate(69 -16)" class="appendix-icon"><svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3440_35088111)">
<path d="M16 31.1109C24.3456 31.1109 31.1111 24.3454 31.1111 15.9998C31.1111 7.65415 24.3456 0.888672 16 0.888672C7.65436 0.888672 0.888885 7.65415 0.888885 15.9998C0.888885 24.3454 7.65436 31.1109 16 31.1109Z" fill="white" stroke="#DEE1EB"/>
<path d="M14.3909 16.7965C14.7364 17.2584 15.1772 17.6406 15.6834 17.9171C16.1896 18.1938 16.7494 18.3582 17.3248 18.3993C17.9001 18.4405 18.4777 18.3575 19.0181 18.1559C19.5586 17.9543 20.0492 17.6389 20.4571 17.2309L22.8708 14.8173C23.6036 14.0586 24.0089 13.0425 23.9998 11.9877C23.9906 10.933 23.5676 9.92404 22.8217 9.17821C22.0759 8.43237 21.067 8.00931 20.0123 8.00015C18.9575 7.99098 17.9413 8.39644 17.1827 9.1292L15.7988 10.505" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.609 15.1874C17.2635 14.7255 16.8227 14.3433 16.3165 14.0667C15.8103 13.7902 15.2505 13.6257 14.6752 13.5845C14.0998 13.5433 13.5223 13.6263 12.9819 13.8279C12.4414 14.0295 11.9506 14.345 11.5428 14.753L9.1292 17.1666C8.39644 17.9252 7.99098 18.9414 8.00015 19.9962C8.00931 21.0509 8.43237 22.0598 9.17821 22.8056C9.92405 23.5515 10.933 23.9745 11.9877 23.9837C13.0425 23.9928 14.0586 23.5875 14.8173 22.8547L16.193 21.4788" stroke="#2E3346" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_3440_35088111">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>
</g></g></a><mask id="d2-2067460405" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect>
<rect x="38.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-855222762" width="256" height="434" viewBox="-101 -101 256 434"><rect x="-101.000000" y="-101.000000" width="256.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-855222762 .text-bold {
font-family: "d2-855222762-font-bold";
}
@ -91,5 +91,6 @@
.d2-855222762 .color-AB4{color:#EDF0FD;}
.d2-855222762 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><rect x="1.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="0.000000" y="166.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="27.000000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 27.000000 68.000000 C 27.000000 106.000000 27.000000 126.000000 27.000000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-855222762)" /></g><mask id="d2-855222762" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect>
<rect x="23.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-685498927" width="290" height="268" viewBox="-101 -101 290 268"><rect x="-101.000000" y="-101.000000" width="290.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-685498927 .text-bold {
font-family: "d2-685498927-font-bold";
}
@font-face {
font-family: d2-685498927-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-685498927 .fill-N1{fill:#0A0F25;}
.d2-685498927 .fill-N2{fill:#676C7E;}
.d2-685498927 .fill-N3{fill:#9499AB;}
.d2-685498927 .fill-N4{fill:#CFD2DD;}
.d2-685498927 .fill-N5{fill:#DEE1EB;}
.d2-685498927 .fill-N6{fill:#EEF1F8;}
.d2-685498927 .fill-N7{fill:#FFFFFF;}
.d2-685498927 .fill-B1{fill:#0D32B2;}
.d2-685498927 .fill-B2{fill:#0D32B2;}
.d2-685498927 .fill-B3{fill:#E3E9FD;}
.d2-685498927 .fill-B4{fill:#E3E9FD;}
.d2-685498927 .fill-B5{fill:#EDF0FD;}
.d2-685498927 .fill-B6{fill:#F7F8FE;}
.d2-685498927 .fill-AA2{fill:#4A6FF3;}
.d2-685498927 .fill-AA4{fill:#EDF0FD;}
.d2-685498927 .fill-AA5{fill:#F7F8FE;}
.d2-685498927 .fill-AB4{fill:#EDF0FD;}
.d2-685498927 .fill-AB5{fill:#F7F8FE;}
.d2-685498927 .stroke-N1{stroke:#0A0F25;}
.d2-685498927 .stroke-N2{stroke:#676C7E;}
.d2-685498927 .stroke-N3{stroke:#9499AB;}
.d2-685498927 .stroke-N4{stroke:#CFD2DD;}
.d2-685498927 .stroke-N5{stroke:#DEE1EB;}
.d2-685498927 .stroke-N6{stroke:#EEF1F8;}
.d2-685498927 .stroke-N7{stroke:#FFFFFF;}
.d2-685498927 .stroke-B1{stroke:#0D32B2;}
.d2-685498927 .stroke-B2{stroke:#0D32B2;}
.d2-685498927 .stroke-B3{stroke:#E3E9FD;}
.d2-685498927 .stroke-B4{stroke:#E3E9FD;}
.d2-685498927 .stroke-B5{stroke:#EDF0FD;}
.d2-685498927 .stroke-B6{stroke:#F7F8FE;}
.d2-685498927 .stroke-AA2{stroke:#4A6FF3;}
.d2-685498927 .stroke-AA4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AA5{stroke:#F7F8FE;}
.d2-685498927 .stroke-AB4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AB5{stroke:#F7F8FE;}
.d2-685498927 .background-color-N1{background-color:#0A0F25;}
.d2-685498927 .background-color-N2{background-color:#676C7E;}
.d2-685498927 .background-color-N3{background-color:#9499AB;}
.d2-685498927 .background-color-N4{background-color:#CFD2DD;}
.d2-685498927 .background-color-N5{background-color:#DEE1EB;}
.d2-685498927 .background-color-N6{background-color:#EEF1F8;}
.d2-685498927 .background-color-N7{background-color:#FFFFFF;}
.d2-685498927 .background-color-B1{background-color:#0D32B2;}
.d2-685498927 .background-color-B2{background-color:#0D32B2;}
.d2-685498927 .background-color-B3{background-color:#E3E9FD;}
.d2-685498927 .background-color-B4{background-color:#E3E9FD;}
.d2-685498927 .background-color-B5{background-color:#EDF0FD;}
.d2-685498927 .background-color-B6{background-color:#F7F8FE;}
.d2-685498927 .background-color-AA2{background-color:#4A6FF3;}
.d2-685498927 .background-color-AA4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AA5{background-color:#F7F8FE;}
.d2-685498927 .background-color-AB4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AB5{background-color:#F7F8FE;}
.d2-685498927 .color-N1{color:#0A0F25;}
.d2-685498927 .color-N2{color:#676C7E;}
.d2-685498927 .color-N3{color:#9499AB;}
.d2-685498927 .color-N4{color:#CFD2DD;}
.d2-685498927 .color-N5{color:#DEE1EB;}
.d2-685498927 .color-N6{color:#EEF1F8;}
.d2-685498927 .color-N7{color:#FFFFFF;}
.d2-685498927 .color-B1{color:#0D32B2;}
.d2-685498927 .color-B2{color:#0D32B2;}
.d2-685498927 .color-B3{color:#E3E9FD;}
.d2-685498927 .color-B4{color:#E3E9FD;}
.d2-685498927 .color-B5{color:#EDF0FD;}
.d2-685498927 .color-B6{color:#F7F8FE;}
.d2-685498927 .color-AA2{color:#4A6FF3;}
.d2-685498927 .color-AA4{color:#EDF0FD;}
.d2-685498927 .color-AA5{color:#F7F8FE;}
.d2-685498927 .color-AB4{color:#EDF0FD;}
.d2-685498927 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="meow"><g class="shape" ><rect x="0.000000" y="0.000000" width="88.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="44.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">meow</text></g><mask id="d2-685498927" maskUnits="userSpaceOnUse" x="-101" y="-101" width="290" height="268">
<rect x="-101" y="-101" width="290" height="268" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="43" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 368 766"><svg id="d2-svg" width="368" height="766" viewBox="-101 -101 368 766"><style type="text/css"><![CDATA[
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 368 766"><svg id="d2-svg" width="368" height="766" viewBox="-101 -101 368 766"><style type="text/css"><![CDATA[
.d2-1644916896 .text-bold {
font-family: "d2-1644916896-font-bold";
}
@ -118,11 +118,19 @@
}
}]]></style><g style="animation: d2Transition-d2-1644916896-0 4200ms infinite" class="d2-1644916896" width="255" height="434" viewBox="-101 -101 255 434"><rect x="-101.000000" y="-101.000000" width="255.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="a"><g class="shape" ><rect x="0.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">a</text></g><g id="b"><g class="shape" ><rect x="0.000000" y="166.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">b</text></g><g id="(a -&gt; b)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 26.500000 68.000000 C 26.500000 106.000000 26.500000 126.000000 26.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-746933975)" /></g><mask id="d2-746933975" maskUnits="userSpaceOnUse" x="-101" y="-101" width="255" height="434">
<rect x="-101" y="-101" width="255" height="434" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-1644916896-1 4200ms infinite" class="d2-1644916896" width="368" height="600" viewBox="-101 -101 368 600"><rect x="-101.000000" y="-101.000000" width="368.000000" height="600.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="a"><g class="shape" ><rect x="0.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">a</text></g><g id="b"><g class="shape" ><rect x="0.000000" y="166.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">b</text></g><g id="d"><g class="shape" ><rect x="56.000000" y="332.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="83.000000" y="370.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">d</text></g><g id="c"><g class="shape" ><rect x="113.000000" y="166.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="139.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">c</text></g><g id="(a -&gt; b)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 26.500000 68.000000 C 26.500000 106.000000 26.500000 126.000000 26.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-919984524)" /></g><g id="(b -&gt; d)[0]"><path d="M 26.500000 234.000000 C 26.500000 272.000000 33.299999 292.000000 58.250760 328.692294" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-919984524)" /></g><g id="(c -&gt; d)[0]"><path d="M 139.500000 234.000000 C 139.500000 272.000000 132.699997 292.000000 107.749240 328.692294" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-919984524)" /></g><mask id="d2-919984524" maskUnits="userSpaceOnUse" x="-101" y="-101" width="368" height="600">
<rect x="-101" y="-101" width="368" height="600" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="78.500000" y="354.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="135.500000" y="188.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></g><g style="animation: d2Transition-d2-1644916896-2 4200ms infinite" class="d2-1644916896" width="368" height="766" viewBox="-101 -101 368 766"><rect x="-101.000000" y="-101.000000" width="368.000000" height="766.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><g id="a"><g class="shape" ><rect x="0.000000" y="0.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">a</text></g><g id="b"><g class="shape" ><rect x="0.000000" y="166.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="26.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">b</text></g><g id="d"><g class="shape" ><rect x="56.000000" y="332.000000" width="54.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="83.000000" y="370.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">d</text></g><g id="c"><g class="shape" ><rect x="113.000000" y="166.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="139.500000" y="204.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">c</text></g><g id="e"><g class="shape" ><rect x="57.000000" y="498.000000" width="53.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="83.500000" y="536.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">e</text></g><g id="(a -&gt; b)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 26.500000 68.000000 C 26.500000 106.000000 26.500000 126.000000 26.500000 162.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-348119987)" /></g><g id="(b -&gt; d)[0]"><path d="M 26.500000 234.000000 C 26.500000 272.000000 33.299999 292.000000 58.250760 328.692294" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-348119987)" /></g><g id="(c -&gt; d)[0]"><path d="M 139.500000 234.000000 C 139.500000 272.000000 132.699997 292.000000 107.749240 328.692294" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-348119987)" /></g><g id="(d -&gt; e)[0]"><path d="M 83.000000 400.000000 C 83.000000 438.000000 83.000000 458.000000 83.000000 494.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-348119987)" /></g><mask id="d2-348119987" maskUnits="userSpaceOnUse" x="-101" y="-101" width="368" height="766">
<rect x="-101" y="-101" width="368" height="766" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="22.500000" y="188.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="78.500000" y="354.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="135.500000" y="188.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="79.500000" y="520.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></g></svg></svg>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.4.2-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 200 200"><svg id="d2-svg" class="d2-121760133" width="200" height="200" viewBox="-100 -100 200 200"><rect x="-100.000000" y="-100.000000" width="200.000000" height="200.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[]]></style><style type="text/css"><![CDATA[.shape {
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 200 200"><svg id="d2-svg" class="d2-121760133" width="200" height="200" viewBox="-100 -100 200 200"><rect x="-100.000000" y="-100.000000" width="200.000000" height="200.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 271 437"><svg id="d2-svg" class="d2-1393279198" width="271" height="437" viewBox="-101 -101 271 437"><rect x="-101.000000" y="-101.000000" width="271.000000" height="437.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1393279198 .text-bold {
font-family: "d2-1393279198-font-bold";
}
@font-face {
font-family: d2-1393279198-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-1393279198 .fill-N1{fill:#0A0F25;}
.d2-1393279198 .fill-N2{fill:#676C7E;}
.d2-1393279198 .fill-N3{fill:#9499AB;}
.d2-1393279198 .fill-N4{fill:#CFD2DD;}
.d2-1393279198 .fill-N5{fill:#DEE1EB;}
.d2-1393279198 .fill-N6{fill:#EEF1F8;}
.d2-1393279198 .fill-N7{fill:#FFFFFF;}
.d2-1393279198 .fill-B1{fill:#0D32B2;}
.d2-1393279198 .fill-B2{fill:#0D32B2;}
.d2-1393279198 .fill-B3{fill:#E3E9FD;}
.d2-1393279198 .fill-B4{fill:#E3E9FD;}
.d2-1393279198 .fill-B5{fill:#EDF0FD;}
.d2-1393279198 .fill-B6{fill:#F7F8FE;}
.d2-1393279198 .fill-AA2{fill:#4A6FF3;}
.d2-1393279198 .fill-AA4{fill:#EDF0FD;}
.d2-1393279198 .fill-AA5{fill:#F7F8FE;}
.d2-1393279198 .fill-AB4{fill:#EDF0FD;}
.d2-1393279198 .fill-AB5{fill:#F7F8FE;}
.d2-1393279198 .stroke-N1{stroke:#0A0F25;}
.d2-1393279198 .stroke-N2{stroke:#676C7E;}
.d2-1393279198 .stroke-N3{stroke:#9499AB;}
.d2-1393279198 .stroke-N4{stroke:#CFD2DD;}
.d2-1393279198 .stroke-N5{stroke:#DEE1EB;}
.d2-1393279198 .stroke-N6{stroke:#EEF1F8;}
.d2-1393279198 .stroke-N7{stroke:#FFFFFF;}
.d2-1393279198 .stroke-B1{stroke:#0D32B2;}
.d2-1393279198 .stroke-B2{stroke:#0D32B2;}
.d2-1393279198 .stroke-B3{stroke:#E3E9FD;}
.d2-1393279198 .stroke-B4{stroke:#E3E9FD;}
.d2-1393279198 .stroke-B5{stroke:#EDF0FD;}
.d2-1393279198 .stroke-B6{stroke:#F7F8FE;}
.d2-1393279198 .stroke-AA2{stroke:#4A6FF3;}
.d2-1393279198 .stroke-AA4{stroke:#EDF0FD;}
.d2-1393279198 .stroke-AA5{stroke:#F7F8FE;}
.d2-1393279198 .stroke-AB4{stroke:#EDF0FD;}
.d2-1393279198 .stroke-AB5{stroke:#F7F8FE;}
.d2-1393279198 .background-color-N1{background-color:#0A0F25;}
.d2-1393279198 .background-color-N2{background-color:#676C7E;}
.d2-1393279198 .background-color-N3{background-color:#9499AB;}
.d2-1393279198 .background-color-N4{background-color:#CFD2DD;}
.d2-1393279198 .background-color-N5{background-color:#DEE1EB;}
.d2-1393279198 .background-color-N6{background-color:#EEF1F8;}
.d2-1393279198 .background-color-N7{background-color:#FFFFFF;}
.d2-1393279198 .background-color-B1{background-color:#0D32B2;}
.d2-1393279198 .background-color-B2{background-color:#0D32B2;}
.d2-1393279198 .background-color-B3{background-color:#E3E9FD;}
.d2-1393279198 .background-color-B4{background-color:#E3E9FD;}
.d2-1393279198 .background-color-B5{background-color:#EDF0FD;}
.d2-1393279198 .background-color-B6{background-color:#F7F8FE;}
.d2-1393279198 .background-color-AA2{background-color:#4A6FF3;}
.d2-1393279198 .background-color-AA4{background-color:#EDF0FD;}
.d2-1393279198 .background-color-AA5{background-color:#F7F8FE;}
.d2-1393279198 .background-color-AB4{background-color:#EDF0FD;}
.d2-1393279198 .background-color-AB5{background-color:#F7F8FE;}
.d2-1393279198 .color-N1{color:#0A0F25;}
.d2-1393279198 .color-N2{color:#676C7E;}
.d2-1393279198 .color-N3{color:#9499AB;}
.d2-1393279198 .color-N4{color:#CFD2DD;}
.d2-1393279198 .color-N5{color:#DEE1EB;}
.d2-1393279198 .color-N6{color:#EEF1F8;}
.d2-1393279198 .color-N7{color:#FFFFFF;}
.d2-1393279198 .color-B1{color:#0D32B2;}
.d2-1393279198 .color-B2{color:#0D32B2;}
.d2-1393279198 .color-B3{color:#E3E9FD;}
.d2-1393279198 .color-B4{color:#E3E9FD;}
.d2-1393279198 .color-B5{color:#EDF0FD;}
.d2-1393279198 .color-B6{color:#F7F8FE;}
.d2-1393279198 .color-AA2{color:#4A6FF3;}
.d2-1393279198 .color-AA4{color:#EDF0FD;}
.d2-1393279198 .color-AA5{color:#F7F8FE;}
.d2-1393279198 .color-AB4{color:#EDF0FD;}
.d2-1393279198 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="x"><g class="shape" ><ellipse rx="34.500000" ry="34.500000" cx="34.500000" cy="34.500000" class="shape stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="34.500000" y="40.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape" ><rect x="2.000000" y="169.000000" width="66.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="35.000000" y="207.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -&gt; y)[0]"><marker id="mk-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 34.980001 70.999900 C 34.599998 109.000000 34.500000 129.000000 34.500000 165.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-3488378134)" mask="url(#d2-1393279198)" /></g><mask id="d2-1393279198" maskUnits="userSpaceOnUse" x="-101" y="-101" width="271" height="437">
<rect x="-101" y="-101" width="271" height="437" fill="white"></rect>
<rect x="30.500000" y="24.000000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="30.500000" y="191.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.5.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 399 268"><svg id="d2-svg" class="d2-3967102011" width="399" height="268" viewBox="-101 -101 399 268"><rect x="-101.000000" y="-101.000000" width="399.000000" height="268.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3967102011 .text-bold {
font-family: "d2-3967102011-font-bold";
}
@font-face {
font-family: d2-3967102011-font-bold;
src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision;
stroke-linejoin: round;
}
.connection {
stroke-linecap: round;
stroke-linejoin: round;
}
.blend {
mix-blend-mode: multiply;
opacity: 0.5;
}
.d2-3967102011 .fill-N1{fill:#0A0F25;}
.d2-3967102011 .fill-N2{fill:#676C7E;}
.d2-3967102011 .fill-N3{fill:#9499AB;}
.d2-3967102011 .fill-N4{fill:#CFD2DD;}
.d2-3967102011 .fill-N5{fill:#DEE1EB;}
.d2-3967102011 .fill-N6{fill:#EEF1F8;}
.d2-3967102011 .fill-N7{fill:#FFFFFF;}
.d2-3967102011 .fill-B1{fill:#0D32B2;}
.d2-3967102011 .fill-B2{fill:#0D32B2;}
.d2-3967102011 .fill-B3{fill:#E3E9FD;}
.d2-3967102011 .fill-B4{fill:#E3E9FD;}
.d2-3967102011 .fill-B5{fill:#EDF0FD;}
.d2-3967102011 .fill-B6{fill:#F7F8FE;}
.d2-3967102011 .fill-AA2{fill:#4A6FF3;}
.d2-3967102011 .fill-AA4{fill:#EDF0FD;}
.d2-3967102011 .fill-AA5{fill:#F7F8FE;}
.d2-3967102011 .fill-AB4{fill:#EDF0FD;}
.d2-3967102011 .fill-AB5{fill:#F7F8FE;}
.d2-3967102011 .stroke-N1{stroke:#0A0F25;}
.d2-3967102011 .stroke-N2{stroke:#676C7E;}
.d2-3967102011 .stroke-N3{stroke:#9499AB;}
.d2-3967102011 .stroke-N4{stroke:#CFD2DD;}
.d2-3967102011 .stroke-N5{stroke:#DEE1EB;}
.d2-3967102011 .stroke-N6{stroke:#EEF1F8;}
.d2-3967102011 .stroke-N7{stroke:#FFFFFF;}
.d2-3967102011 .stroke-B1{stroke:#0D32B2;}
.d2-3967102011 .stroke-B2{stroke:#0D32B2;}
.d2-3967102011 .stroke-B3{stroke:#E3E9FD;}
.d2-3967102011 .stroke-B4{stroke:#E3E9FD;}
.d2-3967102011 .stroke-B5{stroke:#EDF0FD;}
.d2-3967102011 .stroke-B6{stroke:#F7F8FE;}
.d2-3967102011 .stroke-AA2{stroke:#4A6FF3;}
.d2-3967102011 .stroke-AA4{stroke:#EDF0FD;}
.d2-3967102011 .stroke-AA5{stroke:#F7F8FE;}
.d2-3967102011 .stroke-AB4{stroke:#EDF0FD;}
.d2-3967102011 .stroke-AB5{stroke:#F7F8FE;}
.d2-3967102011 .background-color-N1{background-color:#0A0F25;}
.d2-3967102011 .background-color-N2{background-color:#676C7E;}
.d2-3967102011 .background-color-N3{background-color:#9499AB;}
.d2-3967102011 .background-color-N4{background-color:#CFD2DD;}
.d2-3967102011 .background-color-N5{background-color:#DEE1EB;}
.d2-3967102011 .background-color-N6{background-color:#EEF1F8;}
.d2-3967102011 .background-color-N7{background-color:#FFFFFF;}
.d2-3967102011 .background-color-B1{background-color:#0D32B2;}
.d2-3967102011 .background-color-B2{background-color:#0D32B2;}
.d2-3967102011 .background-color-B3{background-color:#E3E9FD;}
.d2-3967102011 .background-color-B4{background-color:#E3E9FD;}
.d2-3967102011 .background-color-B5{background-color:#EDF0FD;}
.d2-3967102011 .background-color-B6{background-color:#F7F8FE;}
.d2-3967102011 .background-color-AA2{background-color:#4A6FF3;}
.d2-3967102011 .background-color-AA4{background-color:#EDF0FD;}
.d2-3967102011 .background-color-AA5{background-color:#F7F8FE;}
.d2-3967102011 .background-color-AB4{background-color:#EDF0FD;}
.d2-3967102011 .background-color-AB5{background-color:#F7F8FE;}
.d2-3967102011 .color-N1{color:#0A0F25;}
.d2-3967102011 .color-N2{color:#676C7E;}
.d2-3967102011 .color-N3{color:#9499AB;}
.d2-3967102011 .color-N4{color:#CFD2DD;}
.d2-3967102011 .color-N5{color:#DEE1EB;}
.d2-3967102011 .color-N6{color:#EEF1F8;}
.d2-3967102011 .color-N7{color:#FFFFFF;}
.d2-3967102011 .color-B1{color:#0D32B2;}
.d2-3967102011 .color-B2{color:#0D32B2;}
.d2-3967102011 .color-B3{color:#E3E9FD;}
.d2-3967102011 .color-B4{color:#E3E9FD;}
.d2-3967102011 .color-B5{color:#EDF0FD;}
.d2-3967102011 .color-B6{color:#F7F8FE;}
.d2-3967102011 .color-AA2{color:#4A6FF3;}
.d2-3967102011 .color-AA4{color:#EDF0FD;}
.d2-3967102011 .color-AA5{color:#F7F8FE;}
.d2-3967102011 .color-AB4{color:#EDF0FD;}
.d2-3967102011 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g id="jon"><g class="shape" ><rect x="0.000000" y="0.000000" width="69.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="34.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">jon</text></g><g id="jan"><g class="shape" ><rect x="129.000000" y="0.000000" width="68.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="163.000000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">jan</text></g><mask id="d2-3967102011" maskUnits="userSpaceOnUse" x="-101" y="-101" width="399" height="268">
<rect x="-101" y="-101" width="399" height="268" fill="white"></rect>
<rect x="22.500000" y="22.500000" width="24" height="21" fill="rgba(0,0,0,0.75)"></rect>
<rect x="151.500000" y="22.500000" width="23" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

Some files were not shown because too many files have changed in this diff Show more