This commit is contained in:
Barry Nolte 2023-08-01 20:03:54 -07:00
commit 64f3136c03
No known key found for this signature in database
GPG key ID: 97D97D823FB7D867
847 changed files with 128780 additions and 64926 deletions

View file

@ -16,7 +16,7 @@ jobs:
with: with:
go-version-file: ./go.mod go-version-file: ./go.mod
cache: true cache: true
- run: COLOR=1 CI_FORCE=1 ./make.sh all race - run: DAILY=1 COLOR=1 CI_FORCE=1 ./make.sh all race
env: env:
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}

View file

@ -1,21 +1,5 @@
#### Features 🚀 #### Features 🚀
- Configure timeout value with D2_TIMEOUT env var [#1392](https://github.com/terrastruct/d2/pull/1392)
- Scale renders and disable fit to screen with `--scale` flag [#1413](https://github.com/terrastruct/d2/pull/1413)
- `null` keyword can be used to un-declare. See [docs](https://d2lang.com/tour/TODO) [#1446](https://github.com/terrastruct/d2/pull/1446)
#### Improvements 🧹 #### Improvements 🧹
- 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)
- Improved rendering and text measurement for code shapes [#1425](https://github.com/terrastruct/d2/pull/1425)
- The autoformatter moves board declarations to the bottom of its scope [#1424](https://github.com/terrastruct/d2/pull/1424)
- All font styles in sketch mode use a consistent font-family [#1463](https://github.com/terrastruct/d2/pull/1463)
- Tooltip and link icons are now positioned on shape border [#1466](https://github.com/terrastruct/d2/pull/1466)
- Tooltip and link icons are always rendered over shapes [#1467](https://github.com/terrastruct/d2/pull/1467)
#### Bugfixes ⛑️ #### Bugfixes ⛑️
- 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,54 @@
D2 v0.6 introduces variable substitutions and globs. These two were the last of the features planned in the initial designs for D2, and v1 is now very close!
The power of variables and globs in a programming language need no introduction, so here's two minimal examples to get started:
**Variables**:
```d2
vars: {
color: aquamarine
}
x.style.fill: ${color}
```
**Globs**:
```d2
x
y
z
*.style.fill: aquamarine
```
Both are live on [D2 Playground](https://play.d2lang.com) so give it a try! Looking forward to your issues and iterating
Layout capability also takes a subtle but important step forward: you can now customize the position of labels and icons.
#### Features 🚀
- Variables and substitutions are implemented. See [docs](https://d2lang.com/tour/vars). [#1473](https://github.com/terrastruct/d2/pull/1473)
- Configure timeout value with D2_TIMEOUT env var [#1392](https://github.com/terrastruct/d2/pull/1392)
- Scale renders and disable fit to screen with `--scale` flag [#1413](https://github.com/terrastruct/d2/pull/1413)
- `null` keyword can be used to un-declare. See [docs](https://d2lang.com/tour/overrides#null) [#1446](https://github.com/terrastruct/d2/pull/1446)
- Develop multi-board diagrams in watch mode (links to layers/scenarios/steps work in `--watch`) [#1503](https://github.com/terrastruct/d2/pull/1503)
- Glob patterns have been implemented. See [docs](https://d2lang.com/tour/globs). [#1479](https://github.com/terrastruct/d2/pull/1479)
- Ampersand filters have been implemented. See [docs](https://d2lang.com/tour/filters). [#1509](https://github.com/terrastruct/d2/pull/1509)
#### Improvements 🧹
- 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)
- Improved rendering and text measurement for code shapes [#1425](https://github.com/terrastruct/d2/pull/1425)
- The autoformatter moves board declarations to the bottom of its scope [#1424](https://github.com/terrastruct/d2/pull/1424)
- All font styles in sketch mode use a consistent font-family [#1463](https://github.com/terrastruct/d2/pull/1463)
- Tooltip and link icons are positioned on shape border [#1466](https://github.com/terrastruct/d2/pull/1466)
- Tooltip and link icons are always rendered over shapes [#1467](https://github.com/terrastruct/d2/pull/1467)
- Boards with no objects are considered folders [#1504](https://github.com/terrastruct/d2/pull/1504)
- `DEBUG` environment variable ignored if set incorrectly [#1505](https://github.com/terrastruct/d2/pull/1505)
#### Bugfixes ⛑️
- 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)
- Fixes person shape label positioning with `multiple` or `3d` [#1478](https://github.com/terrastruct/d2/pull/1478)

View file

@ -1,5 +1,5 @@
# https://hub.docker.com/repository/docker/terrastruct/d2 # https://hub.docker.com/repository/docker/terrastruct/d2
FROM debian:latest FROM ubuntu:latest
ARG TARGETARCH ARG TARGETARCH

View file

@ -500,6 +500,19 @@ type Number struct {
type UnquotedString struct { type UnquotedString struct {
Range Range `json:"range"` Range Range `json:"range"`
Value []InterpolationBox `json:"value"` Value []InterpolationBox `json:"value"`
// Pattern holds the parsed glob pattern if in a key and the unquoted string represents a valid pattern.
Pattern []string `json:"pattern,omitempty"`
}
func (s *UnquotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
} }
func FlatUnquotedString(s string) *UnquotedString { func FlatUnquotedString(s string) *UnquotedString {
@ -513,6 +526,17 @@ type DoubleQuotedString struct {
Value []InterpolationBox `json:"value"` Value []InterpolationBox `json:"value"`
} }
func (s *DoubleQuotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
}
func FlatDoubleQuotedString(s string) *DoubleQuotedString { func FlatDoubleQuotedString(s string) *DoubleQuotedString {
return &DoubleQuotedString{ return &DoubleQuotedString{
Value: []InterpolationBox{{String: &s}}, Value: []InterpolationBox{{String: &s}},
@ -586,7 +610,7 @@ func (m *Map) IsFileMap() bool {
type Key struct { type Key struct {
Range Range `json:"range"` Range Range `json:"range"`
// Indicates this MapKey is an override selector. // Indicates this MapKey is a filter selector.
Ampersand bool `json:"ampersand,omitempty"` Ampersand bool `json:"ampersand,omitempty"`
// At least one of Key and Edges will be set but all four can also be set. // At least one of Key and Edges will be set but all four can also be set.
@ -696,6 +720,19 @@ func (mk *Key) SetScalar(scalar ScalarBox) {
} }
} }
func (mk *Key) HasQueryGlob() bool {
if mk.Key.HasGlob() && len(mk.Edges) == 0 {
return true
}
if mk.EdgeIndex != nil && mk.EdgeIndex.Glob && mk.EdgeKey == nil {
return true
}
if mk.EdgeKey.HasGlob() {
return true
}
return false
}
type KeyPath struct { type KeyPath struct {
Range Range `json:"range"` Range Range `json:"range"`
Path []*StringBox `json:"path"` Path []*StringBox `json:"path"`
@ -716,6 +753,37 @@ func (kp *KeyPath) IDA() (ida []string) {
return ida return ida
} }
func (kp *KeyPath) Copy() *KeyPath {
kp2 := *kp
kp2.Path = nil
kp2.Path = append(kp2.Path, kp.Path...)
return &kp2
}
func (kp *KeyPath) HasDoubleGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && el.ScalarString() == "**" {
return true
}
}
return false
}
func (kp *KeyPath) HasGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && len(el.UnquotedString.Pattern) > 0 {
return true
}
}
return false
}
type Edge struct { type Edge struct {
Range Range `json:"range"` Range Range `json:"range"`
@ -1034,6 +1102,10 @@ func (sb *StringBox) Unbox() String {
} }
} }
func (sb *StringBox) ScalarString() string {
return sb.Unbox().ScalarString()
}
// InterpolationBox is used to select between strings and substitutions in unquoted and // InterpolationBox is used to select between strings and substitutions in unquoted and
// double quoted strings. There is no corresponding interface to avoid unnecessary // double quoted strings. There is no corresponding interface to avoid unnecessary
// abstraction. // abstraction.
@ -1046,12 +1118,11 @@ type InterpolationBox struct {
// & is only special if it begins a key. // & is only special if it begins a key.
// - is only special if followed by another - in a key. // - is only special if followed by another - in a key.
// ' " and | are only special if they begin an unquoted key or value. // ' " and | are only special if they begin an unquoted key or value.
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@'}) var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@', '&'})
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'}) var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})
// RawString returns s in a AST String node that can format s in the most aesthetically // RawString returns s in a AST String node that can format s in the most aesthetically
// pleasing way. // pleasing way.
// TODO: Return StringBox
func RawString(s string, inKey bool) String { func RawString(s string, inKey bool) String {
if s == "" { if s == "" {
return FlatDoubleQuotedString(s) return FlatDoubleQuotedString(s)
@ -1064,10 +1135,6 @@ func RawString(s string, inKey bool) String {
if i+1 < len(s) && s[i+1] != '-' { if i+1 < len(s) && s[i+1] != '-' {
continue continue
} }
case '&':
if i > 0 {
continue
}
} }
if strings.ContainsRune(UnquotedKeySpecials, r) { if strings.ContainsRune(UnquotedKeySpecials, r) {
if !strings.ContainsRune(s, '"') { if !strings.ContainsRune(s, '"') {

View file

@ -102,7 +102,7 @@ func test(t *testing.T, textPath, text string) {
t.Fatal(err) t.Fatal(err)
} }
g, err := d2compiler.Compile("", strings.NewReader(text), nil) g, _, err := d2compiler.Compile("", strings.NewReader(text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -20,6 +20,7 @@ import (
"oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2plugin"
@ -66,7 +67,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
} }
debugFlag, err := ms.Opts.Bool("DEBUG", "debug", "d", false, "print debug logs.") debugFlag, err := ms.Opts.Bool("DEBUG", "debug", "d", false, "print debug logs.")
if err != nil { if err != nil {
return err ms.Log.Warn.Printf("Invalid DEBUG flag value ignored")
debugFlag = go2.Pointer(false)
} }
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.") 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 { if err != nil {
@ -117,11 +119,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.") fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.")
fontSemiboldFlag := ms.Opts.String("D2_FONT_SEMIBOLD", "font-semibold", "", "", "path to .ttf file to use for the semibold font. If none provided, Source Sans Pro Semibold is used.") fontSemiboldFlag := ms.Opts.String("D2_FONT_SEMIBOLD", "font-semibold", "", "", "path to .ttf file to use for the semibold font. If none provided, Source Sans Pro Semibold is used.")
ps, err := d2plugin.ListPlugins(ctx) plugins, err := d2plugin.ListPlugins(ctx)
if err != nil { if err != nil {
return err return err
} }
err = populateLayoutOpts(ctx, ms, ps) err = populateLayoutOpts(ctx, ms, plugins)
if err != nil { if err != nil {
return err return err
} }
@ -146,7 +148,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
case "init-playwright": case "init-playwright":
return initPlaywright() return initPlaywright()
case "layout": case "layout":
return layoutCmd(ctx, ms, ps) return layoutCmd(ctx, ms, plugins)
case "themes": case "themes":
themesCmd(ctx, ms) themesCmd(ctx, ms)
return nil return nil
@ -226,6 +228,38 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
} }
ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag) ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag)
// If flag is not explicitly set by user, set to nil.
// Later, configs from D2 code will only overwrite if they weren't explicitly set by user
flagSet := make(map[string]struct{})
ms.Opts.Flags.Visit(func(f *pflag.Flag) {
flagSet[f.Name] = struct{}{}
})
if ms.Env.Getenv("D2_LAYOUT") == "" {
if _, ok := flagSet["layout"]; !ok {
layoutFlag = nil
}
}
if ms.Env.Getenv("D2_THEME") == "" {
if _, ok := flagSet["theme"]; !ok {
themeFlag = nil
}
}
if ms.Env.Getenv("D2_SKETCH") == "" {
if _, ok := flagSet["sketch"]; !ok {
sketchFlag = nil
}
}
if ms.Env.Getenv("D2_PAD") == "" {
if _, ok := flagSet["pad"]; !ok {
padFlag = nil
}
}
if ms.Env.Getenv("D2_CENTER") == "" {
if _, ok := flagSet["center"]; !ok {
centerFlag = nil
}
}
if *darkThemeFlag == -1 { if *darkThemeFlag == -1 {
darkThemeFlag = nil // TODO this is a temporary solution: https://github.com/terrastruct/util-go/issues/7 darkThemeFlag = nil // TODO this is a temporary solution: https://github.com/terrastruct/util-go/issues/7
} }
@ -241,29 +275,6 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
scale = scaleFlag scale = scaleFlag
} }
plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return layoutNotFound(ctx, ps, *layoutFlag)
}
return err
}
err = d2plugin.HydratePluginOpts(ctx, ms, plugin)
if err != nil {
return err
}
pinfo, err := plugin.Info(ctx)
if err != nil {
return err
}
plocation := pinfo.Type
if pinfo.Type == "binary" {
plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path))
}
ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, plocation)
if !outputFormat.supportsDarkTheme() { if !outputFormat.supportsDarkTheme() {
if darkThemeFlag != nil { if darkThemeFlag != nil {
ms.Log.Warn.Printf("--dark-theme cannot be used while exporting to another format other than .svg") ms.Log.Warn.Printf("--dark-theme cannot be used while exporting to another format other than .svg")
@ -285,10 +296,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
} }
renderOpts := d2svg.RenderOpts{ renderOpts := d2svg.RenderOpts{
Pad: int(*padFlag), Pad: padFlag,
Sketch: *sketchFlag, Sketch: sketchFlag,
Center: *centerFlag, Center: centerFlag,
ThemeID: *themeFlag, ThemeID: themeFlag,
DarkThemeID: darkThemeFlag, DarkThemeID: darkThemeFlag,
Scale: scale, Scale: scale,
} }
@ -298,7 +309,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin") return xmain.UsageErrorf("-w[atch] cannot be combined with reading input from stdin")
} }
w, err := newWatcher(ctx, ms, watcherOpts{ w, err := newWatcher(ctx, ms, watcherOpts{
layoutPlugin: plugin, plugins: plugins,
layout: layoutFlag,
renderOpts: renderOpts, renderOpts: renderOpts,
animateInterval: *animateIntervalFlag, animateInterval: *animateIntervalFlag,
host: *hostFlag, host: *hostFlag,
@ -319,7 +331,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2) ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
defer cancel() defer cancel()
_, written, err := compile(ctx, ms, plugin, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page) _, written, err := compile(ctx, ms, plugins, layoutFlag, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, "", *bundleFlag, *forceAppendixFlag, pw.Page)
if err != nil { if err != nil {
if written { if written {
return fmt.Errorf("failed to fully compile (partial render written): %w", err) return fmt.Errorf("failed to fully compile (partial render written): %w", err)
@ -329,7 +341,32 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return nil return nil
} }
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) { func LayoutResolver(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin) func(engine string) (d2graph.LayoutGraph, error) {
cached := make(map[string]d2graph.LayoutGraph)
return func(engine string) (d2graph.LayoutGraph, error) {
if c, ok := cached[engine]; ok {
return c, nil
}
plugin, err := d2plugin.FindPlugin(ctx, plugins, engine)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return nil, layoutNotFound(ctx, plugins, engine)
}
return nil, err
}
err = d2plugin.HydratePluginOpts(ctx, ms, plugin)
if err != nil {
return nil, err
}
cached[engine] = plugin.Layout
return plugin.Layout, nil
}
}
func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, layout *string, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath, boardPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
start := time.Now() start := time.Now()
input, err := ms.ReadPath(inputPath) input, err := ms.ReadPath(inputPath)
if err != nil { if err != nil {
@ -341,16 +378,12 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
return nil, false, err return nil, false, err
} }
layout := plugin.Layout
opts := &d2lib.CompileOptions{ opts := &d2lib.CompileOptions{
Layout: layout, Ruler: ruler,
Ruler: ruler, FontFamily: fontFamily,
ThemeID: renderOpts.ThemeID, InputPath: inputPath,
FontFamily: fontFamily, LayoutResolver: LayoutResolver(ctx, ms, plugins),
InputPath: inputPath, Layout: layout,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
} }
cancel := background.Repeat(func() { cancel := background.Repeat(func() {
@ -358,12 +391,14 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
}, time.Second*5) }, time.Second*5)
defer cancel() defer cancel()
diagram, g, err := d2lib.Compile(ctx, string(input), opts) diagram, g, err := d2lib.Compile(ctx, string(input), opts, &renderOpts)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
cancel() cancel()
plugin, _ := d2plugin.FindPlugin(ctx, plugins, *opts.Layout)
if animateInterval > 0 { if animateInterval > 0 {
masterID, err := diagram.HashID() masterID, err := diagram.HashID()
if err != nil { if err != nil {
@ -372,6 +407,16 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
renderOpts.MasterID = masterID renderOpts.MasterID = masterID
} }
pinfo, err := plugin.Info(ctx)
if err != nil {
return nil, false, err
}
plocation := pinfo.Type
if pinfo.Type == "binary" {
plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path))
}
ms.Log.Debug.Printf("using layout plugin %s (%s)", *opts.Layout, plocation)
pluginInfo, err := plugin.Info(ctx) pluginInfo, err := plugin.Info(ctx)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@ -455,7 +500,12 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
} }
} }
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram) board := diagram.GetBoard(boardPath)
if board == nil {
return nil, false, fmt.Errorf("Diagram with path %s not found", boardPath)
}
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, board)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -805,7 +855,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
if err != nil { if err != nil {
return svg, err return svg, err
} }
err = doc.AddPDFPage(pngImg, boardPath, opts.ThemeID, rootFill, diagram.Shapes, int64(opts.Pad), viewboxX, viewboxY, pageMap) err = doc.AddPDFPage(pngImg, boardPath, *opts.ThemeID, rootFill, diagram.Shapes, *opts.Pad, viewboxX, viewboxY, pageMap)
if err != nil { if err != nil {
return svg, err return svg, err
} }

View file

@ -8,9 +8,7 @@ function init(reconnectDelay) {
const d2SVG = window.document.querySelector("#d2-svg-container"); const d2SVG = window.document.querySelector("#d2-svg-container");
const devMode = document.body.dataset.d2DevMode === "true"; const devMode = document.body.dataset.d2DevMode === "true";
const ws = new WebSocket( const ws = new WebSocket(`ws://${window.location.host}/watch`);
`ws://${window.location.host}${window.location.pathname}watch`
);
let isInit = true; let isInit = true;
let ratio; let ratio;
ws.onopen = () => { ws.onopen = () => {
@ -28,7 +26,7 @@ function init(reconnectDelay) {
// we can't just set `d2SVG.innerHTML = msg.svg` need to parse this as xml not html // we can't just set `d2SVG.innerHTML = msg.svg` need to parse this as xml not html
const parsedXML = new DOMParser().parseFromString(msg.svg, "text/xml"); const parsedXML = new DOMParser().parseFromString(msg.svg, "text/xml");
d2SVG.replaceChildren(parsedXML.documentElement); d2SVG.replaceChildren(parsedXML.documentElement);
changeFavicon("./static/favicon.ico"); changeFavicon("/static/favicon.ico");
const svgEl = d2SVG.querySelector("#d2-svg"); const svgEl = d2SVG.querySelector("#d2-svg");
// just use inner SVG in watch mode // just use inner SVG in watch mode
svgEl.parentElement.replaceWith(svgEl); svgEl.parentElement.replaceWith(svgEl);
@ -60,7 +58,7 @@ function init(reconnectDelay) {
if (msg.err) { if (msg.err) {
d2ErrDiv.innerText = msg.err; d2ErrDiv.innerText = msg.err;
d2ErrDiv.style.display = "block"; d2ErrDiv.style.display = "block";
changeFavicon("./static/favicon-err.ico"); changeFavicon("/static/favicon-err.ico");
d2ErrDiv.scrollIntoView(); d2ErrDiv.scrollIntoView();
} }
}; };

View file

@ -12,6 +12,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"sync" "sync"
"time" "time"
@ -41,13 +42,15 @@ var devMode = false
var staticFS embed.FS var staticFS embed.FS
type watcherOpts struct { type watcherOpts struct {
layoutPlugin d2plugin.Plugin layout *string
plugins []d2plugin.Plugin
renderOpts d2svg.RenderOpts renderOpts d2svg.RenderOpts
animateInterval int64 animateInterval int64
host string host string
port string port string
inputPath string inputPath string
outputPath string outputPath string
boardPath string
pwd string pwd string
bundle bool bundle bool
forceAppendix bool forceAppendix bool
@ -361,7 +364,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
w.pw = newPW w.pw = newPW
} }
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page) svg, _, err := compile(ctx, w.ms, w.plugins, w.layout, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.boardPath, w.bundle, w.forceAppendix, w.pw.Page)
errs := "" errs := ""
if err != nil { if err != nil {
if len(svg) > 0 { if len(svg) > 0 {
@ -429,15 +432,25 @@ func (w *watcher) handleRoot(hw http.ResponseWriter, r *http.Request) {
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>%s</title> <title>%s</title>
<script src="./static/watch.js"></script> <script src="/static/watch.js"></script>
<link rel="stylesheet" href="./static/watch.css"> <link rel="stylesheet" href="/static/watch.css">
<link id="favicon" rel="icon" href="./static/favicon.ico"> <link id="favicon" rel="icon" href="/static/favicon.ico">
</head> </head>
<body data-d2-dev-mode=%t> <body data-d2-dev-mode=%t>
<div id="d2-err" style="display: none"></div> <div id="d2-err" style="display: none"></div>
<div id="d2-svg-container"></div> <div id="d2-svg-container"></div>
</body> </body>
</html>`, filepath.Base(w.outputPath), w.devMode) </html>`, filepath.Base(w.outputPath), w.devMode)
// if path is "/x.svg", we just want "x"
boardPath := strings.TrimPrefix(r.URL.Path, "/")
if idx := strings.LastIndexByte(boardPath, '.'); idx != -1 {
boardPath = boardPath[:idx]
}
if boardPath != w.boardPath {
w.boardPath = boardPath
w.requestCompile()
}
} }
func (w *watcher) handleWatch(hw http.ResponseWriter, r *http.Request) error { func (w *watcher) handleWatch(hw http.ResponseWriter, r *http.Request) error {

View file

@ -27,7 +27,7 @@ type CompileOptions struct {
FS fs.FS FS fs.FS
} }
func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, error) { func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, *d2target.Config, error) {
if opts == nil { if opts == nil {
opts = &CompileOptions{} opts = &CompileOptions{}
} }
@ -36,7 +36,7 @@ func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, e
UTF16: opts.UTF16, UTF16: opts.UTF16,
}) })
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{ ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
@ -44,16 +44,16 @@ func Compile(p string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph, e
FS: opts.FS, FS: opts.FS,
}) })
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
g, err := compileIR(ast, ir) g, err := compileIR(ast, ir)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
g.SortObjectsByAST() g.SortObjectsByAST()
g.SortEdgesByAST() g.SortEdgesByAST()
return g, nil return g, compileConfig(ir), nil
} }
func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) { func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
@ -92,6 +92,9 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
g.IsFolderOnly = true g.IsFolderOnly = true
} }
} }
if len(g.Objects) == 0 {
g.IsFolderOnly = true
}
return g return g
} }
@ -277,6 +280,8 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
} }
} }
return return
} else if f.Name == "vars" {
return
} else if isReserved { } else if isReserved {
c.compileReserved(&obj.Attributes, f) c.compileReserved(&obj.Attributes, f)
return return
@ -329,7 +334,7 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
Scope: fr.Context.Scope, Scope: fr.Context.Scope,
ScopeAST: fr.Context.ScopeAST, ScopeAST: fr.Context.ScopeAST,
} }
if fr.Context.ScopeMap != nil { if fr.Context.ScopeMap != nil && !d2ir.IsVar(fr.Context.ScopeMap) {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(fr.Context.ScopeMap)) scopeObjIDA := d2graphIDA(d2ir.BoardIDA(fr.Context.ScopeMap))
r.ScopeObj = obj.Graph.Root.EnsureChild(scopeObjIDA) r.ScopeObj = obj.Graph.Root.EnsureChild(scopeObjIDA)
} }
@ -725,7 +730,7 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
Scope: er.Context.Scope, Scope: er.Context.Scope,
ScopeAST: er.Context.ScopeAST, ScopeAST: er.Context.ScopeAST,
} }
if er.Context.ScopeMap != nil { if er.Context.ScopeMap != nil && !d2ir.IsVar(er.Context.ScopeMap) {
scopeObjIDA := d2graphIDA(d2ir.BoardIDA(er.Context.ScopeMap)) scopeObjIDA := d2graphIDA(d2ir.BoardIDA(er.Context.ScopeMap))
r.ScopeObj = edge.Src.Graph.Root.EnsureChild(scopeObjIDA) r.ScopeObj = edge.Src.Graph.Root.EnsureChild(scopeObjIDA)
} }
@ -1283,3 +1288,45 @@ func parentSeqDiagram(n d2ir.Node) *d2ir.Map {
n = m n = m
} }
} }
func compileConfig(ir *d2ir.Map) *d2target.Config {
f := ir.GetField("vars", "d2-config")
if f == nil || f.Map() == nil {
return nil
}
configMap := f.Map()
config := &d2target.Config{}
f = configMap.GetField("sketch")
if f != nil {
val, _ := strconv.ParseBool(f.Primary().Value.ScalarString())
config.Sketch = &val
}
f = configMap.GetField("theme-id")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.ThemeID = go2.Pointer(int64(val))
}
f = configMap.GetField("dark-theme-id")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.DarkThemeID = go2.Pointer(int64(val))
}
f = configMap.GetField("pad")
if f != nil {
val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
config.Pad = go2.Pointer(int64(val))
}
f = configMap.GetField("layout-engine")
if f != nil {
config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString())
}
return config
}

File diff suppressed because it is too large Load diff

View file

@ -12,12 +12,15 @@ import (
"oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2exporter"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2layouts/d2grid" "oss.terrastruct.com/d2/d2layouts/d2grid"
"oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/log" "oss.terrastruct.com/d2/lib/log"
@ -219,7 +222,7 @@ func run(t *testing.T, tc testCase) {
ctx = log.WithTB(ctx, t, nil) ctx = log.WithTB(ctx, t, nil)
ctx = log.Leveled(ctx, slog.LevelDebug) ctx = log.Leveled(ctx, slog.LevelDebug)
g, err := d2compiler.Compile("", strings.NewReader(tc.dsl), &d2compiler.CompileOptions{ g, config, err := d2compiler.Compile("", strings.NewReader(tc.dsl), &d2compiler.CompileOptions{
UTF16: true, UTF16: true,
}) })
if err != nil { if err != nil {
@ -241,6 +244,9 @@ func run(t *testing.T, tc testCase) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if got != nil {
got.Config = config
}
if tc.assertions != nil { if tc.assertions != nil {
t.Run("assertions", func(t *testing.T) { t.Run("assertions", func(t *testing.T) {
@ -267,3 +273,57 @@ func run(t *testing.T, tc testCase) {
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got) err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
assert.Success(t, err) assert.Success(t, err)
} }
// TestHashID tests that 2 diagrams with different theme configs do not equal each other
func TestHashID(t *testing.T) {
ctx := context.Background()
ctx = log.WithTB(ctx, t, nil)
ctx = log.Leveled(ctx, slog.LevelDebug)
aString := `
vars: {
d2-config: {
theme-id: 3
}
}
a -> b
`
bString := `
vars: {
d2-config: {
theme-id: 4
}
}
a -> b
`
da, err := compile(ctx, aString)
assert.JSON(t, nil, err)
db, err := compile(ctx, bString)
assert.JSON(t, nil, err)
hashA, err := da.HashID()
assert.JSON(t, nil, err)
hashB, err := db.HashID()
assert.JSON(t, nil, err)
assert.NotEqual(t, hashA, hashB)
}
func layoutResolver(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
func compile(ctx context.Context, d2 string) (*d2target.Diagram, error) {
ruler, _ := textmeasure.NewRuler()
opts := &d2lib.CompileOptions{
Ruler: ruler,
LayoutResolver: layoutResolver,
Layout: go2.Pointer("dagre"),
}
d, _, e := d2lib.Compile(ctx, d2, opts, nil)
return d, e
}

View file

@ -1651,6 +1651,7 @@ var SimpleReservedKeywords = map[string]struct{}{
"vertical-gap": {}, "vertical-gap": {},
"horizontal-gap": {}, "horizontal-gap": {},
"class": {}, "class": {},
"vars": {},
} }
// ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites // ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites

View file

@ -1,6 +1,7 @@
package d2graph package d2graph
import ( import (
"sort"
"strings" "strings"
"oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2target"
@ -316,10 +317,29 @@ func (obj *Object) GetLabelTopLeft() *geo.Point {
return labelTL return labelTL
} }
func (obj *Object) GetIconTopLeft() *geo.Point {
if obj.IconPosition == nil {
return nil
}
s := obj.ToShape()
iconPosition := label.Position(*obj.IconPosition)
var box *geo.Box
if iconPosition.IsOutside() {
box = s.GetBox()
} else {
box = s.GetInnerBox()
}
return iconPosition.GetPointOnBox(box, label.PADDING, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
}
func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (newStart, newEnd int) { func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (newStart, newEnd int) {
srcShape := edge.Src.ToShape() srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape() dstShape := edge.Dst.ToShape()
startingSegment := geo.Segment{Start: points[startIndex+1], End: points[startIndex]}
// if an edge runs into an outside label, stop the edge at the label instead // if an edge runs into an outside label, stop the edge at the label instead
overlapsOutsideLabel := false overlapsOutsideLabel := false
if edge.Src.HasLabel() { if edge.Src.HasLabel() {
@ -330,18 +350,27 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
labelHeight := float64(edge.Src.LabelDimensions.Height) labelHeight := float64(edge.Src.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Src.Box, label.PADDING, labelWidth, labelHeight) 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) labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box // add left/right padding to box
labelBox.TopLeft.X -= label.PADDING labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING labelBox.Width += 2 * label.PADDING
for labelBox.Contains(startingSegment.End) && startIndex+1 > endIndex {
startingSegment.Start = startingSegment.End
startingSegment.End = points[startIndex+2]
startIndex++
}
if intersections := labelBox.Intersections(startingSegment); len(intersections) > 0 { if intersections := labelBox.Intersections(startingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true overlapsOutsideLabel = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(labelPosition, intersections)
}
// move starting segment to label intersection point // move starting segment to label intersection point
points[startIndex] = intersections[0] points[startIndex] = p
startingSegment.End = intersections[0] startingSegment.End = p
// if the segment becomes too short, just merge it with the next segment // if the segment becomes too short, just merge it with the next segment
if startIndex < len(points) && startingSegment.Length() < MIN_SEGMENT_LEN { if startIndex+1 < endIndex && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex] points[startIndex+1] = points[startIndex]
startIndex++ startIndex++
} }
@ -349,9 +378,20 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
} }
} }
if !overlapsOutsideLabel { if !overlapsOutsideLabel {
if intersections := edge.Src.Intersections(startingSegment); len(intersections) > 0 {
// move starting segment to 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+1 < endIndex && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex]
startIndex++
}
}
// trace the edge to the specific shape's border // trace the edge to the specific shape's border
points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1]) points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1])
} }
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
overlapsOutsideLabel = false overlapsOutsideLabel = false
if edge.Dst.HasLabel() { if edge.Dst.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label // assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
@ -361,18 +401,26 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
labelHeight := float64(edge.Dst.LabelDimensions.Height) labelHeight := float64(edge.Dst.LabelDimensions.Height)
labelTL := labelPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight) 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) labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box // add left/right padding to box
labelBox.TopLeft.X -= label.PADDING labelBox.TopLeft.X -= label.PADDING
labelBox.Width += 2 * label.PADDING labelBox.Width += 2 * label.PADDING
for labelBox.Contains(endingSegment.Start) && endIndex-1 > startIndex {
endingSegment.End = endingSegment.Start
endingSegment.Start = points[endIndex-2]
endIndex--
}
if intersections := labelBox.Intersections(endingSegment); len(intersections) > 0 { if intersections := labelBox.Intersections(endingSegment); len(intersections) > 0 {
overlapsOutsideLabel = true overlapsOutsideLabel = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(labelPosition, intersections)
}
// move ending segment to label intersection point // move ending segment to label intersection point
points[endIndex] = intersections[0] points[endIndex] = p
endingSegment.End = intersections[0] endingSegment.End = p
// if the segment becomes too short, just merge it with the previous segment // if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > 0 && endingSegment.Length() < MIN_SEGMENT_LEN { if endIndex-1 > startIndex && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex] points[endIndex-1] = points[endIndex]
endIndex-- endIndex--
} }
@ -380,7 +428,39 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
} }
} }
if !overlapsOutsideLabel { if !overlapsOutsideLabel {
if intersections := edge.Dst.Intersections(endingSegment); len(intersections) > 0 {
// move ending segment to 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 > startIndex && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
}
points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1]) points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1])
} }
return startIndex, endIndex return startIndex, endIndex
} }
func findOuterIntersection(labelPosition label.Position, intersections []*geo.Point) *geo.Point {
switch labelPosition {
case label.OutsideTopLeft, label.OutsideTopRight, label.OutsideTopCenter:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].Y < intersections[j].Y
})
case label.OutsideBottomLeft, label.OutsideBottomRight, label.OutsideBottomCenter:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].Y > intersections[j].Y
})
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].X < intersections[j].X
})
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
sort.Slice(intersections, func(i, j int) bool {
return intersections[i].X > intersections[j].X
})
}
return intersections[0]
}

View file

@ -13,7 +13,7 @@ import (
func TestSerialization(t *testing.T) { func TestSerialization(t *testing.T) {
t.Parallel() t.Parallel()
g, err := d2compiler.Compile("", strings.NewReader("a.a.b -> a.a.c"), nil) g, _, err := d2compiler.Compile("", strings.NewReader("a.a.b -> a.a.c"), nil)
assert.Nil(t, err) assert.Nil(t, err)
asserts := func(g *d2graph.Graph) { asserts := func(g *d2graph.Graph) {
@ -53,7 +53,7 @@ func TestCasingRegression(t *testing.T) {
script := `UserCreatedTypeField` script := `UserCreatedTypeField`
g, err := d2compiler.Compile("", strings.NewReader(script), nil) g, _, err := d2compiler.Compile("", strings.NewReader(script), nil)
assert.Nil(t, err) assert.Nil(t, err)
_, ok := g.Root.HasChild([]string{"UserCreatedTypeField"}) _, ok := g.Root.HasChild([]string{"UserCreatedTypeField"})

View file

@ -2,11 +2,15 @@ package d2ir
import ( import (
"io/fs" "io/fs"
"strconv"
"strings" "strings"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/util-go/go2"
) )
type compiler struct { type compiler struct {
@ -18,6 +22,8 @@ type compiler struct {
// importCache enables reuse of files imported multiple times. // importCache enables reuse of files imported multiple times.
importCache map[string]*Map importCache map[string]*Map
utf16 bool utf16 bool
globStack []bool
} }
type CompileOptions struct { type CompileOptions struct {
@ -52,14 +58,15 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) {
defer c.popImportStack() defer c.popImportStack()
c.compileMap(m, ast, ast) c.compileMap(m, ast, ast)
c.compileClasses(m) c.compileSubstitutions(m, nil)
c.overlayClasses(m)
if !c.err.Empty() { if !c.err.Empty() {
return nil, c.err return nil, c.err
} }
return m, nil return m, nil
} }
func (c *compiler) compileClasses(m *Map) { func (c *compiler) overlayClasses(m *Map) {
classes := m.GetField("classes") classes := m.GetField("classes")
if classes == nil || classes.Map() == nil { if classes == nil || classes.Map() == nil {
return return
@ -92,10 +99,242 @@ func (c *compiler) compileClasses(m *Map) {
l.Fields = append(l.Fields, base) l.Fields = append(l.Fields, base)
} }
c.compileClasses(l) c.overlayClasses(l)
} }
} }
func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
for _, f := range m.Fields {
if f.Name == "vars" && f.Map() != nil {
varsStack = append([]*Map{f.Map()}, varsStack...)
}
if f.Primary() != nil {
c.resolveSubstitutions(varsStack, f)
}
if arr, ok := f.Composite.(*Array); ok {
for _, val := range arr.Values {
if scalar, ok := val.(*Scalar); ok {
c.resolveSubstitutions(varsStack, scalar)
}
}
} else if f.Map() != nil {
// don't resolve substitutions in vars with the current scope of vars
if f.Name == "vars" {
c.compileSubstitutions(f.Map(), varsStack[1:])
c.validateConfigs(f.Map().GetField("d2-config"))
} else {
c.compileSubstitutions(f.Map(), varsStack)
}
}
}
for _, e := range m.Edges {
if e.Primary() != nil {
c.resolveSubstitutions(varsStack, e)
}
if e.Map() != nil {
c.compileSubstitutions(e.Map(), varsStack)
}
}
}
func (c *compiler) validateConfigs(configs *Field) {
if configs == nil || configs.Map() == nil {
return
}
if NodeBoardKind(ParentMap(ParentMap(configs))) == "" {
c.errorf(configs.LastRef().AST(), `"%s" can only appear at root vars`, configs.Name)
return
}
for _, f := range configs.Map().Fields {
var val string
if f.Primary() == nil {
if f.Name != "theme-colors" {
c.errorf(f.LastRef().AST(), `"%s" needs a value`, f.Name)
continue
}
} else {
val = f.Primary().Value.ScalarString()
}
switch f.Name {
case "sketch", "center":
_, err := strconv.ParseBool(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected a boolean for "%s", got "%s"`, f.Name, val)
continue
}
case "theme-colors":
if f.Map() == nil {
c.errorf(f.LastRef().AST(), `"%s" needs a map`, f.Name)
continue
}
case "theme-id", "dark-theme-id":
valInt, err := strconv.Atoi(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
continue
}
if d2themescatalog.Find(int64(valInt)) == (d2themes.Theme{}) {
c.errorf(f.LastRef().AST(), `%d is not a valid theme ID`, valInt)
continue
}
case "pad":
_, err := strconv.Atoi(val)
if err != nil {
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
continue
}
case "layout-engine":
default:
c.errorf(f.LastRef().AST(), `"%s" is not a valid config`, f.Name)
}
}
}
func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) {
var subbed bool
var resolvedField *Field
switch s := node.Primary().Value.(type) {
case *d2ast.UnquotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
if resolvedField != nil {
if resolvedField.Primary() != nil {
if _, ok := resolvedField.Primary().Value.(*d2ast.Null); ok {
resolvedField = nil
}
}
break
}
}
if resolvedField == nil {
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if box.Substitution.Spread {
if resolvedField.Composite == nil {
c.errorf(box.Substitution, "cannot spread non-composite")
continue
}
switch n := node.(type) {
case *Scalar: // Array value
resolvedArr, ok := resolvedField.Composite.(*Array)
if !ok {
c.errorf(box.Substitution, "cannot spread non-array into array")
continue
}
arr := n.parent.(*Array)
for i, s := range arr.Values {
if s == n {
arr.Values = append(append(arr.Values[:i], resolvedArr.Values...), arr.Values[i+1:]...)
break
}
}
case *Field:
if resolvedField.Map() != nil {
OverlayMap(ParentMap(n), resolvedField.Map())
}
// Remove the placeholder field
m := n.parent.(*Map)
for i, f2 := range m.Fields {
if n == f2 {
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
break
}
}
}
}
if resolvedField.Primary() == nil {
if resolvedField.Composite == nil {
c.errorf(node.LastRef().AST(), `cannot substitute variable without value: "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if len(s.Value) > 1 {
c.errorf(node.LastRef().AST(), `cannot substitute composite variable "%s" as part of a string`, strings.Join(box.Substitution.IDA(), "."))
return
}
switch n := node.(type) {
case *Field:
n.Primary_ = nil
case *Edge:
n.Primary_ = nil
}
} else {
if i == 0 && len(s.Value) == 1 {
node.Primary().Value = resolvedField.Primary().Value
} else {
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
subbed = true
}
}
if resolvedField.Composite != nil {
switch n := node.(type) {
case *Field:
n.Composite = resolvedField.Composite
case *Edge:
if resolvedField.Composite.Map() == nil {
c.errorf(node.LastRef().AST(), `cannot substitute array variable "%s" to an edge`, strings.Join(box.Substitution.IDA(), "."))
return
}
n.Map_ = resolvedField.Composite.Map()
}
}
}
}
if subbed {
s.Coalesce()
}
case *d2ast.DoubleQuotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
if resolvedField != nil {
break
}
}
if resolvedField == nil {
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
return
}
if resolvedField.Primary() == nil && resolvedField.Composite != nil {
c.errorf(node.LastRef().AST(), `cannot substitute map variable "%s" in quotes`, strings.Join(box.Substitution.IDA(), "."))
return
}
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
subbed = true
}
}
if subbed {
s.Coalesce()
}
}
}
func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
if vars == nil {
return nil
}
for i, p := range substitution.Path {
f := vars.GetField(p.Unbox().ScalarString())
if f == nil {
return nil
}
if i == len(substitution.Path)-1 {
return f
}
vars = f.Map()
}
return nil
}
func (c *compiler) overlay(base *Map, f *Field) { func (c *compiler) overlay(base *Map, f *Field) {
if f.Map() == nil || f.Primary() != nil { if f.Map() == nil || f.Primary() != nil {
c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f)) c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f))
@ -107,6 +346,20 @@ func (c *compiler) overlay(base *Map, f *Field) {
} }
func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) { func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
for _, n := range ast.Nodes {
switch {
case n.MapKey != nil:
ok := c.ampersandFilter(&RefContext{
Key: n.MapKey,
Scope: ast,
ScopeMap: dst,
ScopeAST: scopeAST,
})
if !ok {
return
}
}
}
for _, n := range ast.Nodes { for _, n := range ast.Nodes {
switch { switch {
case n.MapKey != nil: case n.MapKey != nil:
@ -116,6 +369,17 @@ func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
ScopeMap: dst, ScopeMap: dst,
ScopeAST: scopeAST, ScopeAST: scopeAST,
}) })
case n.Substitution != nil:
// placeholder field to be resolved at the end
f := &Field{
parent: dst,
Primary_: &Scalar{
Value: &d2ast.UnquotedString{
Value: []d2ast.InterpolationBox{{Substitution: n.Substitution}},
},
},
}
dst.Fields = append(dst.Fields, f)
case n.Import != nil: case n.Import != nil:
impn, ok := c._import(n.Import) impn, ok := c._import(n.Import)
if !ok { if !ok {
@ -135,8 +399,6 @@ func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
} }
} }
} }
case n.Substitution != nil:
panic("TODO")
} }
} }
} }
@ -150,16 +412,87 @@ func (c *compiler) compileKey(refctx *RefContext) {
} }
func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) { func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) {
if refctx.Key != nil && len(refctx.Key.Edges) == 0 && refctx.Key.Value.Null != nil { if refctx.Key.Ampersand {
dst.DeleteField(kp.IDA()...)
return return
} }
f, err := dst.EnsureField(kp, refctx)
fa, err := dst.EnsureField(kp, refctx, true)
if err != nil { if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error)) c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return return
} }
for _, f := range fa {
c._compileField(f, refctx)
}
}
func (c *compiler) ampersandFilter(refctx *RefContext) bool {
if !refctx.Key.Ampersand {
return true
}
if len(c.globStack) == 0 || !c.globStack[len(c.globStack)-1] {
c.errorf(refctx.Key, "glob filters cannot be used outside globs")
return false
}
if len(refctx.Key.Edges) > 0 {
return true
}
fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, false)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return false
}
if len(fa) == 0 {
return false
}
for _, f := range fa {
ok := c._ampersandFilter(f, refctx)
if !ok {
return false
}
}
return true
}
func (c *compiler) _ampersandFilter(f *Field, refctx *RefContext) bool {
if refctx.Key.Value.ScalarBox().Unbox() == nil {
c.errorf(refctx.Key, "glob filters cannot be composites")
return false
}
if a, ok := f.Composite.(*Array); ok {
for _, v := range a.Values {
if s, ok := v.(*Scalar); ok {
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == s.Value.ScalarString() {
return true
}
}
}
}
if f.Primary_ == nil {
return false
}
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() != f.Primary_.Value.ScalarString() {
return false
}
return true
}
func (c *compiler) _compileField(f *Field, refctx *RefContext) {
if len(refctx.Key.Edges) == 0 && refctx.Key.Value.Null != nil {
// For vars, if we delete the field, it may just resolve to an outer scope var of the same name
// Instead we keep it around, so that resolveSubstitutions can find it
if !IsVar(ParentMap(f)) {
ParentMap(f).DeleteField(f.Name)
return
}
}
if refctx.Key.Primary.Unbox() != nil { if refctx.Key.Primary.Unbox() != nil {
f.Primary_ = &Scalar{ f.Primary_ = &Scalar{
parent: f, parent: f,
@ -199,10 +532,12 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
// If new board type, use that as the new scope AST, otherwise, carry on // If new board type, use that as the new scope AST, otherwise, carry on
scopeAST = refctx.ScopeAST scopeAST = refctx.ScopeAST
} }
c.globStack = append(c.globStack, refctx.Key.HasQueryGlob())
c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST) c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST)
c.globStack = c.globStack[:len(c.globStack)-1]
switch NodeBoardKind(f) { switch NodeBoardKind(f) {
case BoardScenario, BoardStep: case BoardScenario, BoardStep:
c.compileClasses(f.Map()) c.overlayClasses(f.Map())
} }
} else if refctx.Key.Value.Import != nil { } else if refctx.Key.Value.Import != nil {
n, ok := c._import(refctx.Key.Value.Import) n, ok := c._import(refctx.Key.Value.Import)
@ -241,7 +576,7 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
c.updateLinks(f.Map()) c.updateLinks(f.Map())
switch NodeBoardKind(f) { switch NodeBoardKind(f) {
case BoardScenario, BoardStep: case BoardScenario, BoardStep:
c.compileClasses(f.Map()) c.overlayClasses(f.Map())
} }
} }
} else if refctx.Key.Value.ScalarBox().Unbox() != nil { } else if refctx.Key.Value.ScalarBox().Unbox() != nil {
@ -259,6 +594,21 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
func (c *compiler) updateLinks(m *Map) { func (c *compiler) updateLinks(m *Map) {
for _, f := range m.Fields { for _, f := range m.Fields {
if f.Name == "link" { if f.Name == "link" {
val := f.Primary().Value.ScalarString()
link, err := d2parser.ParseKey(val)
if err != nil {
continue
}
linkIDA := link.IDA()
if len(linkIDA) == 0 {
continue
}
// When updateLinks is called, all valid board links are already compiled and changed to the qualified path beginning with "root"
if linkIDA[0] != "root" {
continue
}
bida := BoardIDA(f) bida := BoardIDA(f)
aida := IDA(f) aida := IDA(f)
if len(bida) != len(aida) { if len(bida) != len(aida) {
@ -337,12 +687,17 @@ func (c *compiler) compileLink(refctx *RefContext) {
} }
func (c *compiler) compileEdges(refctx *RefContext) { func (c *compiler) compileEdges(refctx *RefContext) {
if refctx.Key.Key != nil { if refctx.Key.Key == nil {
f, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx) c._compileEdges(refctx)
if err != nil { return
c.err.Errors = append(c.err.Errors, err.(d2ast.Error)) }
return
} fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, true)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
return
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok { if _, ok := f.Composite.(*Array); ok {
c.errorf(refctx.Key.Key, "cannot index into array") c.errorf(refctx.Key.Key, "cannot index into array")
return return
@ -352,9 +707,13 @@ func (c *compiler) compileEdges(refctx *RefContext) {
parent: f, parent: f,
} }
} }
refctx.ScopeMap = f.Map() refctx2 := *refctx
refctx2.ScopeMap = f.Map()
c._compileEdges(&refctx2)
} }
}
func (c *compiler) _compileEdges(refctx *RefContext) {
eida := NewEdgeIDs(refctx.Key) eida := NewEdgeIDs(refctx.Key)
for i, eid := range eida { for i, eid := range eida {
if refctx.Key != nil && refctx.Key.Value.Null != nil { if refctx.Key != nil && refctx.Key.Value.Null != nil {
@ -365,66 +724,61 @@ func (c *compiler) compileEdges(refctx *RefContext) {
refctx = refctx.Copy() refctx = refctx.Copy()
refctx.Edge = refctx.Key.Edges[i] refctx.Edge = refctx.Key.Edges[i]
var e *Edge var ea []*Edge
if eid.Index != nil { if eid.Index != nil || eid.Glob {
ea := refctx.ScopeMap.GetEdges(eid) ea = refctx.ScopeMap.GetEdges(eid, refctx)
if len(ea) == 0 { if len(ea) == 0 {
c.errorf(refctx.Edge, "indexed edge does not exist") c.errorf(refctx.Edge, "indexed edge does not exist")
continue continue
} }
e = ea[0] for _, e := range ea {
e.References = append(e.References, &EdgeReference{ e.References = append(e.References, &EdgeReference{
Context: refctx, Context: refctx,
}) })
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Src, refctx) refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Src, refctx)
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Dst, refctx) refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Dst, refctx)
}
} else { } else {
_, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, refctx) var err error
if err != nil { ea, err = refctx.ScopeMap.CreateEdge(eid, refctx)
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue
}
_, err = refctx.ScopeMap.EnsureField(refctx.Edge.Dst, refctx)
if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue
}
e, err = refctx.ScopeMap.CreateEdge(eid, refctx)
if err != nil { if err != nil {
c.err.Errors = append(c.err.Errors, err.(d2ast.Error)) c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
continue continue
} }
} }
if refctx.Key.EdgeKey != nil { for _, e := range ea {
if e.Map_ == nil { if refctx.Key.EdgeKey != nil {
e.Map_ = &Map{
parent: e,
}
}
c.compileField(e.Map_, refctx.Key.EdgeKey, refctx)
} else {
if refctx.Key.Primary.Unbox() != nil {
e.Primary_ = &Scalar{
parent: e,
Value: refctx.Key.Primary.Unbox(),
}
}
if refctx.Key.Value.Array != nil {
c.errorf(refctx.Key.Value.Unbox(), "edges cannot be assigned arrays")
continue
} else if refctx.Key.Value.Map != nil {
if e.Map_ == nil { if e.Map_ == nil {
e.Map_ = &Map{ e.Map_ = &Map{
parent: e, parent: e,
} }
} }
c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST) c.compileField(e.Map_, refctx.Key.EdgeKey, refctx)
} else if refctx.Key.Value.ScalarBox().Unbox() != nil { } else {
e.Primary_ = &Scalar{ if refctx.Key.Primary.Unbox() != nil {
parent: e, e.Primary_ = &Scalar{
Value: refctx.Key.Value.ScalarBox().Unbox(), parent: e,
Value: refctx.Key.Primary.Unbox(),
}
}
if refctx.Key.Value.Array != nil {
c.errorf(refctx.Key.Value.Unbox(), "edges cannot be assigned arrays")
continue
} else if refctx.Key.Value.Map != nil {
if e.Map_ == nil {
e.Map_ = &Map{
parent: e,
}
}
c.globStack = append(c.globStack, refctx.Key.HasQueryGlob())
c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST)
c.globStack = c.globStack[:len(c.globStack)-1]
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
e.Primary_ = &Scalar{
parent: e,
Value: refctx.Key.Value.ScalarBox().Unbox(),
}
} }
} }
} }
@ -481,7 +835,12 @@ func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map)
irv = n irv = n
} }
case *d2ast.Substitution: case *d2ast.Substitution:
// panic("TODO") irv = &Scalar{
parent: dst,
Value: &d2ast.UnquotedString{
Value: []d2ast.InterpolationBox{{Substitution: an.Substitution}},
},
}
} }
dst.Values = append(dst.Values, irv) dst.Values = append(dst.Values, irv)

View file

@ -26,6 +26,8 @@ func TestCompile(t *testing.T) {
t.Run("scenarios", testCompileScenarios) t.Run("scenarios", testCompileScenarios)
t.Run("steps", testCompileSteps) t.Run("steps", testCompileSteps)
t.Run("imports", testCompileImports) t.Run("imports", testCompileImports)
t.Run("patterns", testCompilePatterns)
t.Run("filters", testCompileFilters)
} }
type testCase struct { type testCase struct {
@ -84,23 +86,31 @@ func assertQuery(t testing.TB, n d2ir.Node, nfields, nedges int, primary interfa
m := n.Map() m := n.Map()
p := n.Primary() p := n.Primary()
var na []d2ir.Node
if idStr != "" { if idStr != "" {
var err error var err error
n, err = m.Query(idStr) na, err = m.QueryAll(idStr)
assert.Success(t, err) assert.Success(t, err)
assert.NotEqual(t, n, nil) assert.NotEqual(t, n, nil)
} else {
na = append(na, n)
}
p = n.Primary() for _, n := range na {
m = n.Map() m = n.Map()
p = n.Primary()
assert.Equal(t, nfields, m.FieldCountRecursive())
assert.Equal(t, nedges, m.EdgeCountRecursive())
if !makeScalar(p).Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but got %s", primary, p)
}
} }
assert.Equal(t, nfields, m.FieldCountRecursive()) if len(na) == 0 {
assert.Equal(t, nedges, m.EdgeCountRecursive()) return nil
if !makeScalar(p).Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but got %s", primary, p)
} }
return n return na[0]
} }
func makeScalar(v interface{}) *d2ir.Scalar { func makeScalar(v interface{}) *d2ir.Scalar {

View file

@ -325,6 +325,7 @@ type EdgeID struct {
// If nil, then any EdgeID with equal src/dst/arrows matches. // If nil, then any EdgeID with equal src/dst/arrows matches.
Index *int `json:"index"` Index *int `json:"index"`
Glob bool `json:"glob"`
} }
func NewEdgeIDs(k *d2ast.Key) (eida []*EdgeID) { func NewEdgeIDs(k *d2ast.Key) (eida []*EdgeID) {
@ -337,6 +338,7 @@ func NewEdgeIDs(k *d2ast.Key) (eida []*EdgeID) {
} }
if k.EdgeIndex != nil { if k.EdgeIndex != nil {
eid.Index = k.EdgeIndex.Int eid.Index = k.EdgeIndex.Int
eid.Glob = k.EdgeIndex.Glob
} }
eida = append(eida, eid) eida = append(eida, eid)
} }
@ -585,6 +587,19 @@ func (m *Map) FieldCountRecursive() int {
return acc return acc
} }
func (m *Map) IsContainer() bool {
if m == nil {
return false
}
for _, f := range m.Fields {
_, isReserved := d2graph.ReservedKeywords[f.Name]
if !isReserved {
return true
}
}
return false
}
func (m *Map) EdgeCountRecursive() int { func (m *Map) EdgeCountRecursive() int {
if m == nil { if m == nil {
return 0 return 0
@ -651,7 +666,8 @@ func (m *Map) getField(ida []string) *Field {
return nil return nil
} }
func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) { // EnsureField is a bit of a misnomer. It's more of a Query/Ensure combination function at this point.
func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext, create bool) ([]*Field, error) {
i := 0 i := 0
for kp.Path[i].Unbox().ScalarString() == "_" { for kp.Path[i].Unbox().ScalarString() == "_" {
m = ParentMap(m) m = ParentMap(m)
@ -663,29 +679,73 @@ func (m *Map) EnsureField(kp *d2ast.KeyPath, refctx *RefContext) (*Field, error)
} }
i++ i++
} }
return m.ensureField(i, kp, refctx)
var fa []*Field
err := m.ensureField(i, kp, refctx, create, &fa)
return fa, err
} }
func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field, error) { func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext, create bool, fa *[]*Field) error {
us, ok := kp.Path[i].Unbox().(*d2ast.UnquotedString)
if ok && us.Pattern != nil {
fa2, ok := m.doubleGlob(us.Pattern)
if ok {
if i == len(kp.Path)-1 {
*fa = append(*fa, fa2...)
} else {
for _, f := range fa2 {
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err := f.Map().ensureField(i+1, kp, refctx, create, fa)
if err != nil {
return err
}
}
}
return nil
}
for _, f := range m.Fields {
if matchPattern(f.Name, us.Pattern) {
if i == len(kp.Path)-1 {
*fa = append(*fa, f)
} else {
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err := f.Map().ensureField(i+1, kp, refctx, create, fa)
if err != nil {
return err
}
}
}
}
return nil
}
head := kp.Path[i].Unbox().ScalarString() head := kp.Path[i].Unbox().ScalarString()
if _, ok := d2graph.ReservedKeywords[strings.ToLower(head)]; ok { if _, ok := d2graph.ReservedKeywords[strings.ToLower(head)]; ok {
head = strings.ToLower(head) head = strings.ToLower(head)
if _, ok := d2graph.CompositeReservedKeywords[head]; !ok && i < len(kp.Path)-1 { if _, ok := d2graph.CompositeReservedKeywords[head]; !ok && i < len(kp.Path)-1 {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), fmt.Sprintf(`"%s" must be the last part of the key`, head)) return d2parser.Errorf(kp.Path[i].Unbox(), fmt.Sprintf(`"%s" must be the last part of the key`, head))
} }
} }
if head == "_" { if head == "_" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`) return d2parser.Errorf(kp.Path[i].Unbox(), `parent "_" can only be used in the beginning of paths, e.g. "_.x"`)
} }
if head == "classes" && NodeBoardKind(m) == "" { if head == "classes" && NodeBoardKind(m) == "" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head) return d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
} }
if findBoardKeyword(head) != -1 && NodeBoardKind(m) == "" { if findBoardKeyword(head) != -1 && NodeBoardKind(m) == "" {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head) return d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a board root", head)
} }
for _, f := range m.Fields { for _, f := range m.Fields {
@ -703,19 +763,23 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
} }
if i+1 == len(kp.Path) { if i+1 == len(kp.Path) {
return f, nil *fa = append(*fa, f)
return nil
} }
if _, ok := f.Composite.(*Array); ok { if _, ok := f.Composite.(*Array); ok {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "cannot index into array") return d2parser.Errorf(kp.Path[i].Unbox(), "cannot index into array")
} }
if f.Map() == nil { if f.Map() == nil {
f.Composite = &Map{ f.Composite = &Map{
parent: f, parent: f,
} }
} }
return f.Map().ensureField(i+1, kp, refctx) return f.Map().ensureField(i+1, kp, refctx, create, fa)
} }
if !create {
return nil
}
f := &Field{ f := &Field{
parent: m, parent: m,
Name: head, Name: head,
@ -730,12 +794,13 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
} }
m.Fields = append(m.Fields, f) m.Fields = append(m.Fields, f)
if i+1 == len(kp.Path) { if i+1 == len(kp.Path) {
return f, nil *fa = append(*fa, f)
return nil
} }
f.Composite = &Map{ f.Composite = &Map{
parent: f, parent: f,
} }
return f.Map().ensureField(i+1, kp, refctx) return f.Map().ensureField(i+1, kp, refctx, create, fa)
} }
func (m *Map) DeleteEdge(eid *EdgeID) *Edge { func (m *Map) DeleteEdge(eid *EdgeID) *Edge {
@ -800,7 +865,13 @@ func (m *Map) DeleteField(ida ...string) *Field {
return nil return nil
} }
func (m *Map) GetEdges(eid *EdgeID) []*Edge { func (m *Map) GetEdges(eid *EdgeID, refctx *RefContext) []*Edge {
if refctx != nil {
var ea []*Edge
m.getEdges(eid, refctx, &ea)
return ea
}
eid, m, common, err := eid.resolve(m) eid, m, common, err := eid.resolve(m)
if err != nil { if err != nil {
return nil return nil
@ -811,7 +882,7 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge {
return nil return nil
} }
if f.Map() != nil { if f.Map() != nil {
return f.Map().GetEdges(eid) return f.Map().GetEdges(eid, nil)
} }
return nil return nil
} }
@ -825,65 +896,197 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge {
return ea return ea
} }
func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) { func (m *Map) getEdges(eid *EdgeID, refctx *RefContext, ea *[]*Edge) error {
eid, m, common, err := eid.resolve(m)
if err != nil {
return err
}
if len(common) > 0 {
commonKP := d2ast.MakeKeyPath(common)
lastMatch := 0
for i, el := range commonKP.Path {
for j := lastMatch; j < len(refctx.Edge.Src.Path); j++ {
realEl := refctx.Edge.Src.Path[j]
if el.ScalarString() == realEl.ScalarString() {
commonKP.Path[i] = realEl
lastMatch += j + 1
}
}
}
fa, err := m.EnsureField(commonKP, nil, false)
if err != nil {
return nil
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok {
return d2parser.Errorf(refctx.Edge.Src, "cannot index into array")
}
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err = f.Map().getEdges(eid, refctx, ea)
if err != nil {
return err
}
}
return nil
}
srcFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, nil, false)
if err != nil {
return err
}
dstFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Dst, nil, false)
if err != nil {
return err
}
for _, src := range srcFA {
for _, dst := range dstFA {
eid2 := eid.Copy()
eid2.SrcPath = RelIDA(m, src)
eid2.DstPath = RelIDA(m, dst)
ea2 := m.GetEdges(eid2, nil)
*ea = append(*ea, ea2...)
}
}
return nil
}
func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) ([]*Edge, error) {
var ea []*Edge
return ea, m.createEdge(eid, refctx, &ea)
}
func (m *Map) createEdge(eid *EdgeID, refctx *RefContext, ea *[]*Edge) error {
if ParentEdge(m) != nil { if ParentEdge(m) != nil {
return nil, d2parser.Errorf(refctx.Edge, "cannot create edge inside edge") return d2parser.Errorf(refctx.Edge, "cannot create edge inside edge")
} }
eid, m, common, err := eid.resolve(m) eid, m, common, err := eid.resolve(m)
if err != nil { if err != nil {
return nil, d2parser.Errorf(refctx.Edge, err.Error()) return d2parser.Errorf(refctx.Edge, err.Error())
} }
if len(common) > 0 { if len(common) > 0 {
f, err := m.EnsureField(d2ast.MakeKeyPath(common), nil) commonKP := d2ast.MakeKeyPath(common)
if err != nil { lastMatch := 0
return nil, err for i, el := range commonKP.Path {
} for j := lastMatch; j < len(refctx.Edge.Src.Path); j++ {
if _, ok := f.Composite.(*Array); ok { realEl := refctx.Edge.Src.Path[j]
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot index into array") if el.ScalarString() == realEl.ScalarString() {
} commonKP.Path[i] = realEl
if f.Map() == nil { lastMatch += j + 1
f.Composite = &Map{ }
parent: f,
} }
} }
return f.Map().CreateEdge(eid, refctx) fa, err := m.EnsureField(commonKP, nil, true)
if err != nil {
return err
}
for _, f := range fa {
if _, ok := f.Composite.(*Array); ok {
return d2parser.Errorf(refctx.Edge.Src, "cannot index into array")
}
if f.Map() == nil {
f.Composite = &Map{
parent: f,
}
}
err = f.Map().createEdge(eid, refctx, ea)
if err != nil {
return err
}
}
return nil
} }
ij := findProhibitedEdgeKeyword(eid.SrcPath...) ij := findProhibitedEdgeKeyword(eid.SrcPath...)
if ij != -1 { if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "reserved keywords are prohibited in edges") return d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
} }
ij = findBoardKeyword(eid.SrcPath...) ij = findBoardKeyword(eid.SrcPath...)
if ij == len(eid.SrcPath)-1 { if ij == len(eid.SrcPath)-1 {
return nil, d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense") return d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
}
src := m.GetField(eid.SrcPath...)
if NodeBoardKind(src) != "" {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot create edges between boards")
} }
ij = findProhibitedEdgeKeyword(eid.DstPath...) ij = findProhibitedEdgeKeyword(eid.DstPath...)
if ij != -1 { if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "reserved keywords are prohibited in edges") return d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "reserved keywords are prohibited in edges")
} }
ij = findBoardKeyword(eid.DstPath...) ij = findBoardKeyword(eid.DstPath...)
if ij == len(eid.DstPath)-1 { if ij == len(eid.DstPath)-1 {
return nil, d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense") return d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "edge with board keyword alone doesn't make sense")
}
srcFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Src, refctx, true)
if err != nil {
return err
}
dstFA, err := refctx.ScopeMap.EnsureField(refctx.Edge.Dst, refctx, true)
if err != nil {
return err
}
for _, src := range srcFA {
for _, dst := range dstFA {
if src == dst && (refctx.Edge.Src.HasGlob() || refctx.Edge.Dst.HasGlob()) {
// Globs do not make self edges.
continue
}
if refctx.Edge.Src.HasDoubleGlob() {
// If src has a double glob we only select leafs, those without children.
if src.Map().IsContainer() {
continue
}
if ParentBoard(src) != ParentBoard(dst) {
continue
}
}
if refctx.Edge.Dst.HasDoubleGlob() {
// If dst has a double glob we only select leafs, those without children.
if dst.Map().IsContainer() {
continue
}
if ParentBoard(src) != ParentBoard(dst) {
continue
}
}
eid2 := eid.Copy()
eid2.SrcPath = RelIDA(m, src)
eid2.DstPath = RelIDA(m, dst)
e, err := m.createEdge2(eid2, refctx, src, dst)
if err != nil {
return err
}
*ea = append(*ea, e)
}
}
return nil
}
func (m *Map) createEdge2(eid *EdgeID, refctx *RefContext, src, dst *Field) (*Edge, error) {
if NodeBoardKind(src) != "" {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot create edges between boards")
} }
dst := m.GetField(eid.DstPath...)
if NodeBoardKind(dst) != "" { if NodeBoardKind(dst) != "" {
return nil, d2parser.Errorf(refctx.Edge.Dst, "cannot create edges between boards") return nil, d2parser.Errorf(refctx.Edge.Dst, "cannot create edges between boards")
} }
if ParentBoard(src) != ParentBoard(dst) { if ParentBoard(src) != ParentBoard(dst) {
return nil, d2parser.Errorf(refctx.Edge, "cannot create edges between boards") return nil, d2parser.Errorf(refctx.Edge, "cannot create edges between boards")
} }
eid.Index = nil eid.Index = nil
ea := m.GetEdges(eid) eid.Glob = true
ea := m.GetEdges(eid, nil)
index := len(ea) index := len(ea)
eid.Index = &index eid.Index = &index
eid.Glob = false
e := &Edge{ e := &Edge{
parent: m, parent: m,
ID: eid, ID: eid,
@ -1026,6 +1229,21 @@ func ParentField(n Node) *Field {
} }
} }
func IsVar(n Node) bool {
for {
if n == nil {
return false
}
if NodeBoardKind(n) != "" {
return false
}
if f, ok := n.(*Field); ok && f.Name == "vars" {
return true
}
n = n.Parent()
}
}
func ParentBoard(n Node) Node { func ParentBoard(n Node) Node {
for { for {
n = n.Parent() n = n.Parent()
@ -1144,6 +1362,26 @@ func IDA(n Node) (ida []string) {
} }
} }
// RelIDA returns the path to n relative to p.
func RelIDA(p, n Node) (ida []string) {
for {
f, ok := n.(*Field)
if ok {
ida = append(ida, f.Name)
if f.Root() {
reverseIDA(ida)
return ida
}
}
f = ParentField(n)
if f == nil || f.Root() || f == p || f.Composite == p {
reverseIDA(ida)
return ida
}
n = f
}
}
func reverseIDA(ida []string) { func reverseIDA(ida []string) {
for i := 0; i < len(ida)/2; i++ { for i := 0; i < len(ida)/2; i++ {
tmp := ida[i] tmp := ida[i]

158
d2ir/filter_test.go Normal file
View file

@ -0,0 +1,158 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/util-go/assert"
)
func testCompileFilters(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "base",
run: func(t testing.TB) {
m, err := compile(t, `jacob: {
shape: circle
}
jeremy: {
shape: rectangle
}
*: {
&shape: rectangle
label: I'm a rectangle
}`)
assert.Success(t, err)
assertQuery(t, m, 1, 0, nil, "jacob")
assertQuery(t, m, 2, 0, nil, "jeremy")
assertQuery(t, m, 0, 0, "I'm a rectangle", "jeremy.label")
},
},
{
name: "order",
run: func(t testing.TB) {
m, err := compile(t, `jacob: {
shape: circle
}
jeremy: {
shape: rectangle
}
*: {
label: I'm a rectangle
&shape: rectangle
}`)
assert.Success(t, err)
assertQuery(t, m, 5, 0, nil, "")
assertQuery(t, m, 1, 0, nil, "jacob")
assertQuery(t, m, 2, 0, nil, "jeremy")
assertQuery(t, m, 0, 0, "I'm a rectangle", "jeremy.label")
},
},
{
name: "array",
run: func(t testing.TB) {
m, err := compile(t, `the-little-cannon: {
class: [server; deployed]
}
dino: {
class: [internal; deployed]
}
catapult: {
class: [jacob; server]
}
*: {
&class: server
style.multiple: true
}
`)
assert.Success(t, err)
assertQuery(t, m, 10, 0, nil, "")
assertQuery(t, m, 3, 0, nil, "the-little-cannon")
assertQuery(t, m, 1, 0, nil, "dino")
assertQuery(t, m, 3, 0, nil, "catapult")
},
},
{
name: "edge",
run: func(t testing.TB) {
m, err := compile(t, `x -> y: {
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
x -> y
(x -> *)[*]: {
&source-arrowhead.shape: diamond
&target-arrowhead.shape: diamond
label: diamond shape arrowheads
}
`)
assert.Success(t, err)
assertQuery(t, m, 7, 2, nil, "")
assertQuery(t, m, 5, 0, nil, "(x -> y)[0]")
assertQuery(t, m, 0, 0, "diamond shape arrowheads", "(x -> y)[0].label")
assertQuery(t, m, 0, 0, nil, "(x -> y)[1]")
},
},
}
runa(t, tca)
t.Run("errors", func(t *testing.T) {
tca := []testCase{
{
name: "bad-syntax",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
*.&style: {
fill: red
multiple: true
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/bad-syntax.d2:6:3: unexpected text after map key
TestCompile/filters/errors/bad-syntax.d2:9:1: unexpected map termination character } in file map`)
},
},
{
name: "no-glob",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
jasmine.style: {
&fill: red
multiple: false
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/no-glob.d2:7:3: glob filters cannot be used outside globs`)
},
},
{
name: "composite",
run: func(t testing.TB) {
_, err := compile(t, `jacob.style: {
fill: red
multiple: true
}
*: {
&style: {
fill: red
multiple: true
}
}
`)
assert.ErrorString(t, err, `TestCompile/filters/errors/composite.d2:6:2: glob filters cannot be composites`)
},
},
}
runa(t, tca)
})
}

View file

@ -138,6 +138,39 @@ label: meow`,
assertQuery(t, m, 0, 0, nil, "q.jon") assertQuery(t, m, 0, 0, nil, "q.jon")
}, },
}, },
{
name: "vars/1",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "vars: { ...@x }; q: ${meow}",
"x.d2": "meow: var replaced",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, "var replaced", "q")
},
},
{
name: "vars/2",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "vars: { x: 1 }; ...@a",
"a.d2": "vars: { x: 2 }; hi: ${x}",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, 2, "hi")
},
},
{
name: "vars/3",
run: func(t testing.TB) {
m, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@a; vars: { x: 1 }; hi: ${x}",
"a.d2": "vars: { x: 2 }",
})
assert.Success(t, err)
assertQuery(t, m, 0, 0, 1, "hi")
},
},
} }
runa(t, tca) runa(t, tca)

View file

@ -11,7 +11,7 @@ func OverlayMap(base, overlay *Map) {
} }
for _, oe := range overlay.Edges { for _, oe := range overlay.Edges {
bea := base.GetEdges(oe.ID) bea := base.GetEdges(oe.ID, nil)
if len(bea) == 0 { if len(bea) == 0 {
base.Edges = append(base.Edges, oe.Copy(base).(*Edge)) base.Edges = append(base.Edges, oe.Copy(base).(*Edge))
continue continue

59
d2ir/pattern.go Normal file
View file

@ -0,0 +1,59 @@
package d2ir
import (
"strings"
"oss.terrastruct.com/d2/d2graph"
)
func (m *Map) doubleGlob(pattern []string) ([]*Field, bool) {
if !(len(pattern) == 3 && pattern[0] == "*" && pattern[1] == "" && pattern[2] == "*") {
return nil, false
}
var fa []*Field
m._doubleGlob(&fa)
return fa, true
}
func (m *Map) _doubleGlob(fa *[]*Field) {
for _, f := range m.Fields {
if _, ok := d2graph.ReservedKeywords[f.Name]; ok {
if _, ok := d2graph.BoardKeywords[f.Name]; !ok {
continue
}
}
*fa = append(*fa, f)
if f.Map() != nil {
f.Map()._doubleGlob(fa)
}
}
}
func matchPattern(s string, pattern []string) bool {
if len(pattern) == 0 {
return true
}
if _, ok := d2graph.ReservedKeywords[s]; ok {
return false
}
for i := 0; i < len(pattern); i++ {
if pattern[i] == "*" {
// * so match next.
if i != len(pattern)-1 {
j := strings.Index(strings.ToLower(s), strings.ToLower(pattern[i+1]))
if j == -1 {
return false
}
s = s[j+len(pattern[i+1]):]
i++
}
} else {
if !strings.HasPrefix(strings.ToLower(s), strings.ToLower(pattern[i])) {
return false
}
s = s[len(pattern[i]):]
}
}
return true
}

334
d2ir/pattern_test.go Normal file
View file

@ -0,0 +1,334 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/util-go/assert"
)
func testCompilePatterns(t *testing.T) {
t.Parallel()
tca := []testCase{
{
name: "escaped",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
a\*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 3, 0, nil, "")
assertQuery(t, m, 0, 0, "meow", "animal")
assertQuery(t, m, 0, 0, "yes", "action")
assertQuery(t, m, 0, 0, "globbed", `a\*`)
},
},
{
name: "prefix",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
a*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "action")
},
},
{
name: "case/1",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
action: yes
A*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "action")
},
},
{
name: "case/2",
run: func(t testing.TB) {
m, err := compile(t, `diddy kong
Donkey Kong
*kong: yes`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "yes", "diddy kong")
assertQuery(t, m, 0, 0, "yes", "Donkey Kong")
},
},
{
name: "suffix",
run: func(t testing.TB) {
m, err := compile(t, `animal: meow
jingle: loud
*l: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animal")
assertQuery(t, m, 0, 0, "globbed", "jingle")
},
},
{
name: "prefix-suffix",
run: func(t testing.TB) {
m, err := compile(t, `tinker: meow
thinker: yes
t*r: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinker")
assertQuery(t, m, 0, 0, "globbed", "thinker")
},
},
{
name: "prefix-suffix/2",
run: func(t testing.TB) {
m, err := compile(t, `tinker: meow
thinker: yes
t*ink*r: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinker")
assertQuery(t, m, 0, 0, "globbed", "thinker")
},
},
{
name: "prefix-suffix/3",
run: func(t testing.TB) {
m, err := compile(t, `tinkertinker: meow
thinkerthinker: yes
t*ink*r*t*inke*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 2, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "tinkertinker")
assertQuery(t, m, 0, 0, "globbed", "thinkerthinker")
},
},
{
name: "nested/prefix-suffix/3",
run: func(t testing.TB) {
m, err := compile(t, `animate.constant.tinkertinker: meow
astronaut.constant.thinkerthinker: yes
a*n*t*.constant.t*ink*r*t*inke*: globbed`)
assert.Success(t, err)
assertQuery(t, m, 6, 0, nil, "")
assertQuery(t, m, 0, 0, "globbed", "animate.constant.tinkertinker")
assertQuery(t, m, 0, 0, "globbed", "astronaut.constant.thinkerthinker")
},
},
{
name: "edge/1",
run: func(t testing.TB) {
m, err := compile(t, `animate
animal
an* -> an*`)
assert.Success(t, err)
assertQuery(t, m, 2, 2, nil, "")
assertQuery(t, m, 0, 0, nil, "(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "(animal -> animal)[0]")
},
},
{
name: "edge/2",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
sh*.(an* -> an*)`)
assert.Success(t, err)
assertQuery(t, m, 3, 2, nil, "")
assertQuery(t, m, 2, 2, nil, "shared")
assertQuery(t, m, 0, 0, nil, "shared.(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "shared.(animal -> animate)[0]")
},
},
{
name: "edge/3",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
sh*.an* -> sh*.an*`)
assert.Success(t, err)
assertQuery(t, m, 3, 2, nil, "")
assertQuery(t, m, 2, 2, nil, "shared")
assertQuery(t, m, 0, 0, nil, "shared.(animate -> animal)[0]")
assertQuery(t, m, 0, 0, nil, "shared.(animal -> animal)[0]")
},
},
{
name: "edge-glob-index",
run: func(t testing.TB) {
m, err := compile(t, `a -> b
a -> b
a -> b
(a -> b)[*].style.fill: red
`)
assert.Success(t, err)
assertQuery(t, m, 8, 3, nil, "")
assertQuery(t, m, 0, 0, "red", "(a -> b)[0].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[1].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[2].style.fill")
},
},
{
name: "glob-edge-glob-index",
run: func(t testing.TB) {
m, err := compile(t, `a -> b
a -> b
a -> b
c -> b
(* -> b)[*].style.fill: red
`)
assert.Success(t, err)
assertQuery(t, m, 11, 4, nil, "")
assertQuery(t, m, 0, 0, "red", "(a -> b)[0].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[1].style.fill")
assertQuery(t, m, 0, 0, "red", "(a -> b)[2].style.fill")
assertQuery(t, m, 0, 0, "red", "(c -> b)[0].style.fill")
},
},
{
name: "edge-nexus",
run: func(t testing.TB) {
m, err := compile(t, `a
b
c
d
* -> nexus
`)
assert.Success(t, err)
assertQuery(t, m, 5, 4, nil, "")
assertQuery(t, m, 0, 0, nil, "(a -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(b -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(c -> nexus)[0]")
assertQuery(t, m, 0, 0, nil, "(d -> nexus)[0]")
},
},
{
name: "double-glob/1",
run: func(t testing.TB) {
m, err := compile(t, `shared.animate
shared.animal
**.style.fill: red`)
assert.Success(t, err)
assertQuery(t, m, 9, 0, nil, "")
assertQuery(t, m, 8, 0, nil, "shared")
assertQuery(t, m, 1, 0, nil, "shared.style")
assertQuery(t, m, 2, 0, nil, "shared.animate")
assertQuery(t, m, 1, 0, nil, "shared.animate.style")
assertQuery(t, m, 2, 0, nil, "shared.animal")
assertQuery(t, m, 1, 0, nil, "shared.animal.style")
},
},
{
name: "double-glob/edge-no-container",
run: func(t testing.TB) {
m, err := compile(t, `zone A: {
machine A
machine B: {
submachine A
submachine B
}
}
zone A.** -> load balancer
`)
assert.Success(t, err)
assertQuery(t, m, 6, 3, nil, "")
},
},
{
name: "reserved",
run: func(t testing.TB) {
m, err := compile(t, `vars: {
d2-config: {
layout-engine: elk
}
}
Spiderman 1
Spiderman 2
Spiderman 3
* -> *: arrow`)
assert.Success(t, err)
assertQuery(t, m, 6, 6, nil, "")
assertQuery(t, m, 0, 0, "arrow", "(* -> *)[*]")
},
},
{
name: "scenarios",
run: func(t testing.TB) {
m, err := compile(t, `
scenarios: {
meow: {
e
f
g
h
}
}
a
b
c
d
**: something
** -> **
`)
assert.Success(t, err)
assertQuery(t, m, 10, 24, nil, "")
assertQuery(t, m, 0, 0, "something", "**")
assertQuery(t, m, 0, 0, nil, "(* -> *)[*]")
},
},
{
name: "double-glob/edge/1",
run: func(t testing.TB) {
m, err := compile(t, `fast: {
a
far
}
task: {
a
}
task.** -> fast
`)
assert.Success(t, err)
assertQuery(t, m, 5, 1, nil, "")
},
},
{
name: "double-glob/edge/2",
run: func(t testing.TB) {
m, err := compile(t, `a
**.b -> c
`)
assert.Success(t, err)
assertQuery(t, m, 3, 1, nil, "")
},
},
}
runa(t, tca)
t.Run("errors", func(t *testing.T) {
tca := []testCase{
{
name: "glob-edge-glob-index",
run: func(t testing.TB) {
_, err := compile(t, `(* -> b)[*].style.fill: red
`)
assert.ErrorString(t, err, `TestCompile/patterns/errors/glob-edge-glob-index.d2:1:2: indexed edge does not exist`)
},
},
}
runa(t, tca)
})
}

View file

@ -29,8 +29,14 @@ func (m *Map) QueryAll(idStr string) (na []Node, _ error) {
} }
eida := NewEdgeIDs(k) eida := NewEdgeIDs(k)
for _, eid := range eida {
ea := m.GetEdges(eid) for i, eid := range eida {
refctx := &RefContext{
Key: k,
ScopeMap: m,
Edge: k.Edges[i],
}
ea := m.GetEdges(eid, refctx)
for _, e := range ea { for _, e := range ea {
if k.EdgeKey == nil { if k.EdgeKey == nil {
na = append(na, e) na = append(na, e)
@ -56,7 +62,7 @@ func (m *Map) Query(idStr string) (Node, error) {
return nil, nil return nil, nil
} }
if len(na) > 1 { if len(na) > 1 {
return nil, fmt.Errorf("expected only one query result but got: %#v", err) return nil, fmt.Errorf("expected only one query result but got: %#v", na)
} }
return na[0], nil return na[0], nil
} }

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"regexp"
"strconv"
"strings" "strings"
"github.com/dop251/goja" "github.com/dop251/goja"
@ -180,6 +182,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
elkGraph.LayoutOptions.Direction = "DOWN" elkGraph.LayoutOptions.Direction = "DOWN"
} }
// set label and icon positions for ELK
for _, obj := range g.Objects {
positionLabelsIcons(obj)
}
elkNodes := make(map[*d2graph.Object]*ELKNode) elkNodes := make(map[*d2graph.Object]*ELKNode)
elkEdges := make(map[*d2graph.Edge]*ELKEdge) elkEdges := make(map[*d2graph.Edge]*ELKEdge)
@ -214,18 +221,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
} }
} }
height := obj.Height width, height := adjustDimensions(obj)
width := obj.Width
if obj.HasLabel() {
if obj.HasOutsideBottomLabel() || obj.Icon != nil {
height += float64(obj.LabelDimensions.Height) + label.PADDING
}
width = go2.Max(width, float64(obj.LabelDimensions.Width))
}
// reserve extra space for 3d/multiple by providing elk the larger dimensions
dx, dy := obj.GetModifierElementAdjustments()
width += dx
height += dy
n := &ELKNode{ n := &ELKNode{
ID: obj.AbsID(), ID: obj.AbsID(),
@ -262,41 +258,18 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
case "RIGHT", "LEFT": case "RIGHT", "LEFT":
n.LayoutOptions.NodeSizeMinimum = fmt.Sprintf("(%d, %d)", int(math.Ceil(width)), int(math.Ceil(height))) n.LayoutOptions.NodeSizeMinimum = fmt.Sprintf("(%d, %d)", int(math.Ceil(width)), int(math.Ceil(height)))
} }
if n.LayoutOptions.Padding == DefaultOpts.Padding {
labelHeight := 0
if obj.HasLabel() {
labelHeight = obj.LabelDimensions.Height + label.PADDING
}
n.Height += 100 + float64(labelHeight)
n.Width += 100
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(n.Width), float64(n.Height))
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
paddingTop := n.Height - s.GetInnerBox().Height
n.Height -= (100 + float64(labelHeight))
n.Width -= 100
iconHeight := 0
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
iconHeight = d2target.GetIconSize(s.GetInnerBox(), string(label.InsideTopLeft)) + label.PADDING*2
}
paddingTop += float64(go2.Max(labelHeight, iconHeight))
n.LayoutOptions.Padding = fmt.Sprintf("[top=%d,left=50,bottom=50,right=50]",
// Default padding
go2.Max(int(math.Ceil(paddingTop)), 50),
)
}
} else { } else {
n.LayoutOptions = &elkOpts{ n.LayoutOptions = &elkOpts{
SelfLoopDistribution: "EQUALLY", SelfLoopDistribution: "EQUALLY",
} }
} }
if obj.IsContainer() {
padding := parsePadding(opts.Padding)
padding = adjustPadding(obj, width, height, padding)
n.LayoutOptions.Padding = padding.String()
}
if obj.HasLabel() { if obj.HasLabel() {
n.Labels = append(n.Labels, &ELKLabel{ n.Labels = append(n.Labels, &ELKLabel{
Text: obj.Label.Value, Text: obj.Label.Value,
@ -313,6 +286,41 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
elkNodes[obj] = n elkNodes[obj] = n
}) })
// adjust parent padding for children with outside positioned icons
for _, obj := range g.Objects {
if !obj.IsContainer() {
continue
}
var hasTop, hasBottom bool
for _, child := range obj.ChildrenArray {
if child.Shape.Value == d2target.ShapeImage || child.IconPosition == nil {
continue
}
switch label.Position(*child.IconPosition) {
case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight:
hasTop = true
case label.OutsideBottomLeft, label.OutsideBottomCenter, label.OutsideBottomRight:
hasBottom = true
}
if hasTop && hasBottom {
break
}
}
if hasTop || hasBottom {
padding := parsePadding(elkNodes[obj].LayoutOptions.Padding)
if hasTop {
padding.top = go2.Max(padding.top, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
if hasBottom {
padding.bottom = go2.Max(padding.bottom, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
elkNodes[obj].LayoutOptions.Padding = padding.String()
}
}
for _, edge := range g.Edges { for _, edge := range g.Edges {
e := &ELKEdge{ e := &ELKEdge{
ID: edge.AbsID(), ID: edge.AbsID(),
@ -407,29 +415,6 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
obj.Width = math.Ceil(n.Width) obj.Width = math.Ceil(n.Width)
obj.Height = math.Ceil(n.Height) obj.Height = math.Ceil(n.Height)
if obj.Icon != nil && obj.IconPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.InsideTopLeft))
if obj.LabelPosition == nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopRight))
}
} else {
obj.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
if obj.HasLabel() && obj.LabelPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
} else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
byID[obj.AbsID()] = obj byID[obj.AbsID()] = obj
}) })
@ -463,18 +448,8 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
edge.Route = points edge.Route = points
} }
// remove the extra width/height we added for 3d/multiple after all objects/connections are placed
// and shift the shapes down accordingly
for _, obj := range g.Objects { for _, obj := range g.Objects {
dx, dy := obj.GetModifierElementAdjustments() cleanupAdjustment(obj)
if dx != 0 || dy != 0 {
obj.TopLeft.Y += dy
obj.ShiftDescendants(0, dy)
if !obj.IsContainer() {
obj.Width -= dx
obj.Height -= dy
}
}
} }
for _, edge := range g.Edges { for _, edge := range g.Edges {
@ -808,3 +783,286 @@ func childrenMaxSelfLoop(parent *d2graph.Object, isWidth bool) int {
return max return max
} }
type shapePadding struct {
top, left, bottom, right int
}
// parse out values from elk padding string. e.g. "[top=50,left=50,bottom=50,right=50]"
func parsePadding(in string) shapePadding {
reTop := regexp.MustCompile(`top=(\d+)`)
reLeft := regexp.MustCompile(`left=(\d+)`)
reBottom := regexp.MustCompile(`bottom=(\d+)`)
reRight := regexp.MustCompile(`right=(\d+)`)
padding := shapePadding{}
submatches := reTop.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.top = int(i)
}
}
submatches = reLeft.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.left = int(i)
}
}
submatches = reBottom.FindStringSubmatch(in)
if len(submatches) == 2 {
i, err := strconv.ParseInt(submatches[1], 10, 64)
if err == nil {
padding.bottom = int(i)
}
}
submatches = reRight.FindStringSubmatch(in)
i, err := strconv.ParseInt(submatches[1], 10, 64)
if len(submatches) == 2 {
if err == nil {
padding.right = int(i)
}
}
return padding
}
func (padding shapePadding) String() string {
return fmt.Sprintf("[top=%d,left=%d,bottom=%d,right=%d]", padding.top, padding.left, padding.bottom, padding.right)
}
func adjustPadding(obj *d2graph.Object, width, height float64, padding shapePadding) shapePadding {
if !obj.IsContainer() {
return padding
}
// compute extra space padding for label/icon
var extraTop, extraBottom, extraLeft, extraRight int
if obj.HasLabel() && obj.LabelPosition != nil {
labelHeight := obj.LabelDimensions.Height + 2*label.PADDING
labelWidth := obj.LabelDimensions.Width + 2*label.PADDING
switch label.Position(*obj.LabelPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
// Note: for corners we only add height
extraTop = labelHeight
case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
extraBottom = labelHeight
case label.InsideMiddleLeft:
extraLeft = labelWidth
case label.InsideMiddleRight:
extraRight = labelWidth
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage && obj.IconPosition != nil {
iconSize := d2target.MAX_ICON_SIZE + 2*label.PADDING
switch label.Position(*obj.IconPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
extraTop = go2.Max(extraTop, iconSize)
case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
extraBottom = go2.Max(extraBottom, iconSize)
case label.InsideMiddleLeft:
extraLeft = go2.Max(extraLeft, iconSize)
case label.InsideMiddleRight:
extraRight = go2.Max(extraRight, iconSize)
}
}
maxChildWidth, maxChildHeight := math.Inf(-1), math.Inf(-1)
for _, c := range obj.ChildrenArray {
if c.Width > maxChildWidth {
maxChildWidth = c.Width
}
if c.Height > maxChildHeight {
maxChildHeight = c.Height
}
}
// We don't know exactly what the shape dimensions will be after layout, but for more accurate innerBox dimensions,
// we add the maxChildWidth and maxChildHeight with computed additions for the innerBox calculation
width += maxChildWidth + float64(extraLeft+extraRight)
height += maxChildHeight + float64(extraTop+extraBottom)
contentBox := geo.NewBox(geo.NewPoint(0, 0), width, height)
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value]
s := shape.NewShape(shapeType, contentBox)
innerBox := s.GetInnerBox()
// If the shape inner box + label/icon height becomes greater than the default padding, we want to use that
//
// ┌OUTER───────────────────────────┬────────────────────────────────────────────┐
// │ │ │
// │ ┌INNER──────── ┬ ─────────────│───────────────────────────────────────┐ │
// │ │ │Label Padding │ │ │
// │ │ ┌LABEL─ ┴ ─────────────│───────┐┬ ┌ICON── ┬ ────┐ │ │
// │ │ │ │ ││ │ │ │ │ │
// │ │ │ │ ││Label Height │ Icon│ │ │ │
// │ │ │ │ ││ │ Height│ │ │ │
// │ │ └──────────────────────│───────┘┴ │ │ │ │ │
// │ │ │ └────── ┴ ────┘ │ │
// │ │ │ │ │
// │ │ ┴Default ELK Padding │ │
// │ │ ┌CHILD────────────────────────────────────────────────────────┐ │ │
// │ │ │ │ │ │
// │ │ │ │ │ │
// │ │ │ │ │ │
// │ │ └─────────────────────────────────────────────────────────────┘ │ │
// │ │ │ │
// │ └─────────────────────────────────────────────────────────────────────┘ │
// │ │
// └─────────────────────────────────────────────────────────────────────────────┘
// estimated shape innerBox padding
innerTop := int(math.Ceil(innerBox.TopLeft.Y))
innerBottom := int(math.Ceil(height - (innerBox.TopLeft.Y + innerBox.Height)))
innerLeft := int(math.Ceil(innerBox.TopLeft.X))
innerRight := int(math.Ceil(width - (innerBox.TopLeft.X + innerBox.Width)))
padding.top = go2.Max(padding.top, innerTop+extraTop)
padding.bottom = go2.Max(padding.bottom, innerBottom+extraBottom)
padding.left = go2.Max(padding.left, innerLeft+extraLeft)
padding.right = go2.Max(padding.right, innerRight+extraRight)
return padding
}
func adjustDimensions(obj *d2graph.Object) (width, height float64) {
width = obj.Width
height = obj.Height
// reserve spacing for labels
if obj.HasLabel() {
var position label.Position
if obj.LabelPosition != nil {
position = label.Position(*obj.LabelPosition)
} else if len(obj.ChildrenArray) == 0 && obj.HasOutsideBottomLabel() {
position = label.OutsideBottomCenter
}
if position.IsShapePosition() {
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
width += float64(obj.LabelDimensions.Width) + label.PADDING
default:
// TODO labelWidth+2*label.PADDING
width = go2.Max(width, float64(obj.LabelDimensions.Width))
}
}
// special handling
if obj.HasOutsideBottomLabel() || obj.Icon != nil {
height += float64(obj.LabelDimensions.Height) + label.PADDING
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
var position label.Position
if obj.IconPosition != nil {
position = label.Position(*obj.IconPosition)
}
if position.IsShapePosition() {
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
width += d2target.MAX_ICON_SIZE + label.PADDING
default:
width = go2.Max(width, d2target.MAX_ICON_SIZE+2*label.PADDING)
}
}
}
// reserve extra space for 3d/multiple by providing elk the larger dimensions
dx, dy := obj.GetModifierElementAdjustments()
width += dx
height += dy
return
}
func cleanupAdjustment(obj *d2graph.Object) {
// adjust size and position to account for space reserved for labels
if obj.HasLabel() {
position := label.Position(*obj.LabelPosition)
if position.IsShapePosition() {
var labelWidth float64
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
labelWidth = float64(obj.LabelDimensions.Width) + label.PADDING
obj.Width -= labelWidth
}
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
obj.TopLeft.X += labelWidth
obj.ShiftDescendants(labelWidth/2, 0)
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
obj.ShiftDescendants(-labelWidth/2, 0)
}
}
}
if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
position := label.Position(*obj.IconPosition)
if position.IsShapePosition() {
var iconWidth float64
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom,
label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
iconWidth = d2target.MAX_ICON_SIZE + label.PADDING
obj.Width -= iconWidth
}
switch position {
case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
obj.TopLeft.X += iconWidth
obj.ShiftDescendants(iconWidth/2, 0)
case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
obj.ShiftDescendants(-iconWidth/2, 0)
}
}
}
// special handling to start/end connections below label
if obj.HasOutsideBottomLabel() {
obj.Height -= float64(obj.LabelDimensions.Height) + label.PADDING
}
// remove the extra width/height we added for 3d/multiple after all objects/connections are placed
// and shift the shapes down accordingly
dx, dy := obj.GetModifierElementAdjustments()
if dx != 0 || dy != 0 {
obj.TopLeft.Y += dy
obj.ShiftDescendants(0, dy)
if !obj.IsContainer() {
obj.Width -= dx
obj.Height -= dy
}
}
}
func positionLabelsIcons(obj *d2graph.Object) {
if obj.Icon != nil && obj.IconPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.IconPosition = go2.Pointer(string(label.InsideTopLeft))
if obj.LabelPosition == nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopRight))
return
}
} else {
obj.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
if obj.HasLabel() && obj.LabelPosition == nil {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else if obj.HasOutsideBottomLabel() {
obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter))
} else if obj.Icon != nil {
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
} else {
obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
}
}
}

View file

@ -37,7 +37,7 @@ n2 -> n1: right to left
n1 -> n2 n1 -> n2
n2 -> n1 n2 -> n1
` `
g, err := d2compiler.Compile("", strings.NewReader(input), nil) g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err) assert.Nil(t, err)
n1, has := g.Root.HasChild([]string{"n1"}) n1, has := g.Root.HasChild([]string{"n1"})
@ -177,7 +177,7 @@ a.t2 -> b
b -> a.t2` b -> a.t2`
ctx := log.WithTB(context.Background(), t, nil) ctx := log.WithTB(context.Background(), t, nil)
g, err := d2compiler.Compile("", strings.NewReader(input), nil) g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err) assert.Nil(t, err)
g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram} g.Root.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram}
@ -298,7 +298,7 @@ c
container -> c: edge 1 container -> c: edge 1
` `
ctx := log.WithTB(context.Background(), t, nil) ctx := log.WithTB(context.Background(), t, nil)
g, err := d2compiler.Compile("", strings.NewReader(input), nil) g, _, err := d2compiler.Compile("", strings.NewReader(input), nil)
assert.Nil(t, err) assert.Nil(t, err)
container, has := g.Root.HasChild([]string{"container"}) container, has := g.Root.HasChild([]string{"container"})

View file

@ -15,17 +15,21 @@ import (
"oss.terrastruct.com/d2/d2layouts/d2near" "oss.terrastruct.com/d2/d2layouts/d2near"
"oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2layouts/d2sequence"
"oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/textmeasure" "oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/util-go/go2"
) )
type CompileOptions struct { type CompileOptions struct {
UTF16 bool UTF16 bool
FS fs.FS FS fs.FS
MeasuredTexts []*d2target.MText MeasuredTexts []*d2target.MText
Ruler *textmeasure.Ruler Ruler *textmeasure.Ruler
Layout func(context.Context, *d2graph.Graph) error LayoutResolver func(engine string) (d2graph.LayoutGraph, error)
ThemeID int64
Layout *string
// FontFamily controls the font family used for all texts that are not the following: // FontFamily controls the font family used for all texts that are not the following:
// - code // - code
@ -37,39 +41,45 @@ type CompileOptions struct {
InputPath string InputPath string
} }
func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) { func Compile(ctx context.Context, input string, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) (*d2target.Diagram, *d2graph.Graph, error) {
if opts == nil { if compileOpts == nil {
opts = &CompileOptions{} compileOpts = &CompileOptions{}
}
if renderOpts == nil {
renderOpts = &d2svg.RenderOpts{}
} }
g, err := d2compiler.Compile(opts.InputPath, strings.NewReader(input), &d2compiler.CompileOptions{ g, config, err := d2compiler.Compile(compileOpts.InputPath, strings.NewReader(input), &d2compiler.CompileOptions{
UTF16: opts.UTF16, UTF16: compileOpts.UTF16,
FS: opts.FS, FS: compileOpts.FS,
}) })
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
d, err := compile(ctx, g, opts) applyConfigs(config, compileOpts, renderOpts)
if err != nil { applyDefaults(compileOpts, renderOpts)
return nil, nil, err
d, err := compile(ctx, g, compileOpts, renderOpts)
if d != nil {
d.Config = config
} }
return d, g, nil return d, g, err
} }
func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2target.Diagram, error) { func compile(ctx context.Context, g *d2graph.Graph, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) (*d2target.Diagram, error) {
err := g.ApplyTheme(opts.ThemeID) err := g.ApplyTheme(*renderOpts.ThemeID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(g.Objects) > 0 { if len(g.Objects) > 0 {
err := g.SetDimensions(opts.MeasuredTexts, opts.Ruler, opts.FontFamily) err := g.SetDimensions(compileOpts.MeasuredTexts, compileOpts.Ruler, compileOpts.FontFamily)
if err != nil { if err != nil {
return nil, err return nil, err
} }
coreLayout, err := getLayout(opts) coreLayout, err := getLayout(compileOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -96,27 +106,27 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
} }
} }
d, err := d2exporter.Export(ctx, g, opts.FontFamily) d, err := d2exporter.Export(ctx, g, compileOpts.FontFamily)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, l := range g.Layers { for _, l := range g.Layers {
ld, err := compile(ctx, l, opts) ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
d.Layers = append(d.Layers, ld) d.Layers = append(d.Layers, ld)
} }
for _, l := range g.Scenarios { for _, l := range g.Scenarios {
ld, err := compile(ctx, l, opts) ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
d.Scenarios = append(d.Scenarios, ld) d.Scenarios = append(d.Scenarios, ld)
} }
for _, l := range g.Steps { for _, l := range g.Steps {
ld, err := compile(ctx, l, opts) ld, err := compile(ctx, l, compileOpts, renderOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -127,7 +137,7 @@ func compile(ctx context.Context, g *d2graph.Graph, opts *CompileOptions) (*d2ta
func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) { func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
if opts.Layout != nil { if opts.Layout != nil {
return opts.Layout, nil return opts.LayoutResolver(*opts.Layout)
} else if os.Getenv("D2_LAYOUT") == "dagre" { } else if os.Getenv("D2_LAYOUT") == "dagre" {
defaultLayout := func(ctx context.Context, g *d2graph.Graph) error { defaultLayout := func(ctx context.Context, g *d2graph.Graph) error {
return d2dagrelayout.Layout(ctx, g, nil) return d2dagrelayout.Layout(ctx, g, nil)
@ -137,3 +147,53 @@ func getLayout(opts *CompileOptions) (d2graph.LayoutGraph, error) {
return nil, errors.New("no available layout") return nil, errors.New("no available layout")
} }
} }
// applyConfigs applies the configs read from D2 and applies it to passed in opts
// It will only write to opt fields that are nil, as passed-in opts have precedence
func applyConfigs(config *d2target.Config, compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
if config == nil {
return
}
if compileOpts.Layout == nil {
compileOpts.Layout = config.LayoutEngine
}
if renderOpts.ThemeID == nil {
renderOpts.ThemeID = config.ThemeID
}
if renderOpts.DarkThemeID == nil {
renderOpts.DarkThemeID = config.DarkThemeID
}
if renderOpts.Sketch == nil {
renderOpts.Sketch = config.Sketch
}
if renderOpts.Pad == nil {
renderOpts.Pad = config.Pad
}
if renderOpts.Center == nil {
renderOpts.Center = config.Center
}
}
func applyDefaults(compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
if compileOpts.Layout == nil {
compileOpts.Layout = go2.Pointer("dagre")
}
if renderOpts.ThemeID == nil {
renderOpts.ThemeID = &d2themescatalog.NeutralDefault.ID
}
if renderOpts.Sketch == nil {
renderOpts.Sketch = go2.Pointer(false)
}
if *renderOpts.Sketch {
compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
}
if renderOpts.Pad == nil {
renderOpts.Pad = go2.Pointer(int64(d2svg.DEFAULT_PADDING))
}
if renderOpts.Center == nil {
renderOpts.Center = go2.Pointer(false)
}
}

View file

@ -305,7 +305,7 @@ func pathFromScopeObj(g *d2graph.Graph, key *d2ast.Key, fromScope *d2graph.Objec
func recompile(ast *d2ast.Map) (*d2graph.Graph, error) { func recompile(ast *d2ast.Map) (*d2graph.Graph, error) {
s := d2format.Format(ast) s := d2format.Format(ast)
g, err := d2compiler.Compile(ast.Range.Path, strings.NewReader(s), nil) g, _, err := d2compiler.Compile(ast.Range.Path, strings.NewReader(s), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err) return nil, fmt.Errorf("failed to recompile:\n%s\n%w", s, err)
} }

View file

@ -7002,7 +7002,7 @@ type editTest struct {
func (tc editTest) run(t *testing.T) { func (tc editTest) run(t *testing.T) {
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -7265,7 +7265,7 @@ scenarios: {
t.Parallel() t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -7725,7 +7725,7 @@ z
t.Parallel() t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -8095,7 +8095,7 @@ layers: {
t.Parallel() t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -8325,7 +8325,7 @@ scenarios: {
t.Parallel() t.Parallel()
d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
g, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -1030,9 +1030,15 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
var sb strings.Builder var sb strings.Builder
var rawb strings.Builder var rawb strings.Builder
lastPatternIndex := 0
defer func() { defer func() {
sv := strings.TrimRightFunc(sb.String(), unicode.IsSpace) sv := strings.TrimRightFunc(sb.String(), unicode.IsSpace)
rawv := strings.TrimRightFunc(rawb.String(), unicode.IsSpace) rawv := strings.TrimRightFunc(rawb.String(), unicode.IsSpace)
if s.Pattern != nil {
if lastPatternIndex < len(sv) {
s.Pattern = append(s.Pattern, sv[lastPatternIndex:])
}
}
if sv == "" { if sv == "" {
if len(s.Value) > 0 { if len(s.Value) > 0 {
return return
@ -1092,7 +1098,7 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
} }
if inKey { if inKey {
switch r { switch r {
case ':', '.', '<', '>': case ':', '.', '<', '>', '&':
p.rewind() p.rewind()
return s return s
case '-': case '-':
@ -1118,18 +1124,12 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
rawb.WriteRune(r) rawb.WriteRune(r)
r = r2 r = r2
case '*': case '*':
// TODO: need a peekNotSpace across escaped newlines if sb.Len() == 0 {
r2, eof := p.peek() s.Pattern = append(s.Pattern, "*")
if eof { } else {
return s s.Pattern = append(s.Pattern, sb.String()[lastPatternIndex:], "*")
} }
if r2 == '-' { lastPatternIndex = len(sb.String()) + 1
p.rewind()
return s
}
sb.WriteRune(r)
rawb.WriteRune(r)
r = r2
} }
} }
@ -1560,7 +1560,8 @@ func (p *parser) parseArrayNode(r rune) d2ast.ArrayNodeBox {
p.replay(r) p.replay(r)
vbox := p.parseValue() vbox := p.parseValue()
if vbox.UnquotedString != nil && vbox.UnquotedString.ScalarString() == "" { if vbox.UnquotedString != nil && vbox.UnquotedString.ScalarString() == "" &&
!(len(vbox.UnquotedString.Value) > 0 && vbox.UnquotedString.Value[0].Substitution != nil) {
p.errorf(p.pos, p.pos.Advance(r, p.utf16), "unquoted strings cannot start on %q", r) p.errorf(p.pos, p.pos.Advance(r, p.utf16), "unquoted strings cannot start on %q", r)
} }
box.Null = vbox.Null box.Null = vbox.Null

View file

@ -49,10 +49,10 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
// TODO account for stroke width of root border // TODO account for stroke width of root border
tl, br := rootDiagram.NestedBoundingBox() tl, br := rootDiagram.NestedBoundingBox()
left := tl.X - renderOpts.Pad left := tl.X - int(*renderOpts.Pad)
top := tl.Y - renderOpts.Pad top := tl.Y - int(*renderOpts.Pad)
width := br.X - tl.X + renderOpts.Pad*2 width := br.X - tl.X + int(*renderOpts.Pad)*2
height := br.Y - tl.Y + renderOpts.Pad*2 height := br.Y - tl.Y + int(*renderOpts.Pad)*2
fitToScreenWrapperOpening := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="%s" preserveAspectRatio="xMinYMin meet" viewBox="0 0 %d %d">`, fitToScreenWrapperOpening := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="%s" preserveAspectRatio="xMinYMin meet" viewBox="0 0 %d %d">`,
version.Version, version.Version,
@ -93,7 +93,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, css) fmt.Fprintf(buf, `<style type="text/css">%s</style>`, css)
} }
if renderOpts.Sketch { if renderOpts.Sketch != nil && *renderOpts.Sketch {
d2sketch.DefineFillPatterns(buf) d2sketch.DefineFillPatterns(buf)
} }

View file

@ -17,6 +17,7 @@ import (
"oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2layouts/d2elklayout" "oss.terrastruct.com/d2/d2layouts/d2elklayout"
"oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2lib"
@ -1339,16 +1340,22 @@ func run(t *testing.T, tc testCase) {
return return
} }
layout := d2dagrelayout.DefaultLayout layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
if strings.EqualFold(tc.engine, "elk") { if strings.EqualFold(engine, "elk") {
layout = d2elklayout.DefaultLayout return d2elklayout.DefaultLayout, nil
}
return d2dagrelayout.DefaultLayout, nil
}
renderOpts := &d2svg.RenderOpts{
Sketch: go2.Pointer(true),
ThemeID: go2.Pointer(tc.themeID),
} }
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler, Ruler: ruler,
Layout: layout, Layout: &tc.engine,
FontFamily: go2.Pointer(d2fonts.HandDrawn), LayoutResolver: layoutResolver,
ThemeID: tc.themeID, FontFamily: go2.Pointer(d2fonts.HandDrawn),
}) }, renderOpts)
if !tassert.Nil(t, err) { if !tassert.Nil(t, err) {
return return
} }
@ -1356,11 +1363,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestSketch/")) dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestSketch/"))
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ svgBytes, err := d2svg.Render(diagram, renderOpts)
Pad: d2svg.DEFAULT_PADDING,
Sketch: true,
ThemeID: tc.themeID,
})
assert.Success(t, err) assert.Success(t, err)
err = os.MkdirAll(dataPath, 0755) err = os.MkdirAll(dataPath, 0755)
assert.Success(t, err) assert.Success(t, err)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 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: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

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: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

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: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because one or more lines are too long

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: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 167 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: 115 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 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: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 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: 497 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 216 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -16,6 +16,7 @@ import (
"oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2renderers/d2svg" "oss.terrastruct.com/d2/d2renderers/d2svg"
@ -152,11 +153,17 @@ func run(t *testing.T, tc testCase) {
return return
} }
renderOpts := &d2svg.RenderOpts{
ThemeID: &tc.themeID,
}
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler, Ruler: ruler,
Layout: d2dagrelayout.DefaultLayout, LayoutResolver: layoutResolver,
ThemeID: tc.themeID, }, renderOpts)
})
if !tassert.Nil(t, err) { if !tassert.Nil(t, err) {
return return
} }
@ -164,10 +171,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestAppendix/")) dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestAppendix/"))
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ svgBytes, err := d2svg.Render(diagram, renderOpts)
Pad: d2svg.DEFAULT_PADDING,
ThemeID: tc.themeID,
})
assert.Success(t, err) assert.Success(t, err)
svgBytes = appendix.Append(diagram, ruler, svgBytes) svgBytes = appendix.Append(diagram, ruler, svgBytes)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

View file

@ -1,12 +1,12 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-3057089836" 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 { .appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1)); filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
} }
.d2-916646398 .text-bold { .d2-3057089836 .text-bold {
font-family: "d2-916646398-font-bold"; font-family: "d2-3057089836-font-bold";
} }
@font-face { @font-face {
font-family: d2-916646398-font-bold; font-family: d2-3057089836-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-916646398 .fill-N1{fill:#0A0F25;} .d2-3057089836 .fill-N1{fill:#0A0F25;}
.d2-916646398 .fill-N2{fill:#676C7E;} .d2-3057089836 .fill-N2{fill:#676C7E;}
.d2-916646398 .fill-N3{fill:#9499AB;} .d2-3057089836 .fill-N3{fill:#9499AB;}
.d2-916646398 .fill-N4{fill:#CFD2DD;} .d2-3057089836 .fill-N4{fill:#CFD2DD;}
.d2-916646398 .fill-N5{fill:#DEE1EB;} .d2-3057089836 .fill-N5{fill:#DEE1EB;}
.d2-916646398 .fill-N6{fill:#EEF1F8;} .d2-3057089836 .fill-N6{fill:#EEF1F8;}
.d2-916646398 .fill-N7{fill:#FFFFFF;} .d2-3057089836 .fill-N7{fill:#FFFFFF;}
.d2-916646398 .fill-B1{fill:#0D32B2;} .d2-3057089836 .fill-B1{fill:#0D32B2;}
.d2-916646398 .fill-B2{fill:#0D32B2;} .d2-3057089836 .fill-B2{fill:#0D32B2;}
.d2-916646398 .fill-B3{fill:#E3E9FD;} .d2-3057089836 .fill-B3{fill:#E3E9FD;}
.d2-916646398 .fill-B4{fill:#E3E9FD;} .d2-3057089836 .fill-B4{fill:#E3E9FD;}
.d2-916646398 .fill-B5{fill:#EDF0FD;} .d2-3057089836 .fill-B5{fill:#EDF0FD;}
.d2-916646398 .fill-B6{fill:#F7F8FE;} .d2-3057089836 .fill-B6{fill:#F7F8FE;}
.d2-916646398 .fill-AA2{fill:#4A6FF3;} .d2-3057089836 .fill-AA2{fill:#4A6FF3;}
.d2-916646398 .fill-AA4{fill:#EDF0FD;} .d2-3057089836 .fill-AA4{fill:#EDF0FD;}
.d2-916646398 .fill-AA5{fill:#F7F8FE;} .d2-3057089836 .fill-AA5{fill:#F7F8FE;}
.d2-916646398 .fill-AB4{fill:#EDF0FD;} .d2-3057089836 .fill-AB4{fill:#EDF0FD;}
.d2-916646398 .fill-AB5{fill:#F7F8FE;} .d2-3057089836 .fill-AB5{fill:#F7F8FE;}
.d2-916646398 .stroke-N1{stroke:#0A0F25;} .d2-3057089836 .stroke-N1{stroke:#0A0F25;}
.d2-916646398 .stroke-N2{stroke:#676C7E;} .d2-3057089836 .stroke-N2{stroke:#676C7E;}
.d2-916646398 .stroke-N3{stroke:#9499AB;} .d2-3057089836 .stroke-N3{stroke:#9499AB;}
.d2-916646398 .stroke-N4{stroke:#CFD2DD;} .d2-3057089836 .stroke-N4{stroke:#CFD2DD;}
.d2-916646398 .stroke-N5{stroke:#DEE1EB;} .d2-3057089836 .stroke-N5{stroke:#DEE1EB;}
.d2-916646398 .stroke-N6{stroke:#EEF1F8;} .d2-3057089836 .stroke-N6{stroke:#EEF1F8;}
.d2-916646398 .stroke-N7{stroke:#FFFFFF;} .d2-3057089836 .stroke-N7{stroke:#FFFFFF;}
.d2-916646398 .stroke-B1{stroke:#0D32B2;} .d2-3057089836 .stroke-B1{stroke:#0D32B2;}
.d2-916646398 .stroke-B2{stroke:#0D32B2;} .d2-3057089836 .stroke-B2{stroke:#0D32B2;}
.d2-916646398 .stroke-B3{stroke:#E3E9FD;} .d2-3057089836 .stroke-B3{stroke:#E3E9FD;}
.d2-916646398 .stroke-B4{stroke:#E3E9FD;} .d2-3057089836 .stroke-B4{stroke:#E3E9FD;}
.d2-916646398 .stroke-B5{stroke:#EDF0FD;} .d2-3057089836 .stroke-B5{stroke:#EDF0FD;}
.d2-916646398 .stroke-B6{stroke:#F7F8FE;} .d2-3057089836 .stroke-B6{stroke:#F7F8FE;}
.d2-916646398 .stroke-AA2{stroke:#4A6FF3;} .d2-3057089836 .stroke-AA2{stroke:#4A6FF3;}
.d2-916646398 .stroke-AA4{stroke:#EDF0FD;} .d2-3057089836 .stroke-AA4{stroke:#EDF0FD;}
.d2-916646398 .stroke-AA5{stroke:#F7F8FE;} .d2-3057089836 .stroke-AA5{stroke:#F7F8FE;}
.d2-916646398 .stroke-AB4{stroke:#EDF0FD;} .d2-3057089836 .stroke-AB4{stroke:#EDF0FD;}
.d2-916646398 .stroke-AB5{stroke:#F7F8FE;} .d2-3057089836 .stroke-AB5{stroke:#F7F8FE;}
.d2-916646398 .background-color-N1{background-color:#0A0F25;} .d2-3057089836 .background-color-N1{background-color:#0A0F25;}
.d2-916646398 .background-color-N2{background-color:#676C7E;} .d2-3057089836 .background-color-N2{background-color:#676C7E;}
.d2-916646398 .background-color-N3{background-color:#9499AB;} .d2-3057089836 .background-color-N3{background-color:#9499AB;}
.d2-916646398 .background-color-N4{background-color:#CFD2DD;} .d2-3057089836 .background-color-N4{background-color:#CFD2DD;}
.d2-916646398 .background-color-N5{background-color:#DEE1EB;} .d2-3057089836 .background-color-N5{background-color:#DEE1EB;}
.d2-916646398 .background-color-N6{background-color:#EEF1F8;} .d2-3057089836 .background-color-N6{background-color:#EEF1F8;}
.d2-916646398 .background-color-N7{background-color:#FFFFFF;} .d2-3057089836 .background-color-N7{background-color:#FFFFFF;}
.d2-916646398 .background-color-B1{background-color:#0D32B2;} .d2-3057089836 .background-color-B1{background-color:#0D32B2;}
.d2-916646398 .background-color-B2{background-color:#0D32B2;} .d2-3057089836 .background-color-B2{background-color:#0D32B2;}
.d2-916646398 .background-color-B3{background-color:#E3E9FD;} .d2-3057089836 .background-color-B3{background-color:#E3E9FD;}
.d2-916646398 .background-color-B4{background-color:#E3E9FD;} .d2-3057089836 .background-color-B4{background-color:#E3E9FD;}
.d2-916646398 .background-color-B5{background-color:#EDF0FD;} .d2-3057089836 .background-color-B5{background-color:#EDF0FD;}
.d2-916646398 .background-color-B6{background-color:#F7F8FE;} .d2-3057089836 .background-color-B6{background-color:#F7F8FE;}
.d2-916646398 .background-color-AA2{background-color:#4A6FF3;} .d2-3057089836 .background-color-AA2{background-color:#4A6FF3;}
.d2-916646398 .background-color-AA4{background-color:#EDF0FD;} .d2-3057089836 .background-color-AA4{background-color:#EDF0FD;}
.d2-916646398 .background-color-AA5{background-color:#F7F8FE;} .d2-3057089836 .background-color-AA5{background-color:#F7F8FE;}
.d2-916646398 .background-color-AB4{background-color:#EDF0FD;} .d2-3057089836 .background-color-AB4{background-color:#EDF0FD;}
.d2-916646398 .background-color-AB5{background-color:#F7F8FE;} .d2-3057089836 .background-color-AB5{background-color:#F7F8FE;}
.d2-916646398 .color-N1{color:#0A0F25;} .d2-3057089836 .color-N1{color:#0A0F25;}
.d2-916646398 .color-N2{color:#676C7E;} .d2-3057089836 .color-N2{color:#676C7E;}
.d2-916646398 .color-N3{color:#9499AB;} .d2-3057089836 .color-N3{color:#9499AB;}
.d2-916646398 .color-N4{color:#CFD2DD;} .d2-3057089836 .color-N4{color:#CFD2DD;}
.d2-916646398 .color-N5{color:#DEE1EB;} .d2-3057089836 .color-N5{color:#DEE1EB;}
.d2-916646398 .color-N6{color:#EEF1F8;} .d2-3057089836 .color-N6{color:#EEF1F8;}
.d2-916646398 .color-N7{color:#FFFFFF;} .d2-3057089836 .color-N7{color:#FFFFFF;}
.d2-916646398 .color-B1{color:#0D32B2;} .d2-3057089836 .color-B1{color:#0D32B2;}
.d2-916646398 .color-B2{color:#0D32B2;} .d2-3057089836 .color-B2{color:#0D32B2;}
.d2-916646398 .color-B3{color:#E3E9FD;} .d2-3057089836 .color-B3{color:#E3E9FD;}
.d2-916646398 .color-B4{color:#E3E9FD;} .d2-3057089836 .color-B4{color:#E3E9FD;}
.d2-916646398 .color-B5{color:#EDF0FD;} .d2-3057089836 .color-B5{color:#EDF0FD;}
.d2-916646398 .color-B6{color:#F7F8FE;} .d2-3057089836 .color-B6{color:#F7F8FE;}
.d2-916646398 .color-AA2{color:#4A6FF3;} .d2-3057089836 .color-AA2{color:#4A6FF3;}
.d2-916646398 .color-AA4{color:#EDF0FD;} .d2-3057089836 .color-AA4{color:#EDF0FD;}
.d2-916646398 .color-AA5{color:#F7F8FE;} .d2-3057089836 .color-AA5{color:#F7F8FE;}
.d2-916646398 .color-AB4{color:#EDF0FD;} .d2-3057089836 .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></a><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><mask id="d2-916646398" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285"> .d2-3057089836 .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></a><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><mask id="d2-3057089836" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect> <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> <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> </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>

Before

Width:  |  Height:  |  Size: 657 KiB

After

Width:  |  Height:  |  Size: 657 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -69,10 +69,10 @@ var grain string
var paper string var paper string
type RenderOpts struct { type RenderOpts struct {
Pad int Pad *int64
Sketch bool Sketch *bool
Center bool Center *bool
ThemeID int64 ThemeID *int64
DarkThemeID *int64 DarkThemeID *int64
Font string Font string
// the svg will be scaled by this factor, if unset the svg will fit to screen // the svg will be scaled by this factor, if unset the svg will fit to screen
@ -1201,9 +1201,11 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
offsetY /= 2 offsetY /= 2
} }
box.TopLeft.Y -= float64(offsetY) box.TopLeft.Y -= float64(offsetY)
box.Height += float64(offsetY)
box.Width += d2target.THREE_DEE_OFFSET box.Width += d2target.THREE_DEE_OFFSET
} else if targetShape.Multiple { } else if targetShape.Multiple {
box.TopLeft.Y -= d2target.MULTIPLE_OFFSET box.TopLeft.Y -= d2target.MULTIPLE_OFFSET
box.Height += d2target.MULTIPLE_OFFSET
box.Width += d2target.MULTIPLE_OFFSET box.Width += d2target.MULTIPLE_OFFSET
} }
} else { } else {
@ -1218,12 +1220,11 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
fontClass := "text" fontClass := "text"
if targetShape.FontFamily == "mono" { if targetShape.FontFamily == "mono" {
fontClass = "text-mono" fontClass = "text-mono"
} else { }
if targetShape.Bold { if targetShape.Bold {
fontClass += "-bold" fontClass += "-bold"
} else if targetShape.Italic { } else if targetShape.Italic {
fontClass += "-italic" fontClass += "-italic"
}
} }
if targetShape.Underline { if targetShape.Underline {
fontClass += " text-underline" fontClass += " text-underline"
@ -1340,7 +1341,11 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
} }
} }
} }
if targetShape.Tooltip != "" {
fmt.Fprintf(writer, `<title>%s</title>`,
svg.EscapeText(targetShape.Tooltip),
)
}
addAppendixItems(appendixWriter, targetShape, s) addAppendixItems(appendixWriter, targetShape, s)
fmt.Fprint(writer, closingTag) fmt.Fprint(writer, closingTag)
@ -1389,12 +1394,12 @@ func addAppendixItems(writer io.Writer, targetShape d2target.Shape, s shape.Shap
x := int(math.Ceil(p1.X)) x := int(math.Ceil(p1.X))
y := int(math.Ceil(p1.Y)) y := int(math.Ceil(p1.Y))
fmt.Fprintf(writer, `<g transform="translate(%d %d)" class="appendix-icon">%s</g>`, fmt.Fprintf(writer, `<g transform="translate(%d %d)" class="appendix-icon"><title>%s</title>%s</g>`,
x-appendixIconRadius, x-appendixIconRadius,
y-appendixIconRadius, y-appendixIconRadius,
svg.EscapeText(targetShape.Tooltip),
TooltipIcon, TooltipIcon,
) )
fmt.Fprintf(writer, `<title>%s</title>`, svg.EscapeText(targetShape.Tooltip))
} }
if targetShape.Link != "" { if targetShape.Link != "" {
if p2 == nil { if p2 == nil {
@ -1687,26 +1692,28 @@ func appendOnTrigger(buf *bytes.Buffer, source string, triggers []string, newCon
} }
} }
const DEFAULT_THEME int64 = 0
var DEFAULT_DARK_THEME *int64 = nil // no theme selected var DEFAULT_DARK_THEME *int64 = nil // no theme selected
func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
var sketchRunner *d2sketch.Runner var sketchRunner *d2sketch.Runner
pad := DEFAULT_PADDING pad := DEFAULT_PADDING
themeID := DEFAULT_THEME themeID := d2themescatalog.NeutralDefault.ID
darkThemeID := DEFAULT_DARK_THEME darkThemeID := DEFAULT_DARK_THEME
var scale *float64 var scale *float64
if opts != nil { if opts != nil {
pad = opts.Pad if opts.Pad != nil {
if opts.Sketch { pad = int(*opts.Pad)
}
if opts.Sketch != nil && *opts.Sketch {
var err error var err error
sketchRunner, err = d2sketch.InitSketchVM() sketchRunner, err = d2sketch.InitSketchVM()
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
themeID = opts.ThemeID if opts.ThemeID != nil {
themeID = *opts.ThemeID
}
darkThemeID = opts.DarkThemeID darkThemeID = opts.DarkThemeID
scale = opts.Scale scale = opts.Scale
} }
@ -1790,7 +1797,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
upperBuf := &bytes.Buffer{} upperBuf := &bytes.Buffer{}
if opts.MasterID == "" { if opts.MasterID == "" {
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf` EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
themeStylesheet, err := ThemeCSS(diagramHash, themeID, darkThemeID) themeStylesheet, err := ThemeCSS(diagramHash, &themeID, darkThemeID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1911,7 +1918,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
} }
alignment := "xMinYMin" alignment := "xMinYMin"
if opts.Center { if opts.Center != nil && *opts.Center {
alignment = "xMidYMid" alignment = "xMidYMid"
} }
fitToScreenWrapperOpening := "" fitToScreenWrapperOpening := ""
@ -1952,8 +1959,11 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
} }
// TODO include only colors that are being used to reduce size // TODO include only colors that are being used to reduce size
func ThemeCSS(diagramHash string, themeID int64, darkThemeID *int64) (stylesheet string, err error) { func ThemeCSS(diagramHash string, themeID *int64, darkThemeID *int64) (stylesheet string, err error) {
out, err := singleThemeRulesets(diagramHash, themeID) if themeID == nil {
themeID = &d2themescatalog.NeutralDefault.ID
}
out, err := singleThemeRulesets(diagramHash, *themeID)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -17,6 +17,7 @@ import (
"oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2fonts"
@ -426,12 +427,18 @@ func run(t *testing.T, tc testCase) {
return return
} }
renderOpts := &d2svg.RenderOpts{
ThemeID: go2.Pointer(int64(200)),
}
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.DefaultLayout, nil
}
diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
Ruler: ruler, Ruler: ruler,
Layout: d2dagrelayout.DefaultLayout, LayoutResolver: layoutResolver,
FontFamily: go2.Pointer(d2fonts.HandDrawn), FontFamily: go2.Pointer(d2fonts.HandDrawn),
ThemeID: 200, }, renderOpts)
})
if !tassert.Nil(t, err) { if !tassert.Nil(t, err) {
return return
} }
@ -439,10 +446,7 @@ func run(t *testing.T, tc testCase) {
dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestDarkTheme/")) dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestDarkTheme/"))
pathGotSVG := filepath.Join(dataPath, "dark_theme.got.svg") pathGotSVG := filepath.Join(dataPath, "dark_theme.got.svg")
svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ svgBytes, err := d2svg.Render(diagram, renderOpts)
Pad: d2svg.DEFAULT_PADDING,
ThemeID: 200,
})
assert.Success(t, err) assert.Success(t, err)
err = os.MkdirAll(dataPath, 0755) err = os.MkdirAll(dataPath, 0755)
assert.Success(t, err) assert.Success(t, err)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -6,6 +6,7 @@ import (
"hash/fnv" "hash/fnv"
"math" "math"
"net/url" "net/url"
"os"
"strings" "strings"
"oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/go2"
@ -38,8 +39,24 @@ const (
var BorderOffset = geo.NewVector(5, 5) var BorderOffset = geo.NewVector(5, 5)
type Config struct {
Sketch *bool `json:"sketch"`
ThemeID *int64 `json:"themeID"`
DarkThemeID *int64 `json:"darkThemeID"`
Pad *int64 `json:"pad"`
Center *bool `json:"center"`
LayoutEngine *string `json:"layoutEngine"`
ThemeOverrides *ThemeOverrides `json:"themeOverrides"`
}
type ThemeOverrides struct {
N1 *string `json:"n1"`
// TODO
}
type Diagram struct { type Diagram struct {
Name string `json:"name"` Name string `json:"name"`
Config *Config `json:"config,omitempty"`
// See docs on the same field in d2graph to understand what it means. // See docs on the same field in d2graph to understand what it means.
IsFolderOnly bool `json:"isFolderOnly"` IsFolderOnly bool `json:"isFolderOnly"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
@ -56,6 +73,76 @@ type Diagram struct {
Steps []*Diagram `json:"steps,omitempty"` Steps []*Diagram `json:"steps,omitempty"`
} }
// boardPath comes in the form of "x/layers/z/scenarios/a"
// or in the form of "layers/z/scenarios/a"
func (d *Diagram) GetBoard(boardPath string) *Diagram {
path := strings.Split(boardPath, string(os.PathSeparator))
if len(path) == 0 || len(boardPath) == 0 {
return d
}
return d.getBoard(path)
}
func (d *Diagram) getBoard(boardPath []string) *Diagram {
if len(boardPath) == 0 {
return d
}
head := boardPath[0]
if head == "index" {
return d
}
switch head {
case "layers":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Layers {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
case "scenarios":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Scenarios {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
case "steps":
if len(boardPath) < 2 {
return nil
}
for _, b := range d.Steps {
if b.Name == boardPath[1] {
return b.getBoard(boardPath[2:])
}
}
}
for _, b := range d.Layers {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
for _, b := range d.Scenarios {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
for _, b := range d.Steps {
if b.Name == head {
return b.getBoard(boardPath[2:])
}
}
return nil
}
func (diagram Diagram) Bytes() ([]byte, error) { func (diagram Diagram) Bytes() ([]byte, error) {
b1, err := json.Marshal(diagram.Shapes) b1, err := json.Marshal(diagram.Shapes)
if err != nil { if err != nil {
@ -65,7 +152,19 @@ func (diagram Diagram) Bytes() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
base := append(b1, b2...) b3, err := json.Marshal(diagram.Root)
if err != nil {
return nil, err
}
base := append(append(b1, b2...), b3...)
if diagram.Config != nil {
b, err := json.Marshal(diagram.Config)
if err != nil {
return nil, err
}
base = append(base, b...)
}
for _, d := range diagram.Layers { for _, d := range diagram.Layers {
slices, err := d.Bytes() slices, err := d.Bytes()
@ -199,11 +298,11 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_TOP") { if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_TOP") {
y1 = go2.Min(y1, targetShape.Pos.Y-label.PADDING-size) y1 = go2.Min(y1, targetShape.Pos.Y-label.PADDING-size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_BOTTOM") { } else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_BOTTOM") {
y2 = go2.Max(y2, targetShape.Pos.Y+label.PADDING+size) y2 = go2.Max(y2, targetShape.Pos.Y+targetShape.Height+label.PADDING+size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_LEFT") { } else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_LEFT") {
x1 = go2.Min(x1, targetShape.Pos.X-label.PADDING-size) x1 = go2.Min(x1, targetShape.Pos.X-label.PADDING-size)
} else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_RIGHT") { } else if strings.HasPrefix(targetShape.IconPosition, "OUTSIDE_RIGHT") {
x2 = go2.Max(x2, targetShape.Pos.X+label.PADDING+size) x2 = go2.Max(x2, targetShape.Pos.X+targetShape.Width+label.PADDING+size)
} }
} }

View file

@ -11,21 +11,24 @@ import (
"oss.terrastruct.com/d2/d2renderers/d2svg" "oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/d2themes/d2themescatalog" "oss.terrastruct.com/d2/d2themes/d2themescatalog"
"oss.terrastruct.com/d2/lib/textmeasure" "oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/util-go/go2"
) )
// Remember to add if err != nil checks in production. // Remember to add if err != nil checks in production.
func main() { func main() {
ruler, _ := textmeasure.NewRuler() ruler, _ := textmeasure.NewRuler()
defaultLayout := func(ctx context.Context, g *d2graph.Graph) error { layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
return d2dagrelayout.Layout(ctx, g, nil) return d2dagrelayout.DefaultLayout, nil
} }
diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ renderOpts := &d2svg.RenderOpts{
Layout: defaultLayout, Pad: go2.Pointer(int64(5)),
Ruler: ruler, ThemeID: &d2themescatalog.GrapeSoda.ID,
}) }
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{ compileOpts := &d2lib.CompileOptions{
Pad: d2svg.DEFAULT_PADDING, LayoutResolver: layoutResolver,
ThemeID: d2themescatalog.GrapeSoda.ID, Ruler: ruler,
}) }
diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", compileOpts, renderOpts)
out, _ := d2svg.Render(diagram, renderOpts)
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
} }

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
"oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2lib"
"oss.terrastruct.com/d2/d2oracle" "oss.terrastruct.com/d2/d2oracle"
@ -15,10 +16,14 @@ import (
func main() { func main() {
// From one.go // From one.go
ruler, _ := textmeasure.NewRuler() ruler, _ := textmeasure.NewRuler()
_, graph, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
Layout: d2dagrelayout.DefaultLayout, return d2dagrelayout.DefaultLayout, nil
Ruler: ruler, }
}) compileOpts := &d2lib.CompileOptions{
LayoutResolver: layoutResolver,
Ruler: ruler,
}
_, graph, _ := d2lib.Compile(context.Background(), "x -> y", compileOpts, nil)
// Create a shape with the ID, "meow" // Create a shape with the ID, "meow"
graph, _, _ = d2oracle.Create(graph, nil, "meow") graph, _, _ = d2oracle.Create(graph, nil, "meow")

View file

@ -16,15 +16,15 @@ import (
// Remember to add if err != nil checks in production. // Remember to add if err != nil checks in production.
func main() { func main() {
graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil) graph, config, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil)
graph.ApplyTheme(d2themescatalog.NeutralDefault.ID) graph.ApplyTheme(d2themescatalog.NeutralDefault.ID)
ruler, _ := textmeasure.NewRuler() ruler, _ := textmeasure.NewRuler()
_ = graph.SetDimensions(nil, ruler, nil) _ = graph.SetDimensions(nil, ruler, nil)
_ = d2dagrelayout.Layout(context.Background(), graph, nil) _ = d2dagrelayout.Layout(context.Background(), graph, nil)
diagram, _ := d2exporter.Export(context.Background(), graph, nil) diagram, _ := d2exporter.Export(context.Background(), graph, nil)
diagram.Config = config
out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{ out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{
Pad: d2svg.DEFAULT_PADDING, ThemeID: &d2themescatalog.NeutralDefault.ID,
ThemeID: d2themescatalog.NeutralDefault.ID,
}) })
_ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600)
} }

View file

@ -34,6 +34,7 @@ layers: {
link: steps.1 link: steps.1
style.font-size: 24 style.font-size: 24
} }
steps: { steps: {
1: { 1: {
titled: Earn pre-requisite titles (IM) titled: Earn pre-requisite titles (IM)

View file

@ -74,8 +74,17 @@ func TestCLI_E2E(t *testing.T) {
writeFile(t, dir, "empty-layer.d2", `layers: { x: {} }`) writeFile(t, dir, "empty-layer.d2", `layers: { x: {} }`)
err := runTestMain(t, ctx, dir, env, "empty-layer.d2") err := runTestMain(t, ctx, dir, env, "empty-layer.d2")
assert.Success(t, err) assert.Success(t, err)
},
},
{
name: "layer-link",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "test.d2", `doh: { link: layers.test2 }; layers: { test2: @test2.d2 }`)
writeFile(t, dir, "test2.d2", `x: I'm a Mac { link: https://example.com }`)
err := runTestMain(t, ctx, dir, env, "test.d2", "layer-link.svg")
assert.Success(t, err)
assert.TestdataDir(t, filepath.Join(dir, "empty-layer")) assert.TestdataDir(t, filepath.Join(dir, "layer-link"))
}, },
}, },
{ {
@ -111,6 +120,38 @@ func TestCLI_E2E(t *testing.T) {
shape: text shape: text
} }
steps: {
1: {
Approach road
}
2: {
Approach road -> Cross road
}
3: {
Cross road -> Make you wonder why
}
}
`)
err := runTestMain(t, ctx, dir, env, "--animate-interval=1400", "animation.d2")
assert.Success(t, err)
svg := readFile(t, dir, "animation.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "vars-animation",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "animation.d2", `vars: {
d2-config: {
theme-id: 300
}
}
Chicken's plan: {
style.font-size: 35
near: top-center
shape: text
}
steps: { steps: {
1: { 1: {
Approach road Approach road
@ -404,6 +445,17 @@ steps: {
assert.Testdata(t, ".svg", svg) assert.Testdata(t, ".svg", svg)
}, },
}, },
{
name: "import_vars",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `vars: { d2-config: @config }; x -> y`)
writeFile(t, dir, "config.d2", `theme-id: 200`)
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", name: "import_spread_nested",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
@ -449,6 +501,26 @@ steps: {
}) })
}, },
}, },
{
name: "vars-config",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "hello-world.d2", `vars: {
d2-config: {
sketch: true
layout-engine: elk
}
}
x -> y -> a.dream
it -> was -> all -> a.dream
i used to read
`)
env.Setenv("D2_THEME", "1")
err := runTestMain(t, ctx, dir, env, "--pad=10", "hello-world.d2")
assert.Success(t, err)
svg := readFile(t, dir, "hello-world.svg")
assert.Testdata(t, ".svg", svg)
},
},
} }
ctx := context.Background() ctx := context.Background()

View file

@ -1,9 +1,9 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1843626214" 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 { .d2-1843626214 .text-bold {
font-family: "d2-855222762-font-bold"; font-family: "d2-1843626214-font-bold";
} }
@font-face { @font-face {
font-family: d2-855222762-font-bold; font-family: d2-1843626214-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-855222762 .fill-N1{fill:#0A0F25;} .d2-1843626214 .fill-N1{fill:#0A0F25;}
.d2-855222762 .fill-N2{fill:#676C7E;} .d2-1843626214 .fill-N2{fill:#676C7E;}
.d2-855222762 .fill-N3{fill:#9499AB;} .d2-1843626214 .fill-N3{fill:#9499AB;}
.d2-855222762 .fill-N4{fill:#CFD2DD;} .d2-1843626214 .fill-N4{fill:#CFD2DD;}
.d2-855222762 .fill-N5{fill:#DEE1EB;} .d2-1843626214 .fill-N5{fill:#DEE1EB;}
.d2-855222762 .fill-N6{fill:#EEF1F8;} .d2-1843626214 .fill-N6{fill:#EEF1F8;}
.d2-855222762 .fill-N7{fill:#FFFFFF;} .d2-1843626214 .fill-N7{fill:#FFFFFF;}
.d2-855222762 .fill-B1{fill:#0D32B2;} .d2-1843626214 .fill-B1{fill:#0D32B2;}
.d2-855222762 .fill-B2{fill:#0D32B2;} .d2-1843626214 .fill-B2{fill:#0D32B2;}
.d2-855222762 .fill-B3{fill:#E3E9FD;} .d2-1843626214 .fill-B3{fill:#E3E9FD;}
.d2-855222762 .fill-B4{fill:#E3E9FD;} .d2-1843626214 .fill-B4{fill:#E3E9FD;}
.d2-855222762 .fill-B5{fill:#EDF0FD;} .d2-1843626214 .fill-B5{fill:#EDF0FD;}
.d2-855222762 .fill-B6{fill:#F7F8FE;} .d2-1843626214 .fill-B6{fill:#F7F8FE;}
.d2-855222762 .fill-AA2{fill:#4A6FF3;} .d2-1843626214 .fill-AA2{fill:#4A6FF3;}
.d2-855222762 .fill-AA4{fill:#EDF0FD;} .d2-1843626214 .fill-AA4{fill:#EDF0FD;}
.d2-855222762 .fill-AA5{fill:#F7F8FE;} .d2-1843626214 .fill-AA5{fill:#F7F8FE;}
.d2-855222762 .fill-AB4{fill:#EDF0FD;} .d2-1843626214 .fill-AB4{fill:#EDF0FD;}
.d2-855222762 .fill-AB5{fill:#F7F8FE;} .d2-1843626214 .fill-AB5{fill:#F7F8FE;}
.d2-855222762 .stroke-N1{stroke:#0A0F25;} .d2-1843626214 .stroke-N1{stroke:#0A0F25;}
.d2-855222762 .stroke-N2{stroke:#676C7E;} .d2-1843626214 .stroke-N2{stroke:#676C7E;}
.d2-855222762 .stroke-N3{stroke:#9499AB;} .d2-1843626214 .stroke-N3{stroke:#9499AB;}
.d2-855222762 .stroke-N4{stroke:#CFD2DD;} .d2-1843626214 .stroke-N4{stroke:#CFD2DD;}
.d2-855222762 .stroke-N5{stroke:#DEE1EB;} .d2-1843626214 .stroke-N5{stroke:#DEE1EB;}
.d2-855222762 .stroke-N6{stroke:#EEF1F8;} .d2-1843626214 .stroke-N6{stroke:#EEF1F8;}
.d2-855222762 .stroke-N7{stroke:#FFFFFF;} .d2-1843626214 .stroke-N7{stroke:#FFFFFF;}
.d2-855222762 .stroke-B1{stroke:#0D32B2;} .d2-1843626214 .stroke-B1{stroke:#0D32B2;}
.d2-855222762 .stroke-B2{stroke:#0D32B2;} .d2-1843626214 .stroke-B2{stroke:#0D32B2;}
.d2-855222762 .stroke-B3{stroke:#E3E9FD;} .d2-1843626214 .stroke-B3{stroke:#E3E9FD;}
.d2-855222762 .stroke-B4{stroke:#E3E9FD;} .d2-1843626214 .stroke-B4{stroke:#E3E9FD;}
.d2-855222762 .stroke-B5{stroke:#EDF0FD;} .d2-1843626214 .stroke-B5{stroke:#EDF0FD;}
.d2-855222762 .stroke-B6{stroke:#F7F8FE;} .d2-1843626214 .stroke-B6{stroke:#F7F8FE;}
.d2-855222762 .stroke-AA2{stroke:#4A6FF3;} .d2-1843626214 .stroke-AA2{stroke:#4A6FF3;}
.d2-855222762 .stroke-AA4{stroke:#EDF0FD;} .d2-1843626214 .stroke-AA4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AA5{stroke:#F7F8FE;} .d2-1843626214 .stroke-AA5{stroke:#F7F8FE;}
.d2-855222762 .stroke-AB4{stroke:#EDF0FD;} .d2-1843626214 .stroke-AB4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AB5{stroke:#F7F8FE;} .d2-1843626214 .stroke-AB5{stroke:#F7F8FE;}
.d2-855222762 .background-color-N1{background-color:#0A0F25;} .d2-1843626214 .background-color-N1{background-color:#0A0F25;}
.d2-855222762 .background-color-N2{background-color:#676C7E;} .d2-1843626214 .background-color-N2{background-color:#676C7E;}
.d2-855222762 .background-color-N3{background-color:#9499AB;} .d2-1843626214 .background-color-N3{background-color:#9499AB;}
.d2-855222762 .background-color-N4{background-color:#CFD2DD;} .d2-1843626214 .background-color-N4{background-color:#CFD2DD;}
.d2-855222762 .background-color-N5{background-color:#DEE1EB;} .d2-1843626214 .background-color-N5{background-color:#DEE1EB;}
.d2-855222762 .background-color-N6{background-color:#EEF1F8;} .d2-1843626214 .background-color-N6{background-color:#EEF1F8;}
.d2-855222762 .background-color-N7{background-color:#FFFFFF;} .d2-1843626214 .background-color-N7{background-color:#FFFFFF;}
.d2-855222762 .background-color-B1{background-color:#0D32B2;} .d2-1843626214 .background-color-B1{background-color:#0D32B2;}
.d2-855222762 .background-color-B2{background-color:#0D32B2;} .d2-1843626214 .background-color-B2{background-color:#0D32B2;}
.d2-855222762 .background-color-B3{background-color:#E3E9FD;} .d2-1843626214 .background-color-B3{background-color:#E3E9FD;}
.d2-855222762 .background-color-B4{background-color:#E3E9FD;} .d2-1843626214 .background-color-B4{background-color:#E3E9FD;}
.d2-855222762 .background-color-B5{background-color:#EDF0FD;} .d2-1843626214 .background-color-B5{background-color:#EDF0FD;}
.d2-855222762 .background-color-B6{background-color:#F7F8FE;} .d2-1843626214 .background-color-B6{background-color:#F7F8FE;}
.d2-855222762 .background-color-AA2{background-color:#4A6FF3;} .d2-1843626214 .background-color-AA2{background-color:#4A6FF3;}
.d2-855222762 .background-color-AA4{background-color:#EDF0FD;} .d2-1843626214 .background-color-AA4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AA5{background-color:#F7F8FE;} .d2-1843626214 .background-color-AA5{background-color:#F7F8FE;}
.d2-855222762 .background-color-AB4{background-color:#EDF0FD;} .d2-1843626214 .background-color-AB4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AB5{background-color:#F7F8FE;} .d2-1843626214 .background-color-AB5{background-color:#F7F8FE;}
.d2-855222762 .color-N1{color:#0A0F25;} .d2-1843626214 .color-N1{color:#0A0F25;}
.d2-855222762 .color-N2{color:#676C7E;} .d2-1843626214 .color-N2{color:#676C7E;}
.d2-855222762 .color-N3{color:#9499AB;} .d2-1843626214 .color-N3{color:#9499AB;}
.d2-855222762 .color-N4{color:#CFD2DD;} .d2-1843626214 .color-N4{color:#CFD2DD;}
.d2-855222762 .color-N5{color:#DEE1EB;} .d2-1843626214 .color-N5{color:#DEE1EB;}
.d2-855222762 .color-N6{color:#EEF1F8;} .d2-1843626214 .color-N6{color:#EEF1F8;}
.d2-855222762 .color-N7{color:#FFFFFF;} .d2-1843626214 .color-N7{color:#FFFFFF;}
.d2-855222762 .color-B1{color:#0D32B2;} .d2-1843626214 .color-B1{color:#0D32B2;}
.d2-855222762 .color-B2{color:#0D32B2;} .d2-1843626214 .color-B2{color:#0D32B2;}
.d2-855222762 .color-B3{color:#E3E9FD;} .d2-1843626214 .color-B3{color:#E3E9FD;}
.d2-855222762 .color-B4{color:#E3E9FD;} .d2-1843626214 .color-B4{color:#E3E9FD;}
.d2-855222762 .color-B5{color:#EDF0FD;} .d2-1843626214 .color-B5{color:#EDF0FD;}
.d2-855222762 .color-B6{color:#F7F8FE;} .d2-1843626214 .color-B6{color:#F7F8FE;}
.d2-855222762 .color-AA2{color:#4A6FF3;} .d2-1843626214 .color-AA2{color:#4A6FF3;}
.d2-855222762 .color-AA4{color:#EDF0FD;} .d2-1843626214 .color-AA4{color:#EDF0FD;}
.d2-855222762 .color-AA5{color:#F7F8FE;} .d2-1843626214 .color-AA5{color:#F7F8FE;}
.d2-855222762 .color-AB4{color:#EDF0FD;} .d2-1843626214 .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"> .d2-1843626214 .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-1843626214)" /></g><mask id="d2-1843626214" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect> <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="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> <rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,16 +1,16 @@
<?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[ <?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.6.0-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 { .d2-281690071 .text {
font-family: "d2-508224771-font-regular"; font-family: "d2-281690071-font-regular";
} }
@font-face { @font-face {
font-family: d2-508224771-font-regular; font-family: d2-281690071-font-regular;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
} }
.d2-508224771 .text-bold { .d2-281690071 .text-bold {
font-family: "d2-508224771-font-bold"; font-family: "d2-281690071-font-bold";
} }
@font-face { @font-face {
font-family: d2-508224771-font-bold; font-family: d2-281690071-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -25,92 +25,92 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-508224771 .fill-N1{fill:#0A0F25;} .d2-281690071 .fill-N1{fill:#0A0F25;}
.d2-508224771 .fill-N2{fill:#676C7E;} .d2-281690071 .fill-N2{fill:#676C7E;}
.d2-508224771 .fill-N3{fill:#9499AB;} .d2-281690071 .fill-N3{fill:#9499AB;}
.d2-508224771 .fill-N4{fill:#CFD2DD;} .d2-281690071 .fill-N4{fill:#CFD2DD;}
.d2-508224771 .fill-N5{fill:#DEE1EB;} .d2-281690071 .fill-N5{fill:#DEE1EB;}
.d2-508224771 .fill-N6{fill:#EEF1F8;} .d2-281690071 .fill-N6{fill:#EEF1F8;}
.d2-508224771 .fill-N7{fill:#FFFFFF;} .d2-281690071 .fill-N7{fill:#FFFFFF;}
.d2-508224771 .fill-B1{fill:#0D32B2;} .d2-281690071 .fill-B1{fill:#0D32B2;}
.d2-508224771 .fill-B2{fill:#0D32B2;} .d2-281690071 .fill-B2{fill:#0D32B2;}
.d2-508224771 .fill-B3{fill:#E3E9FD;} .d2-281690071 .fill-B3{fill:#E3E9FD;}
.d2-508224771 .fill-B4{fill:#E3E9FD;} .d2-281690071 .fill-B4{fill:#E3E9FD;}
.d2-508224771 .fill-B5{fill:#EDF0FD;} .d2-281690071 .fill-B5{fill:#EDF0FD;}
.d2-508224771 .fill-B6{fill:#F7F8FE;} .d2-281690071 .fill-B6{fill:#F7F8FE;}
.d2-508224771 .fill-AA2{fill:#4A6FF3;} .d2-281690071 .fill-AA2{fill:#4A6FF3;}
.d2-508224771 .fill-AA4{fill:#EDF0FD;} .d2-281690071 .fill-AA4{fill:#EDF0FD;}
.d2-508224771 .fill-AA5{fill:#F7F8FE;} .d2-281690071 .fill-AA5{fill:#F7F8FE;}
.d2-508224771 .fill-AB4{fill:#EDF0FD;} .d2-281690071 .fill-AB4{fill:#EDF0FD;}
.d2-508224771 .fill-AB5{fill:#F7F8FE;} .d2-281690071 .fill-AB5{fill:#F7F8FE;}
.d2-508224771 .stroke-N1{stroke:#0A0F25;} .d2-281690071 .stroke-N1{stroke:#0A0F25;}
.d2-508224771 .stroke-N2{stroke:#676C7E;} .d2-281690071 .stroke-N2{stroke:#676C7E;}
.d2-508224771 .stroke-N3{stroke:#9499AB;} .d2-281690071 .stroke-N3{stroke:#9499AB;}
.d2-508224771 .stroke-N4{stroke:#CFD2DD;} .d2-281690071 .stroke-N4{stroke:#CFD2DD;}
.d2-508224771 .stroke-N5{stroke:#DEE1EB;} .d2-281690071 .stroke-N5{stroke:#DEE1EB;}
.d2-508224771 .stroke-N6{stroke:#EEF1F8;} .d2-281690071 .stroke-N6{stroke:#EEF1F8;}
.d2-508224771 .stroke-N7{stroke:#FFFFFF;} .d2-281690071 .stroke-N7{stroke:#FFFFFF;}
.d2-508224771 .stroke-B1{stroke:#0D32B2;} .d2-281690071 .stroke-B1{stroke:#0D32B2;}
.d2-508224771 .stroke-B2{stroke:#0D32B2;} .d2-281690071 .stroke-B2{stroke:#0D32B2;}
.d2-508224771 .stroke-B3{stroke:#E3E9FD;} .d2-281690071 .stroke-B3{stroke:#E3E9FD;}
.d2-508224771 .stroke-B4{stroke:#E3E9FD;} .d2-281690071 .stroke-B4{stroke:#E3E9FD;}
.d2-508224771 .stroke-B5{stroke:#EDF0FD;} .d2-281690071 .stroke-B5{stroke:#EDF0FD;}
.d2-508224771 .stroke-B6{stroke:#F7F8FE;} .d2-281690071 .stroke-B6{stroke:#F7F8FE;}
.d2-508224771 .stroke-AA2{stroke:#4A6FF3;} .d2-281690071 .stroke-AA2{stroke:#4A6FF3;}
.d2-508224771 .stroke-AA4{stroke:#EDF0FD;} .d2-281690071 .stroke-AA4{stroke:#EDF0FD;}
.d2-508224771 .stroke-AA5{stroke:#F7F8FE;} .d2-281690071 .stroke-AA5{stroke:#F7F8FE;}
.d2-508224771 .stroke-AB4{stroke:#EDF0FD;} .d2-281690071 .stroke-AB4{stroke:#EDF0FD;}
.d2-508224771 .stroke-AB5{stroke:#F7F8FE;} .d2-281690071 .stroke-AB5{stroke:#F7F8FE;}
.d2-508224771 .background-color-N1{background-color:#0A0F25;} .d2-281690071 .background-color-N1{background-color:#0A0F25;}
.d2-508224771 .background-color-N2{background-color:#676C7E;} .d2-281690071 .background-color-N2{background-color:#676C7E;}
.d2-508224771 .background-color-N3{background-color:#9499AB;} .d2-281690071 .background-color-N3{background-color:#9499AB;}
.d2-508224771 .background-color-N4{background-color:#CFD2DD;} .d2-281690071 .background-color-N4{background-color:#CFD2DD;}
.d2-508224771 .background-color-N5{background-color:#DEE1EB;} .d2-281690071 .background-color-N5{background-color:#DEE1EB;}
.d2-508224771 .background-color-N6{background-color:#EEF1F8;} .d2-281690071 .background-color-N6{background-color:#EEF1F8;}
.d2-508224771 .background-color-N7{background-color:#FFFFFF;} .d2-281690071 .background-color-N7{background-color:#FFFFFF;}
.d2-508224771 .background-color-B1{background-color:#0D32B2;} .d2-281690071 .background-color-B1{background-color:#0D32B2;}
.d2-508224771 .background-color-B2{background-color:#0D32B2;} .d2-281690071 .background-color-B2{background-color:#0D32B2;}
.d2-508224771 .background-color-B3{background-color:#E3E9FD;} .d2-281690071 .background-color-B3{background-color:#E3E9FD;}
.d2-508224771 .background-color-B4{background-color:#E3E9FD;} .d2-281690071 .background-color-B4{background-color:#E3E9FD;}
.d2-508224771 .background-color-B5{background-color:#EDF0FD;} .d2-281690071 .background-color-B5{background-color:#EDF0FD;}
.d2-508224771 .background-color-B6{background-color:#F7F8FE;} .d2-281690071 .background-color-B6{background-color:#F7F8FE;}
.d2-508224771 .background-color-AA2{background-color:#4A6FF3;} .d2-281690071 .background-color-AA2{background-color:#4A6FF3;}
.d2-508224771 .background-color-AA4{background-color:#EDF0FD;} .d2-281690071 .background-color-AA4{background-color:#EDF0FD;}
.d2-508224771 .background-color-AA5{background-color:#F7F8FE;} .d2-281690071 .background-color-AA5{background-color:#F7F8FE;}
.d2-508224771 .background-color-AB4{background-color:#EDF0FD;} .d2-281690071 .background-color-AB4{background-color:#EDF0FD;}
.d2-508224771 .background-color-AB5{background-color:#F7F8FE;} .d2-281690071 .background-color-AB5{background-color:#F7F8FE;}
.d2-508224771 .color-N1{color:#0A0F25;} .d2-281690071 .color-N1{color:#0A0F25;}
.d2-508224771 .color-N2{color:#676C7E;} .d2-281690071 .color-N2{color:#676C7E;}
.d2-508224771 .color-N3{color:#9499AB;} .d2-281690071 .color-N3{color:#9499AB;}
.d2-508224771 .color-N4{color:#CFD2DD;} .d2-281690071 .color-N4{color:#CFD2DD;}
.d2-508224771 .color-N5{color:#DEE1EB;} .d2-281690071 .color-N5{color:#DEE1EB;}
.d2-508224771 .color-N6{color:#EEF1F8;} .d2-281690071 .color-N6{color:#EEF1F8;}
.d2-508224771 .color-N7{color:#FFFFFF;} .d2-281690071 .color-N7{color:#FFFFFF;}
.d2-508224771 .color-B1{color:#0D32B2;} .d2-281690071 .color-B1{color:#0D32B2;}
.d2-508224771 .color-B2{color:#0D32B2;} .d2-281690071 .color-B2{color:#0D32B2;}
.d2-508224771 .color-B3{color:#E3E9FD;} .d2-281690071 .color-B3{color:#E3E9FD;}
.d2-508224771 .color-B4{color:#E3E9FD;} .d2-281690071 .color-B4{color:#E3E9FD;}
.d2-508224771 .color-B5{color:#EDF0FD;} .d2-281690071 .color-B5{color:#EDF0FD;}
.d2-508224771 .color-B6{color:#F7F8FE;} .d2-281690071 .color-B6{color:#F7F8FE;}
.d2-508224771 .color-AA2{color:#4A6FF3;} .d2-281690071 .color-AA2{color:#4A6FF3;}
.d2-508224771 .color-AA4{color:#EDF0FD;} .d2-281690071 .color-AA4{color:#EDF0FD;}
.d2-508224771 .color-AA5{color:#F7F8FE;} .d2-281690071 .color-AA5{color:#F7F8FE;}
.d2-508224771 .color-AB4{color:#EDF0FD;} .d2-281690071 .color-AB4{color:#EDF0FD;}
.d2-508224771 .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><style type="text/css">.md em, .d2-281690071 .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><style type="text/css">.md em,
.md dfn { .md dfn {
font-family: "d2-508224771-font-italic"; font-family: "d2-281690071-font-italic";
} }
.md b, .md b,
.md strong { .md strong {
font-family: "d2-508224771-font-bold"; font-family: "d2-281690071-font-bold";
} }
.md code, .md code,
.md kbd, .md kbd,
.md pre, .md pre,
.md samp { .md samp {
font-family: "d2-508224771-font-mono"; font-family: "d2-281690071-font-mono";
font-size: 1em; font-size: 1em;
} }
@ -126,7 +126,7 @@
margin: 0; margin: 0;
color: var(--color-fg-default); color: var(--color-fg-default);
background-color: transparent; /* we don't want to define the background color */ background-color: transparent; /* we don't want to define the background color */
font-family: "d2-508224771-font-regular"; font-family: "d2-281690071-font-regular";
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
word-wrap: break-word; word-wrap: break-word;
@ -832,7 +832,7 @@
.md .contains-task-list:dir(rtl) .task-list-item-checkbox { .md .contains-task-list:dir(rtl) .task-list-item-checkbox {
margin: 0 -1.6em 0.25em 0.2em; margin: 0 -1.6em 0.25em 0.2em;
} }
</style><style type="text/css"><![CDATA[@keyframes d2Transition-d2-508224771-0 { </style><style type="text/css"><![CDATA[@keyframes d2Transition-d2-281690071-0 {
0%, 0.000000% { 0%, 0.000000% {
opacity: 0; opacity: 0;
} }
@ -842,7 +842,7 @@
25.000000%, 100% { 25.000000%, 100% {
opacity: 0; opacity: 0;
} }
}@keyframes d2Transition-d2-508224771-1 { }@keyframes d2Transition-d2-281690071-1 {
0%, 24.982143% { 0%, 24.982143% {
opacity: 0; opacity: 0;
} }
@ -852,7 +852,7 @@
50.000000%, 100% { 50.000000%, 100% {
opacity: 0; opacity: 0;
} }
}@keyframes d2Transition-d2-508224771-2 { }@keyframes d2Transition-d2-281690071-2 {
0%, 49.982143% { 0%, 49.982143% {
opacity: 0; opacity: 0;
} }
@ -862,26 +862,26 @@
75.000000%, 100% { 75.000000%, 100% {
opacity: 0; opacity: 0;
} }
}@keyframes d2Transition-d2-508224771-3 { }@keyframes d2Transition-d2-281690071-3 {
0%, 74.982143% { 0%, 74.982143% {
opacity: 0; opacity: 0;
} }
75.000000%, 100.000000% { 75.000000%, 100.000000% {
opacity: 1; opacity: 1;
} }
}]]></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"> }]]></style><g style="animation: d2Transition-d2-281690071-0 5600ms infinite" class="d2-281690071" 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-281690071" maskUnits="userSpaceOnUse" x="-206" y="-166" width="412" height="247">
<rect x="-206" y="-166" width="412" height="247" fill="white"></rect> <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> <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"> </mask></g><g style="animation: d2Transition-d2-281690071-1 5600ms infinite" class="d2-281690071" 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-2457953887" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="333">
<rect x="-131" y="-166" width="412" height="333" fill="white"></rect> <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="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> <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"> </mask></g><g style="animation: d2Transition-d2-281690071-2 5600ms infinite" class="d2-281690071" 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-1673714443)" /></g><mask id="d2-1673714443" maskUnits="userSpaceOnUse" x="-131" y="-166" width="412" height="499">
<rect x="-131" y="-166" width="412" height="499" fill="white"></rect> <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="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="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> <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"> </mask></g><g style="animation: d2Transition-d2-281690071-3 5600ms infinite" class="d2-281690071" 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-1425945673)" /></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-1425945673)" /></g><mask id="d2-1425945673" maskUnits="userSpaceOnUse" x="-104" y="-166" width="412" height="665">
<rect x="-104" y="-166" width="412" height="665" fill="white"></rect> <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="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="64.500000" y="188.500000" width="75" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,9 +1,9 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-3054270525" 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 { .d2-3054270525 .text-bold {
font-family: "d2-685498927-font-bold"; font-family: "d2-3054270525-font-bold";
} }
@font-face { @font-face {
font-family: d2-685498927-font-bold; font-family: d2-3054270525-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-685498927 .fill-N1{fill:#0A0F25;} .d2-3054270525 .fill-N1{fill:#0A0F25;}
.d2-685498927 .fill-N2{fill:#676C7E;} .d2-3054270525 .fill-N2{fill:#676C7E;}
.d2-685498927 .fill-N3{fill:#9499AB;} .d2-3054270525 .fill-N3{fill:#9499AB;}
.d2-685498927 .fill-N4{fill:#CFD2DD;} .d2-3054270525 .fill-N4{fill:#CFD2DD;}
.d2-685498927 .fill-N5{fill:#DEE1EB;} .d2-3054270525 .fill-N5{fill:#DEE1EB;}
.d2-685498927 .fill-N6{fill:#EEF1F8;} .d2-3054270525 .fill-N6{fill:#EEF1F8;}
.d2-685498927 .fill-N7{fill:#FFFFFF;} .d2-3054270525 .fill-N7{fill:#FFFFFF;}
.d2-685498927 .fill-B1{fill:#0D32B2;} .d2-3054270525 .fill-B1{fill:#0D32B2;}
.d2-685498927 .fill-B2{fill:#0D32B2;} .d2-3054270525 .fill-B2{fill:#0D32B2;}
.d2-685498927 .fill-B3{fill:#E3E9FD;} .d2-3054270525 .fill-B3{fill:#E3E9FD;}
.d2-685498927 .fill-B4{fill:#E3E9FD;} .d2-3054270525 .fill-B4{fill:#E3E9FD;}
.d2-685498927 .fill-B5{fill:#EDF0FD;} .d2-3054270525 .fill-B5{fill:#EDF0FD;}
.d2-685498927 .fill-B6{fill:#F7F8FE;} .d2-3054270525 .fill-B6{fill:#F7F8FE;}
.d2-685498927 .fill-AA2{fill:#4A6FF3;} .d2-3054270525 .fill-AA2{fill:#4A6FF3;}
.d2-685498927 .fill-AA4{fill:#EDF0FD;} .d2-3054270525 .fill-AA4{fill:#EDF0FD;}
.d2-685498927 .fill-AA5{fill:#F7F8FE;} .d2-3054270525 .fill-AA5{fill:#F7F8FE;}
.d2-685498927 .fill-AB4{fill:#EDF0FD;} .d2-3054270525 .fill-AB4{fill:#EDF0FD;}
.d2-685498927 .fill-AB5{fill:#F7F8FE;} .d2-3054270525 .fill-AB5{fill:#F7F8FE;}
.d2-685498927 .stroke-N1{stroke:#0A0F25;} .d2-3054270525 .stroke-N1{stroke:#0A0F25;}
.d2-685498927 .stroke-N2{stroke:#676C7E;} .d2-3054270525 .stroke-N2{stroke:#676C7E;}
.d2-685498927 .stroke-N3{stroke:#9499AB;} .d2-3054270525 .stroke-N3{stroke:#9499AB;}
.d2-685498927 .stroke-N4{stroke:#CFD2DD;} .d2-3054270525 .stroke-N4{stroke:#CFD2DD;}
.d2-685498927 .stroke-N5{stroke:#DEE1EB;} .d2-3054270525 .stroke-N5{stroke:#DEE1EB;}
.d2-685498927 .stroke-N6{stroke:#EEF1F8;} .d2-3054270525 .stroke-N6{stroke:#EEF1F8;}
.d2-685498927 .stroke-N7{stroke:#FFFFFF;} .d2-3054270525 .stroke-N7{stroke:#FFFFFF;}
.d2-685498927 .stroke-B1{stroke:#0D32B2;} .d2-3054270525 .stroke-B1{stroke:#0D32B2;}
.d2-685498927 .stroke-B2{stroke:#0D32B2;} .d2-3054270525 .stroke-B2{stroke:#0D32B2;}
.d2-685498927 .stroke-B3{stroke:#E3E9FD;} .d2-3054270525 .stroke-B3{stroke:#E3E9FD;}
.d2-685498927 .stroke-B4{stroke:#E3E9FD;} .d2-3054270525 .stroke-B4{stroke:#E3E9FD;}
.d2-685498927 .stroke-B5{stroke:#EDF0FD;} .d2-3054270525 .stroke-B5{stroke:#EDF0FD;}
.d2-685498927 .stroke-B6{stroke:#F7F8FE;} .d2-3054270525 .stroke-B6{stroke:#F7F8FE;}
.d2-685498927 .stroke-AA2{stroke:#4A6FF3;} .d2-3054270525 .stroke-AA2{stroke:#4A6FF3;}
.d2-685498927 .stroke-AA4{stroke:#EDF0FD;} .d2-3054270525 .stroke-AA4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AA5{stroke:#F7F8FE;} .d2-3054270525 .stroke-AA5{stroke:#F7F8FE;}
.d2-685498927 .stroke-AB4{stroke:#EDF0FD;} .d2-3054270525 .stroke-AB4{stroke:#EDF0FD;}
.d2-685498927 .stroke-AB5{stroke:#F7F8FE;} .d2-3054270525 .stroke-AB5{stroke:#F7F8FE;}
.d2-685498927 .background-color-N1{background-color:#0A0F25;} .d2-3054270525 .background-color-N1{background-color:#0A0F25;}
.d2-685498927 .background-color-N2{background-color:#676C7E;} .d2-3054270525 .background-color-N2{background-color:#676C7E;}
.d2-685498927 .background-color-N3{background-color:#9499AB;} .d2-3054270525 .background-color-N3{background-color:#9499AB;}
.d2-685498927 .background-color-N4{background-color:#CFD2DD;} .d2-3054270525 .background-color-N4{background-color:#CFD2DD;}
.d2-685498927 .background-color-N5{background-color:#DEE1EB;} .d2-3054270525 .background-color-N5{background-color:#DEE1EB;}
.d2-685498927 .background-color-N6{background-color:#EEF1F8;} .d2-3054270525 .background-color-N6{background-color:#EEF1F8;}
.d2-685498927 .background-color-N7{background-color:#FFFFFF;} .d2-3054270525 .background-color-N7{background-color:#FFFFFF;}
.d2-685498927 .background-color-B1{background-color:#0D32B2;} .d2-3054270525 .background-color-B1{background-color:#0D32B2;}
.d2-685498927 .background-color-B2{background-color:#0D32B2;} .d2-3054270525 .background-color-B2{background-color:#0D32B2;}
.d2-685498927 .background-color-B3{background-color:#E3E9FD;} .d2-3054270525 .background-color-B3{background-color:#E3E9FD;}
.d2-685498927 .background-color-B4{background-color:#E3E9FD;} .d2-3054270525 .background-color-B4{background-color:#E3E9FD;}
.d2-685498927 .background-color-B5{background-color:#EDF0FD;} .d2-3054270525 .background-color-B5{background-color:#EDF0FD;}
.d2-685498927 .background-color-B6{background-color:#F7F8FE;} .d2-3054270525 .background-color-B6{background-color:#F7F8FE;}
.d2-685498927 .background-color-AA2{background-color:#4A6FF3;} .d2-3054270525 .background-color-AA2{background-color:#4A6FF3;}
.d2-685498927 .background-color-AA4{background-color:#EDF0FD;} .d2-3054270525 .background-color-AA4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AA5{background-color:#F7F8FE;} .d2-3054270525 .background-color-AA5{background-color:#F7F8FE;}
.d2-685498927 .background-color-AB4{background-color:#EDF0FD;} .d2-3054270525 .background-color-AB4{background-color:#EDF0FD;}
.d2-685498927 .background-color-AB5{background-color:#F7F8FE;} .d2-3054270525 .background-color-AB5{background-color:#F7F8FE;}
.d2-685498927 .color-N1{color:#0A0F25;} .d2-3054270525 .color-N1{color:#0A0F25;}
.d2-685498927 .color-N2{color:#676C7E;} .d2-3054270525 .color-N2{color:#676C7E;}
.d2-685498927 .color-N3{color:#9499AB;} .d2-3054270525 .color-N3{color:#9499AB;}
.d2-685498927 .color-N4{color:#CFD2DD;} .d2-3054270525 .color-N4{color:#CFD2DD;}
.d2-685498927 .color-N5{color:#DEE1EB;} .d2-3054270525 .color-N5{color:#DEE1EB;}
.d2-685498927 .color-N6{color:#EEF1F8;} .d2-3054270525 .color-N6{color:#EEF1F8;}
.d2-685498927 .color-N7{color:#FFFFFF;} .d2-3054270525 .color-N7{color:#FFFFFF;}
.d2-685498927 .color-B1{color:#0D32B2;} .d2-3054270525 .color-B1{color:#0D32B2;}
.d2-685498927 .color-B2{color:#0D32B2;} .d2-3054270525 .color-B2{color:#0D32B2;}
.d2-685498927 .color-B3{color:#E3E9FD;} .d2-3054270525 .color-B3{color:#E3E9FD;}
.d2-685498927 .color-B4{color:#E3E9FD;} .d2-3054270525 .color-B4{color:#E3E9FD;}
.d2-685498927 .color-B5{color:#EDF0FD;} .d2-3054270525 .color-B5{color:#EDF0FD;}
.d2-685498927 .color-B6{color:#F7F8FE;} .d2-3054270525 .color-B6{color:#F7F8FE;}
.d2-685498927 .color-AA2{color:#4A6FF3;} .d2-3054270525 .color-AA2{color:#4A6FF3;}
.d2-685498927 .color-AA4{color:#EDF0FD;} .d2-3054270525 .color-AA4{color:#EDF0FD;}
.d2-685498927 .color-AA5{color:#F7F8FE;} .d2-3054270525 .color-AA5{color:#F7F8FE;}
.d2-685498927 .color-AB4{color:#EDF0FD;} .d2-3054270525 .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"> .d2-3054270525 .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-3054270525" maskUnits="userSpaceOnUse" x="-101" y="-101" width="290" height="268">
<rect x="-101" y="-101" width="290" height="268" fill="white"></rect> <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> <rect x="22.500000" y="22.500000" width="43" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg> </mask></svg></svg>

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -1,12 +1,12 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-1655546234" 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 { .appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1)); filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
} }
.d2-2353227294 .text-bold { .d2-1655546234 .text-bold {
font-family: "d2-2353227294-font-bold"; font-family: "d2-1655546234-font-bold";
} }
@font-face { @font-face {
font-family: d2-2353227294-font-bold; font-family: d2-1655546234-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-2353227294 .fill-N1{fill:#0A0F25;} .d2-1655546234 .fill-N1{fill:#0A0F25;}
.d2-2353227294 .fill-N2{fill:#676C7E;} .d2-1655546234 .fill-N2{fill:#676C7E;}
.d2-2353227294 .fill-N3{fill:#9499AB;} .d2-1655546234 .fill-N3{fill:#9499AB;}
.d2-2353227294 .fill-N4{fill:#CFD2DD;} .d2-1655546234 .fill-N4{fill:#CFD2DD;}
.d2-2353227294 .fill-N5{fill:#DEE1EB;} .d2-1655546234 .fill-N5{fill:#DEE1EB;}
.d2-2353227294 .fill-N6{fill:#EEF1F8;} .d2-1655546234 .fill-N6{fill:#EEF1F8;}
.d2-2353227294 .fill-N7{fill:#FFFFFF;} .d2-1655546234 .fill-N7{fill:#FFFFFF;}
.d2-2353227294 .fill-B1{fill:#0D32B2;} .d2-1655546234 .fill-B1{fill:#0D32B2;}
.d2-2353227294 .fill-B2{fill:#0D32B2;} .d2-1655546234 .fill-B2{fill:#0D32B2;}
.d2-2353227294 .fill-B3{fill:#E3E9FD;} .d2-1655546234 .fill-B3{fill:#E3E9FD;}
.d2-2353227294 .fill-B4{fill:#E3E9FD;} .d2-1655546234 .fill-B4{fill:#E3E9FD;}
.d2-2353227294 .fill-B5{fill:#EDF0FD;} .d2-1655546234 .fill-B5{fill:#EDF0FD;}
.d2-2353227294 .fill-B6{fill:#F7F8FE;} .d2-1655546234 .fill-B6{fill:#F7F8FE;}
.d2-2353227294 .fill-AA2{fill:#4A6FF3;} .d2-1655546234 .fill-AA2{fill:#4A6FF3;}
.d2-2353227294 .fill-AA4{fill:#EDF0FD;} .d2-1655546234 .fill-AA4{fill:#EDF0FD;}
.d2-2353227294 .fill-AA5{fill:#F7F8FE;} .d2-1655546234 .fill-AA5{fill:#F7F8FE;}
.d2-2353227294 .fill-AB4{fill:#EDF0FD;} .d2-1655546234 .fill-AB4{fill:#EDF0FD;}
.d2-2353227294 .fill-AB5{fill:#F7F8FE;} .d2-1655546234 .fill-AB5{fill:#F7F8FE;}
.d2-2353227294 .stroke-N1{stroke:#0A0F25;} .d2-1655546234 .stroke-N1{stroke:#0A0F25;}
.d2-2353227294 .stroke-N2{stroke:#676C7E;} .d2-1655546234 .stroke-N2{stroke:#676C7E;}
.d2-2353227294 .stroke-N3{stroke:#9499AB;} .d2-1655546234 .stroke-N3{stroke:#9499AB;}
.d2-2353227294 .stroke-N4{stroke:#CFD2DD;} .d2-1655546234 .stroke-N4{stroke:#CFD2DD;}
.d2-2353227294 .stroke-N5{stroke:#DEE1EB;} .d2-1655546234 .stroke-N5{stroke:#DEE1EB;}
.d2-2353227294 .stroke-N6{stroke:#EEF1F8;} .d2-1655546234 .stroke-N6{stroke:#EEF1F8;}
.d2-2353227294 .stroke-N7{stroke:#FFFFFF;} .d2-1655546234 .stroke-N7{stroke:#FFFFFF;}
.d2-2353227294 .stroke-B1{stroke:#0D32B2;} .d2-1655546234 .stroke-B1{stroke:#0D32B2;}
.d2-2353227294 .stroke-B2{stroke:#0D32B2;} .d2-1655546234 .stroke-B2{stroke:#0D32B2;}
.d2-2353227294 .stroke-B3{stroke:#E3E9FD;} .d2-1655546234 .stroke-B3{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B4{stroke:#E3E9FD;} .d2-1655546234 .stroke-B4{stroke:#E3E9FD;}
.d2-2353227294 .stroke-B5{stroke:#EDF0FD;} .d2-1655546234 .stroke-B5{stroke:#EDF0FD;}
.d2-2353227294 .stroke-B6{stroke:#F7F8FE;} .d2-1655546234 .stroke-B6{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AA2{stroke:#4A6FF3;} .d2-1655546234 .stroke-AA2{stroke:#4A6FF3;}
.d2-2353227294 .stroke-AA4{stroke:#EDF0FD;} .d2-1655546234 .stroke-AA4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AA5{stroke:#F7F8FE;} .d2-1655546234 .stroke-AA5{stroke:#F7F8FE;}
.d2-2353227294 .stroke-AB4{stroke:#EDF0FD;} .d2-1655546234 .stroke-AB4{stroke:#EDF0FD;}
.d2-2353227294 .stroke-AB5{stroke:#F7F8FE;} .d2-1655546234 .stroke-AB5{stroke:#F7F8FE;}
.d2-2353227294 .background-color-N1{background-color:#0A0F25;} .d2-1655546234 .background-color-N1{background-color:#0A0F25;}
.d2-2353227294 .background-color-N2{background-color:#676C7E;} .d2-1655546234 .background-color-N2{background-color:#676C7E;}
.d2-2353227294 .background-color-N3{background-color:#9499AB;} .d2-1655546234 .background-color-N3{background-color:#9499AB;}
.d2-2353227294 .background-color-N4{background-color:#CFD2DD;} .d2-1655546234 .background-color-N4{background-color:#CFD2DD;}
.d2-2353227294 .background-color-N5{background-color:#DEE1EB;} .d2-1655546234 .background-color-N5{background-color:#DEE1EB;}
.d2-2353227294 .background-color-N6{background-color:#EEF1F8;} .d2-1655546234 .background-color-N6{background-color:#EEF1F8;}
.d2-2353227294 .background-color-N7{background-color:#FFFFFF;} .d2-1655546234 .background-color-N7{background-color:#FFFFFF;}
.d2-2353227294 .background-color-B1{background-color:#0D32B2;} .d2-1655546234 .background-color-B1{background-color:#0D32B2;}
.d2-2353227294 .background-color-B2{background-color:#0D32B2;} .d2-1655546234 .background-color-B2{background-color:#0D32B2;}
.d2-2353227294 .background-color-B3{background-color:#E3E9FD;} .d2-1655546234 .background-color-B3{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B4{background-color:#E3E9FD;} .d2-1655546234 .background-color-B4{background-color:#E3E9FD;}
.d2-2353227294 .background-color-B5{background-color:#EDF0FD;} .d2-1655546234 .background-color-B5{background-color:#EDF0FD;}
.d2-2353227294 .background-color-B6{background-color:#F7F8FE;} .d2-1655546234 .background-color-B6{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AA2{background-color:#4A6FF3;} .d2-1655546234 .background-color-AA2{background-color:#4A6FF3;}
.d2-2353227294 .background-color-AA4{background-color:#EDF0FD;} .d2-1655546234 .background-color-AA4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AA5{background-color:#F7F8FE;} .d2-1655546234 .background-color-AA5{background-color:#F7F8FE;}
.d2-2353227294 .background-color-AB4{background-color:#EDF0FD;} .d2-1655546234 .background-color-AB4{background-color:#EDF0FD;}
.d2-2353227294 .background-color-AB5{background-color:#F7F8FE;} .d2-1655546234 .background-color-AB5{background-color:#F7F8FE;}
.d2-2353227294 .color-N1{color:#0A0F25;} .d2-1655546234 .color-N1{color:#0A0F25;}
.d2-2353227294 .color-N2{color:#676C7E;} .d2-1655546234 .color-N2{color:#676C7E;}
.d2-2353227294 .color-N3{color:#9499AB;} .d2-1655546234 .color-N3{color:#9499AB;}
.d2-2353227294 .color-N4{color:#CFD2DD;} .d2-1655546234 .color-N4{color:#CFD2DD;}
.d2-2353227294 .color-N5{color:#DEE1EB;} .d2-1655546234 .color-N5{color:#DEE1EB;}
.d2-2353227294 .color-N6{color:#EEF1F8;} .d2-1655546234 .color-N6{color:#EEF1F8;}
.d2-2353227294 .color-N7{color:#FFFFFF;} .d2-1655546234 .color-N7{color:#FFFFFF;}
.d2-2353227294 .color-B1{color:#0D32B2;} .d2-1655546234 .color-B1{color:#0D32B2;}
.d2-2353227294 .color-B2{color:#0D32B2;} .d2-1655546234 .color-B2{color:#0D32B2;}
.d2-2353227294 .color-B3{color:#E3E9FD;} .d2-1655546234 .color-B3{color:#E3E9FD;}
.d2-2353227294 .color-B4{color:#E3E9FD;} .d2-1655546234 .color-B4{color:#E3E9FD;}
.d2-2353227294 .color-B5{color:#EDF0FD;} .d2-1655546234 .color-B5{color:#EDF0FD;}
.d2-2353227294 .color-B6{color:#F7F8FE;} .d2-1655546234 .color-B6{color:#F7F8FE;}
.d2-2353227294 .color-AA2{color:#4A6FF3;} .d2-1655546234 .color-AA2{color:#4A6FF3;}
.d2-2353227294 .color-AA4{color:#EDF0FD;} .d2-1655546234 .color-AA4{color:#EDF0FD;}
.d2-2353227294 .color-AA5{color:#F7F8FE;} .d2-1655546234 .color-AA5{color:#F7F8FE;}
.d2-2353227294 .color-AB4{color:#EDF0FD;} .d2-1655546234 .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></a><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"> .d2-1655546234 .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></a><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)"> <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="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="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"/>
@ -104,7 +104,7 @@
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
</g><mask id="d2-2353227294" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="285"> </g><mask id="d2-1655546234" maskUnits="userSpaceOnUse" x="-101" y="-118" width="305" height="285">
<rect x="-101" y="-118" width="305" height="285" fill="white"></rect> <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> <rect x="38.500000" y="22.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg> </mask></svg></svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,12 +1,12 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-3111330921" 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 { .appendix-icon {
filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1)); filter: drop-shadow(0px 0px 32px rgba(31, 36, 58, 0.1));
} }
.d2-2067460405 .text-bold { .d2-3111330921 .text-bold {
font-family: "d2-2067460405-font-bold"; font-family: "d2-3111330921-font-bold";
} }
@font-face { @font-face {
font-family: d2-2067460405-font-bold; font-family: d2-3111330921-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -21,78 +21,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-2067460405 .fill-N1{fill:#0A0F25;} .d2-3111330921 .fill-N1{fill:#0A0F25;}
.d2-2067460405 .fill-N2{fill:#676C7E;} .d2-3111330921 .fill-N2{fill:#676C7E;}
.d2-2067460405 .fill-N3{fill:#9499AB;} .d2-3111330921 .fill-N3{fill:#9499AB;}
.d2-2067460405 .fill-N4{fill:#CFD2DD;} .d2-3111330921 .fill-N4{fill:#CFD2DD;}
.d2-2067460405 .fill-N5{fill:#DEE1EB;} .d2-3111330921 .fill-N5{fill:#DEE1EB;}
.d2-2067460405 .fill-N6{fill:#EEF1F8;} .d2-3111330921 .fill-N6{fill:#EEF1F8;}
.d2-2067460405 .fill-N7{fill:#FFFFFF;} .d2-3111330921 .fill-N7{fill:#FFFFFF;}
.d2-2067460405 .fill-B1{fill:#0D32B2;} .d2-3111330921 .fill-B1{fill:#0D32B2;}
.d2-2067460405 .fill-B2{fill:#0D32B2;} .d2-3111330921 .fill-B2{fill:#0D32B2;}
.d2-2067460405 .fill-B3{fill:#E3E9FD;} .d2-3111330921 .fill-B3{fill:#E3E9FD;}
.d2-2067460405 .fill-B4{fill:#E3E9FD;} .d2-3111330921 .fill-B4{fill:#E3E9FD;}
.d2-2067460405 .fill-B5{fill:#EDF0FD;} .d2-3111330921 .fill-B5{fill:#EDF0FD;}
.d2-2067460405 .fill-B6{fill:#F7F8FE;} .d2-3111330921 .fill-B6{fill:#F7F8FE;}
.d2-2067460405 .fill-AA2{fill:#4A6FF3;} .d2-3111330921 .fill-AA2{fill:#4A6FF3;}
.d2-2067460405 .fill-AA4{fill:#EDF0FD;} .d2-3111330921 .fill-AA4{fill:#EDF0FD;}
.d2-2067460405 .fill-AA5{fill:#F7F8FE;} .d2-3111330921 .fill-AA5{fill:#F7F8FE;}
.d2-2067460405 .fill-AB4{fill:#EDF0FD;} .d2-3111330921 .fill-AB4{fill:#EDF0FD;}
.d2-2067460405 .fill-AB5{fill:#F7F8FE;} .d2-3111330921 .fill-AB5{fill:#F7F8FE;}
.d2-2067460405 .stroke-N1{stroke:#0A0F25;} .d2-3111330921 .stroke-N1{stroke:#0A0F25;}
.d2-2067460405 .stroke-N2{stroke:#676C7E;} .d2-3111330921 .stroke-N2{stroke:#676C7E;}
.d2-2067460405 .stroke-N3{stroke:#9499AB;} .d2-3111330921 .stroke-N3{stroke:#9499AB;}
.d2-2067460405 .stroke-N4{stroke:#CFD2DD;} .d2-3111330921 .stroke-N4{stroke:#CFD2DD;}
.d2-2067460405 .stroke-N5{stroke:#DEE1EB;} .d2-3111330921 .stroke-N5{stroke:#DEE1EB;}
.d2-2067460405 .stroke-N6{stroke:#EEF1F8;} .d2-3111330921 .stroke-N6{stroke:#EEF1F8;}
.d2-2067460405 .stroke-N7{stroke:#FFFFFF;} .d2-3111330921 .stroke-N7{stroke:#FFFFFF;}
.d2-2067460405 .stroke-B1{stroke:#0D32B2;} .d2-3111330921 .stroke-B1{stroke:#0D32B2;}
.d2-2067460405 .stroke-B2{stroke:#0D32B2;} .d2-3111330921 .stroke-B2{stroke:#0D32B2;}
.d2-2067460405 .stroke-B3{stroke:#E3E9FD;} .d2-3111330921 .stroke-B3{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B4{stroke:#E3E9FD;} .d2-3111330921 .stroke-B4{stroke:#E3E9FD;}
.d2-2067460405 .stroke-B5{stroke:#EDF0FD;} .d2-3111330921 .stroke-B5{stroke:#EDF0FD;}
.d2-2067460405 .stroke-B6{stroke:#F7F8FE;} .d2-3111330921 .stroke-B6{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AA2{stroke:#4A6FF3;} .d2-3111330921 .stroke-AA2{stroke:#4A6FF3;}
.d2-2067460405 .stroke-AA4{stroke:#EDF0FD;} .d2-3111330921 .stroke-AA4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AA5{stroke:#F7F8FE;} .d2-3111330921 .stroke-AA5{stroke:#F7F8FE;}
.d2-2067460405 .stroke-AB4{stroke:#EDF0FD;} .d2-3111330921 .stroke-AB4{stroke:#EDF0FD;}
.d2-2067460405 .stroke-AB5{stroke:#F7F8FE;} .d2-3111330921 .stroke-AB5{stroke:#F7F8FE;}
.d2-2067460405 .background-color-N1{background-color:#0A0F25;} .d2-3111330921 .background-color-N1{background-color:#0A0F25;}
.d2-2067460405 .background-color-N2{background-color:#676C7E;} .d2-3111330921 .background-color-N2{background-color:#676C7E;}
.d2-2067460405 .background-color-N3{background-color:#9499AB;} .d2-3111330921 .background-color-N3{background-color:#9499AB;}
.d2-2067460405 .background-color-N4{background-color:#CFD2DD;} .d2-3111330921 .background-color-N4{background-color:#CFD2DD;}
.d2-2067460405 .background-color-N5{background-color:#DEE1EB;} .d2-3111330921 .background-color-N5{background-color:#DEE1EB;}
.d2-2067460405 .background-color-N6{background-color:#EEF1F8;} .d2-3111330921 .background-color-N6{background-color:#EEF1F8;}
.d2-2067460405 .background-color-N7{background-color:#FFFFFF;} .d2-3111330921 .background-color-N7{background-color:#FFFFFF;}
.d2-2067460405 .background-color-B1{background-color:#0D32B2;} .d2-3111330921 .background-color-B1{background-color:#0D32B2;}
.d2-2067460405 .background-color-B2{background-color:#0D32B2;} .d2-3111330921 .background-color-B2{background-color:#0D32B2;}
.d2-2067460405 .background-color-B3{background-color:#E3E9FD;} .d2-3111330921 .background-color-B3{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B4{background-color:#E3E9FD;} .d2-3111330921 .background-color-B4{background-color:#E3E9FD;}
.d2-2067460405 .background-color-B5{background-color:#EDF0FD;} .d2-3111330921 .background-color-B5{background-color:#EDF0FD;}
.d2-2067460405 .background-color-B6{background-color:#F7F8FE;} .d2-3111330921 .background-color-B6{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AA2{background-color:#4A6FF3;} .d2-3111330921 .background-color-AA2{background-color:#4A6FF3;}
.d2-2067460405 .background-color-AA4{background-color:#EDF0FD;} .d2-3111330921 .background-color-AA4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AA5{background-color:#F7F8FE;} .d2-3111330921 .background-color-AA5{background-color:#F7F8FE;}
.d2-2067460405 .background-color-AB4{background-color:#EDF0FD;} .d2-3111330921 .background-color-AB4{background-color:#EDF0FD;}
.d2-2067460405 .background-color-AB5{background-color:#F7F8FE;} .d2-3111330921 .background-color-AB5{background-color:#F7F8FE;}
.d2-2067460405 .color-N1{color:#0A0F25;} .d2-3111330921 .color-N1{color:#0A0F25;}
.d2-2067460405 .color-N2{color:#676C7E;} .d2-3111330921 .color-N2{color:#676C7E;}
.d2-2067460405 .color-N3{color:#9499AB;} .d2-3111330921 .color-N3{color:#9499AB;}
.d2-2067460405 .color-N4{color:#CFD2DD;} .d2-3111330921 .color-N4{color:#CFD2DD;}
.d2-2067460405 .color-N5{color:#DEE1EB;} .d2-3111330921 .color-N5{color:#DEE1EB;}
.d2-2067460405 .color-N6{color:#EEF1F8;} .d2-3111330921 .color-N6{color:#EEF1F8;}
.d2-2067460405 .color-N7{color:#FFFFFF;} .d2-3111330921 .color-N7{color:#FFFFFF;}
.d2-2067460405 .color-B1{color:#0D32B2;} .d2-3111330921 .color-B1{color:#0D32B2;}
.d2-2067460405 .color-B2{color:#0D32B2;} .d2-3111330921 .color-B2{color:#0D32B2;}
.d2-2067460405 .color-B3{color:#E3E9FD;} .d2-3111330921 .color-B3{color:#E3E9FD;}
.d2-2067460405 .color-B4{color:#E3E9FD;} .d2-3111330921 .color-B4{color:#E3E9FD;}
.d2-2067460405 .color-B5{color:#EDF0FD;} .d2-3111330921 .color-B5{color:#EDF0FD;}
.d2-2067460405 .color-B6{color:#F7F8FE;} .d2-3111330921 .color-B6{color:#F7F8FE;}
.d2-2067460405 .color-AA2{color:#4A6FF3;} .d2-3111330921 .color-AA2{color:#4A6FF3;}
.d2-2067460405 .color-AA4{color:#EDF0FD;} .d2-3111330921 .color-AA4{color:#EDF0FD;}
.d2-2067460405 .color-AA5{color:#F7F8FE;} .d2-3111330921 .color-AA5{color:#F7F8FE;}
.d2-2067460405 .color-AB4{color:#EDF0FD;} .d2-3111330921 .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></a><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"> .d2-3111330921 .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></a><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)"> <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="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="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"/>
@ -104,7 +104,7 @@
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
</g><mask id="d2-2067460405" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285"> </g><mask id="d2-3111330921" maskUnits="userSpaceOnUse" x="-101" y="-118" width="304" height="285">
<rect x="-101" y="-118" width="304" height="285" fill="white"></rect> <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> <rect x="38.500000" y="22.500000" width="8" height="21" fill="rgba(0,0,0,0.75)"></rect>
</mask></svg></svg> </mask></svg></svg>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,9 +1,9 @@
<?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[ <?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.6.0-HEAD" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1843626214" 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 { .d2-1843626214 .text-bold {
font-family: "d2-855222762-font-bold"; font-family: "d2-1843626214-font-bold";
} }
@font-face { @font-face {
font-family: d2-855222762-font-bold; font-family: d2-1843626214-font-bold;
src: url("data:application/font-woff;base64,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"); src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape { }]]></style><style type="text/css"><![CDATA[.shape {
shape-rendering: geometricPrecision; shape-rendering: geometricPrecision;
@ -18,78 +18,78 @@
opacity: 0.5; opacity: 0.5;
} }
.d2-855222762 .fill-N1{fill:#0A0F25;} .d2-1843626214 .fill-N1{fill:#0A0F25;}
.d2-855222762 .fill-N2{fill:#676C7E;} .d2-1843626214 .fill-N2{fill:#676C7E;}
.d2-855222762 .fill-N3{fill:#9499AB;} .d2-1843626214 .fill-N3{fill:#9499AB;}
.d2-855222762 .fill-N4{fill:#CFD2DD;} .d2-1843626214 .fill-N4{fill:#CFD2DD;}
.d2-855222762 .fill-N5{fill:#DEE1EB;} .d2-1843626214 .fill-N5{fill:#DEE1EB;}
.d2-855222762 .fill-N6{fill:#EEF1F8;} .d2-1843626214 .fill-N6{fill:#EEF1F8;}
.d2-855222762 .fill-N7{fill:#FFFFFF;} .d2-1843626214 .fill-N7{fill:#FFFFFF;}
.d2-855222762 .fill-B1{fill:#0D32B2;} .d2-1843626214 .fill-B1{fill:#0D32B2;}
.d2-855222762 .fill-B2{fill:#0D32B2;} .d2-1843626214 .fill-B2{fill:#0D32B2;}
.d2-855222762 .fill-B3{fill:#E3E9FD;} .d2-1843626214 .fill-B3{fill:#E3E9FD;}
.d2-855222762 .fill-B4{fill:#E3E9FD;} .d2-1843626214 .fill-B4{fill:#E3E9FD;}
.d2-855222762 .fill-B5{fill:#EDF0FD;} .d2-1843626214 .fill-B5{fill:#EDF0FD;}
.d2-855222762 .fill-B6{fill:#F7F8FE;} .d2-1843626214 .fill-B6{fill:#F7F8FE;}
.d2-855222762 .fill-AA2{fill:#4A6FF3;} .d2-1843626214 .fill-AA2{fill:#4A6FF3;}
.d2-855222762 .fill-AA4{fill:#EDF0FD;} .d2-1843626214 .fill-AA4{fill:#EDF0FD;}
.d2-855222762 .fill-AA5{fill:#F7F8FE;} .d2-1843626214 .fill-AA5{fill:#F7F8FE;}
.d2-855222762 .fill-AB4{fill:#EDF0FD;} .d2-1843626214 .fill-AB4{fill:#EDF0FD;}
.d2-855222762 .fill-AB5{fill:#F7F8FE;} .d2-1843626214 .fill-AB5{fill:#F7F8FE;}
.d2-855222762 .stroke-N1{stroke:#0A0F25;} .d2-1843626214 .stroke-N1{stroke:#0A0F25;}
.d2-855222762 .stroke-N2{stroke:#676C7E;} .d2-1843626214 .stroke-N2{stroke:#676C7E;}
.d2-855222762 .stroke-N3{stroke:#9499AB;} .d2-1843626214 .stroke-N3{stroke:#9499AB;}
.d2-855222762 .stroke-N4{stroke:#CFD2DD;} .d2-1843626214 .stroke-N4{stroke:#CFD2DD;}
.d2-855222762 .stroke-N5{stroke:#DEE1EB;} .d2-1843626214 .stroke-N5{stroke:#DEE1EB;}
.d2-855222762 .stroke-N6{stroke:#EEF1F8;} .d2-1843626214 .stroke-N6{stroke:#EEF1F8;}
.d2-855222762 .stroke-N7{stroke:#FFFFFF;} .d2-1843626214 .stroke-N7{stroke:#FFFFFF;}
.d2-855222762 .stroke-B1{stroke:#0D32B2;} .d2-1843626214 .stroke-B1{stroke:#0D32B2;}
.d2-855222762 .stroke-B2{stroke:#0D32B2;} .d2-1843626214 .stroke-B2{stroke:#0D32B2;}
.d2-855222762 .stroke-B3{stroke:#E3E9FD;} .d2-1843626214 .stroke-B3{stroke:#E3E9FD;}
.d2-855222762 .stroke-B4{stroke:#E3E9FD;} .d2-1843626214 .stroke-B4{stroke:#E3E9FD;}
.d2-855222762 .stroke-B5{stroke:#EDF0FD;} .d2-1843626214 .stroke-B5{stroke:#EDF0FD;}
.d2-855222762 .stroke-B6{stroke:#F7F8FE;} .d2-1843626214 .stroke-B6{stroke:#F7F8FE;}
.d2-855222762 .stroke-AA2{stroke:#4A6FF3;} .d2-1843626214 .stroke-AA2{stroke:#4A6FF3;}
.d2-855222762 .stroke-AA4{stroke:#EDF0FD;} .d2-1843626214 .stroke-AA4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AA5{stroke:#F7F8FE;} .d2-1843626214 .stroke-AA5{stroke:#F7F8FE;}
.d2-855222762 .stroke-AB4{stroke:#EDF0FD;} .d2-1843626214 .stroke-AB4{stroke:#EDF0FD;}
.d2-855222762 .stroke-AB5{stroke:#F7F8FE;} .d2-1843626214 .stroke-AB5{stroke:#F7F8FE;}
.d2-855222762 .background-color-N1{background-color:#0A0F25;} .d2-1843626214 .background-color-N1{background-color:#0A0F25;}
.d2-855222762 .background-color-N2{background-color:#676C7E;} .d2-1843626214 .background-color-N2{background-color:#676C7E;}
.d2-855222762 .background-color-N3{background-color:#9499AB;} .d2-1843626214 .background-color-N3{background-color:#9499AB;}
.d2-855222762 .background-color-N4{background-color:#CFD2DD;} .d2-1843626214 .background-color-N4{background-color:#CFD2DD;}
.d2-855222762 .background-color-N5{background-color:#DEE1EB;} .d2-1843626214 .background-color-N5{background-color:#DEE1EB;}
.d2-855222762 .background-color-N6{background-color:#EEF1F8;} .d2-1843626214 .background-color-N6{background-color:#EEF1F8;}
.d2-855222762 .background-color-N7{background-color:#FFFFFF;} .d2-1843626214 .background-color-N7{background-color:#FFFFFF;}
.d2-855222762 .background-color-B1{background-color:#0D32B2;} .d2-1843626214 .background-color-B1{background-color:#0D32B2;}
.d2-855222762 .background-color-B2{background-color:#0D32B2;} .d2-1843626214 .background-color-B2{background-color:#0D32B2;}
.d2-855222762 .background-color-B3{background-color:#E3E9FD;} .d2-1843626214 .background-color-B3{background-color:#E3E9FD;}
.d2-855222762 .background-color-B4{background-color:#E3E9FD;} .d2-1843626214 .background-color-B4{background-color:#E3E9FD;}
.d2-855222762 .background-color-B5{background-color:#EDF0FD;} .d2-1843626214 .background-color-B5{background-color:#EDF0FD;}
.d2-855222762 .background-color-B6{background-color:#F7F8FE;} .d2-1843626214 .background-color-B6{background-color:#F7F8FE;}
.d2-855222762 .background-color-AA2{background-color:#4A6FF3;} .d2-1843626214 .background-color-AA2{background-color:#4A6FF3;}
.d2-855222762 .background-color-AA4{background-color:#EDF0FD;} .d2-1843626214 .background-color-AA4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AA5{background-color:#F7F8FE;} .d2-1843626214 .background-color-AA5{background-color:#F7F8FE;}
.d2-855222762 .background-color-AB4{background-color:#EDF0FD;} .d2-1843626214 .background-color-AB4{background-color:#EDF0FD;}
.d2-855222762 .background-color-AB5{background-color:#F7F8FE;} .d2-1843626214 .background-color-AB5{background-color:#F7F8FE;}
.d2-855222762 .color-N1{color:#0A0F25;} .d2-1843626214 .color-N1{color:#0A0F25;}
.d2-855222762 .color-N2{color:#676C7E;} .d2-1843626214 .color-N2{color:#676C7E;}
.d2-855222762 .color-N3{color:#9499AB;} .d2-1843626214 .color-N3{color:#9499AB;}
.d2-855222762 .color-N4{color:#CFD2DD;} .d2-1843626214 .color-N4{color:#CFD2DD;}
.d2-855222762 .color-N5{color:#DEE1EB;} .d2-1843626214 .color-N5{color:#DEE1EB;}
.d2-855222762 .color-N6{color:#EEF1F8;} .d2-1843626214 .color-N6{color:#EEF1F8;}
.d2-855222762 .color-N7{color:#FFFFFF;} .d2-1843626214 .color-N7{color:#FFFFFF;}
.d2-855222762 .color-B1{color:#0D32B2;} .d2-1843626214 .color-B1{color:#0D32B2;}
.d2-855222762 .color-B2{color:#0D32B2;} .d2-1843626214 .color-B2{color:#0D32B2;}
.d2-855222762 .color-B3{color:#E3E9FD;} .d2-1843626214 .color-B3{color:#E3E9FD;}
.d2-855222762 .color-B4{color:#E3E9FD;} .d2-1843626214 .color-B4{color:#E3E9FD;}
.d2-855222762 .color-B5{color:#EDF0FD;} .d2-1843626214 .color-B5{color:#EDF0FD;}
.d2-855222762 .color-B6{color:#F7F8FE;} .d2-1843626214 .color-B6{color:#F7F8FE;}
.d2-855222762 .color-AA2{color:#4A6FF3;} .d2-1843626214 .color-AA2{color:#4A6FF3;}
.d2-855222762 .color-AA4{color:#EDF0FD;} .d2-1843626214 .color-AA4{color:#EDF0FD;}
.d2-855222762 .color-AA5{color:#F7F8FE;} .d2-1843626214 .color-AA5{color:#F7F8FE;}
.d2-855222762 .color-AB4{color:#EDF0FD;} .d2-1843626214 .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"> .d2-1843626214 .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-1843626214)" /></g><mask id="d2-1843626214" maskUnits="userSpaceOnUse" x="-101" y="-101" width="256" height="434">
<rect x="-101" y="-101" width="256" height="434" fill="white"></rect> <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="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> <rect x="22.500000" y="188.500000" width="9" height="21" fill="rgba(0,0,0,0.75)"></rect>

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

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