Merge branch 'terrastruct:master' into master

This commit is contained in:
imJoshuaRice 2024-07-09 10:45:25 +01:00 committed by GitHub
commit 8bf3039cc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
797 changed files with 66859 additions and 12884 deletions

View file

@ -46,34 +46,63 @@ https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8
## What does D2 look like? ## What does D2 look like?
```d2 ```d2
# Actors vars: {
hans: Hans Niemann d2-config: {
layout-engine: elk
defendants: { # Terminal theme code
mc: Magnus Carlsen theme-id: 300
playmagnus: Play Magnus Group }
chesscom: Chess.com }
naka: Hikaru Nakamura network: {
cell tower: {
mc -> playmagnus: Owns majority satellites: {
playmagnus <-> chesscom: Merger talks shape: stored_data
chesscom -> naka: Sponsoring style.multiple: true
} }
# Accusations transmitter
hans -> defendants: 'sueing for $100M'
# Claim satellites -> transmitter: send
defendants.naka -> hans: Accused of cheating on his stream satellites -> transmitter: send
defendants.mc -> hans: Lost then withdrew with accusations satellites -> transmitter: send
defendants.chesscom -> hans: 72 page report of cheating }
online portal: {
ui: {shape: hexagon}
}
data processor: {
storage: {
shape: cylinder
style.multiple: true
}
}
cell tower.transmitter -> data processor.storage: phone logs
}
user: {
shape: person
width: 130
}
user -> network.cell tower: make call
user -> network.online portal.ui: access {
style.stroke-dash: 3
}
api server -> network.online portal.ui: display
api server -> logs: persist
logs: {shape: page; style.multiple: true}
network.data processor -> api server
``` ```
> There is syntax highlighting with the editor plugins linked below. <p align="center">
<img width="400px" src="./docs/assets/example.svg" alt="D2 render example" />
</p>
<img src="./docs/assets/syntax.png" alt="D2 render example" /> > Open in [playground](https://play.d2lang.com/?script=rVLLTsQwDLznKyJxbrWwtyLxFdyR1Zg2ahpHibvLCvXfcdqGfSHthVv8yIxn7APE1OhvpbV5qVryn7ZbQ60dnGjiCn1nPTYa3bCkn_Q7xtF6cJp7HFG3ZHCpLGFlTaP3u51kZjUrj3ykOKyYLTr5REeMhSMBS84yppKRXA9B-BJTRPNhgKEU-OSwHifHNjjp4DitxLNa-SP4NFpmjOoGXVdvl2VBR2_-sWeZgLwTp3SgyOCKnsnKa5PU4xd05OfyIWvTIVKLKdHZExEOHd4Z0p4E3oi2h25s8Ge764uZs4Rr4vqXMfQkAhx1SVanplQWtU0QMCbyEh-t4b7Rz_td6cuo267ryzWPMMiFgHN3XVdu1dkmaPM8K-EiLnGkASsDScj2mQqCFcvj4RGUsSnI_d70Z2GrCptYrVHZTRADXv80VWgL0bVvGfJMoH4A)
> Rendered with the TALA layout engine.
> For more examples, see [./docs/examples](./docs/examples). > For more examples, see [./docs/examples](./docs/examples).
@ -231,6 +260,7 @@ let us know and we'll be happy to include it here!
- **Logseq-D2**: [https://github.com/b-yp/logseq-d2](https://github.com/b-yp/logseq-d2) - **Logseq-D2**: [https://github.com/b-yp/logseq-d2](https://github.com/b-yp/logseq-d2)
- **ent2d2**: [https://github.com/tmc/ent2d2](https://github.com/tmc/ent2d2) - **ent2d2**: [https://github.com/tmc/ent2d2](https://github.com/tmc/ent2d2)
- **MkDocs Plugin**: [https://github.com/landmaj/mkdocs-d2-plugin](https://github.com/landmaj/mkdocs-d2-plugin) - **MkDocs Plugin**: [https://github.com/landmaj/mkdocs-d2-plugin](https://github.com/landmaj/mkdocs-d2-plugin)
- **Remark Plugin**: [https://github.com/mech-a/remark-d2](https://github.com/mech-a/remark-d2)
### Misc ### Misc
@ -261,6 +291,12 @@ Do you have or see an open-source project with `.d2` files? Please submit a PR a
this selected list of featured projects using D2. this selected list of featured projects using D2.
- [ElasticSearch](https://github.com/elastic/beats/blob/main/libbeat/publisher/queue/proxy/diagrams/broker.d2) - [ElasticSearch](https://github.com/elastic/beats/blob/main/libbeat/publisher/queue/proxy/diagrams/broker.d2)
- [Sourcegraph](https://handbook.sourcegraph.com/departments/engineering/managed-services/telemetry-gateway/#dev-architecture-diagram)
- [Temporal](https://github.com/temporalio/temporal/blob/0be2681c994470c7c61ea88e4fcef89bb4024e58/docs/_assets/matching-context.d2)
- [Tauri](https://v2.tauri.app/concept/inter-process-communication/)
- Rust GUI framework (78.5k stars)
- [Intellij](https://github.com/JetBrains/intellij-community/blob/45bcfc17a3f3e0d8548bc69e922d4ca97ac21b2b/platform/settings/docs/topics/overview.md)
- [Coder](https://coder.com/blog/managing-templates-in-coder)
- [UC - [UC
Berkeley](https://github.com/ucb-bar/hammer/blob/2b5c04d7b7d9ee3c73575efcd7ee0698bd5bfa88/doc/Hammer-Use/hier.d2) Berkeley](https://github.com/ucb-bar/hammer/blob/2b5c04d7b7d9ee3c73575efcd7ee0698bd5bfa88/doc/Hammer-Use/hier.d2)
- [Coronacheck](https://github.com/minvws/nl-covid19-coronacheck-app-ios/blob/e1567e9d1633b3273c537a105bff0e7d3a57ecfe/Diagrams/client-side-datamodel.d2) - [Coronacheck](https://github.com/minvws/nl-covid19-coronacheck-app-ios/blob/e1567e9d1633b3273c537a105bff0e7d3a57ecfe/Diagrams/client-side-datamodel.d2)

View file

@ -241,8 +241,9 @@ create_windows_amd64() {
'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
| jq -r '.Reservations[].Instances[].State.Name') | jq -r '.Reservations[].Instances[].State.Name')
if [ -z "$state" ]; then if [ -z "$state" ]; then
# public AMIs are deprecated every few months so just search the latest Windows Server one for recreating
sh_c aws ec2 run-instances \ sh_c aws ec2 run-instances \
--image-id=ami-0c5300e833c2b32f3 \ --image-id=ami-03ea14ccbeab7b2d5 \
--count=1 \ --count=1 \
--instance-type=t3.medium \ --instance-type=t3.medium \
--security-groups=windows \ --security-groups=windows \
@ -441,19 +442,22 @@ init_remote_windows() {
header "$REMOTE_NAME" header "$REMOTE_NAME"
wait_remote_host_windows wait_remote_host_windows
# rsync was broken in this script last ran on 4/10/24.
# had to upgrade with `pacman -Syyu rsync` after
init_ps1=$(cat <<EOF init_ps1=$(cat <<EOF
\$ProgressPreference = 'SilentlyContinue' \$ProgressPreference = 'SilentlyContinue'
# Bootstrap PowerShell v7 # Bootstrap PowerShell v7
if ((\$PSVersionTable.PSVersion).Major -eq 5) { if ((\$PSVersionTable.PSVersion).Major -eq 5) {
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile .\microsoft.ui.xaml.2.7.3.zip Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.8.6 -OutFile .\microsoft.ui.xaml.2.8.6.zip
Expand-Archive -Force .\microsoft.ui.xaml.2.7.3.zip Expand-Archive -Force .\microsoft.ui.xaml.2.8.6.zip
Add-AppxPackage .\microsoft.ui.xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx Add-AppxPackage .\microsoft.ui.xaml.2.8.6\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.8.appx
Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.3.2691/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.7.10861/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.3.2691/7bcb1a0ab33340daa57fa5b81faec616_License1.xml -OutFile .\7bcb1a0ab33340daa57fa5b81faec616_License1.xml Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.7.10861/30fe89a9836a4cfbbd3fedce72a58680_License1.xml -OutFile .\30fe89a9836a4cfbbd3fedce72a58680_License1.xml
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxProvisionedPackage -online -PackagePath .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -LicensePath .\7bcb1a0ab33340daa57fa5b81faec616_License1.xml -DependencyPackagePath Microsoft.VCLibs.x64.14.00.Desktop.appx Add-AppxProvisionedPackage -online -PackagePath .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -LicensePath .\30fe89a9836a4cfbbd3fedce72a58680_License1.xml -DependencyPackagePath Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle Add-AppxPackage .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
winget install --silent --accept-package-agreements --accept-source-agreements Microsoft.DotNet.SDK.7 winget install --silent --accept-package-agreements --accept-source-agreements Microsoft.DotNet.SDK.7
@ -551,6 +555,7 @@ EOF
) )
# Windows and AWS SSM both defeated me. # Windows and AWS SSM both defeated me.
# alixander: Step 3's output got mangled for me, so had to replace the line with `printf '%s\n' "$gen_init_ps1" > script` and then open it with vim
FGCOLOR=3 bigheader "WARNING: WINDOWS INITIALIZATION MUST BE COMPLETED MANUALLY OVER RDP AND POWERSHELL!" FGCOLOR=3 bigheader "WARNING: WINDOWS INITIALIZATION MUST BE COMPLETED MANUALLY OVER RDP AND POWERSHELL!"
warn '1. Obtain Windows RDP password with:' warn '1. Obtain Windows RDP password with:'

View file

@ -4,7 +4,7 @@ cd -- "$(dirname "$0")/../.."
. ./ci/sub/lib.sh . ./ci/sub/lib.sh
tag="$(sh_c docker build \ tag="$(sh_c docker build \
--build-arg GOVERSION="1.20.8.linux-$ARCH" \ --build-arg GOVERSION="1.22.2.linux-$ARCH" \
-qf ./ci/release/linux/Dockerfile ./ci/release/linux)" -qf ./ci/release/linux/Dockerfile ./ci/release/linux)"
docker_run \ docker_run \
-e DRY_RUN \ -e DRY_RUN \

View file

@ -2,6 +2,14 @@
#### Improvements 🧹 #### Improvements 🧹
- Opacity 0 shapes no longer have a label mask which made any segment of connections going through them lower opacity [#1940](https://github.com/terrastruct/d2/pull/1940)
- Bidirectional connections are now animated in opposite directions rather than one direction [#1939](https://github.com/terrastruct/d2/pull/1939)
#### Bugfixes ⛑️ #### Bugfixes ⛑️
- Fixes edge case of bad import syntax crashing using d2 as a library [1829](https://github.com/terrastruct/d2/pull/1829) - Local relative icons are relative to the d2 file instead of CLI invoke path [#1924](https://github.com/terrastruct/d2/pull/1924)
- Custom label positions weren't being read when the width was smaller than the label [#1928](https://github.com/terrastruct/d2/pull/1928)
- Using `shape: circle` for arrowheads no longer removes all arrowheads along path in sketch mode [#1942](https://github.com/terrastruct/d2/pull/1942)
- Globs to null connections work [#1965](https://github.com/terrastruct/d2/pull/1965)
- Edge globs setting styles inherit correctly in child boards [#1967](https://github.com/terrastruct/d2/pull/1967)
- Board links imported with spread imports work [#1972](https://github.com/terrastruct/d2/pull/1972)

View file

@ -0,0 +1,19 @@
#### Features 🚀
- `style.underline` works on connections [#1836](https://github.com/terrastruct/d2/pull/1836)
- `none` is added as an accepted value for `fill-pattern`. Previously there was no way to cancel the `fill-pattern` on select objects set by a theme that applies it (Origami) [#1882](https://github.com/terrastruct/d2/pull/1882)
#### Improvements 🧹
- Dimensions can be set less than label dimensions [#1901](https://github.com/terrastruct/d2/pull/1901)
- Boards no longer inherit `label` fields from parents [#1838](https://github.com/terrastruct/d2/pull/1838)
- Prevents `near` targeting a child of a special object like grid cells, which wasn't doing anything [#1851](https://github.com/terrastruct/d2/pull/1851)
#### Bugfixes ⛑️
- Theme flags on CLI apply to PDFs [#1894](https://github.com/terrastruct/d2/pull/1894)
- Fixes styles in connections not overriding styles set by globs [#1857](https://github.com/terrastruct/d2/pull/1857)
- Fixes `null` being set on a nested shape not working in certain cases when connections also pointed to that shape [#1830](https://github.com/terrastruct/d2/pull/1830)
- Fixes edge case of bad import syntax crashing using d2 as a library [#1829](https://github.com/terrastruct/d2/pull/1829)
- Fixes `style.fill` not applying to markdown [#1872](https://github.com/terrastruct/d2/pull/1872)
- Fixes compiler erroring on certain styles when the shape's `shape` value is not all lowercase (e.g. `Circle`) [#1887](https://github.com/terrastruct/d2/pull/1887)

View file

@ -0,0 +1,7 @@
D2 0.6.5 has a hotfix for 0.6.4 breaking plugin compatibility. Also includes 2 compiler fixes regarding substitutions/vars.
#### Bugfixes ⛑️
- Fix executable plugins that implement standalone router [#1910](https://github.com/terrastruct/d2/pull/1910)
- Fix compiler error with multiple nested spread substitutions [#1913](https://github.com/terrastruct/d2/pull/1913)
- Fix substitutions from imports into different scopes [#1914](https://github.com/terrastruct/d2/pull/1914)

View file

@ -862,9 +862,6 @@ func (mk *Key) HasTripleGlob() bool {
return true return true
} }
} }
if mk.EdgeIndex != nil && mk.EdgeIndex.Glob {
return true
}
if mk.EdgeKey.HasTripleGlob() { if mk.EdgeKey.HasTripleGlob() {
return true return true
} }
@ -1025,6 +1022,7 @@ type EdgeIndex struct {
} }
func (ei1 *EdgeIndex) Equals(ei2 *EdgeIndex) bool { func (ei1 *EdgeIndex) Equals(ei2 *EdgeIndex) bool {
// TODO probably should be checking the values, but will wait until something breaks to change
if ei1.Int != ei2.Int { if ei1.Int != ei2.Int {
return false return false
} }

View file

@ -146,6 +146,10 @@ func (gs *dslGenState) edge() error {
} }
} }
if src == dst && gs.nodeShapes[dst] == d2target.ShapeSequenceDiagram {
return nil
}
srcArrow := "-" srcArrow := "-"
if gs.randBool() { if gs.randBool() {
srcArrow = "<" srcArrow = "<"
@ -265,7 +269,7 @@ func (gs *dslGenState) randStr(n int, inKey bool) string {
func (gs *dslGenState) randShape() string { func (gs *dslGenState) randShape() string {
for { for {
s := shapes[gs.rand.Intn(len(shapes))] s := shapes[gs.rand.Intn(len(shapes))]
if s != d2target.ShapeImage { if s != d2target.ShapeImage && s != d2target.ShapeText {
return s return s
} }
} }

View file

@ -459,7 +459,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
if os.Getenv("D2_LSP_MODE") == "1" { if os.Getenv("D2_LSP_MODE") == "1" {
// only the parse result is needed if running d2 for lsp, // only the parse result is needed if running d2 for lsp,
// if this, "fails", the AST is still valid and can be sent // if this, "fails", the AST is still valid and can be sent
// to vscode extention // to vscode extension
ast, err := d2lib.Parse(ctx, string(input), opts) ast, err := d2lib.Parse(ctx, string(input), opts)
type LspOutputData struct { type LspOutputData struct {
@ -529,7 +529,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
ext := getExportExtension(outputPath) ext := getExportExtension(outputPath)
switch ext { switch ext {
case GIF: case GIF:
svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, diagram) svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, inputPath, diagram)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -553,7 +553,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
path := []pdf.BoardTitle{ path := []pdf.BoardTitle{
{Name: diagram.Root.Label, BoardID: "root"}, {Name: diagram.Root.Label, BoardID: "root"},
} }
pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, path, pageMap, diagram.Root.Label != "") pdf, err := renderPDF(ctx, ms, plugin, renderOpts, inputPath, outputPath, page, ruler, diagram, nil, path, pageMap, diagram.Root.Label != "")
if err != nil { if err != nil {
return pdf, false, err return pdf, false, err
} }
@ -574,7 +574,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
path := []pptx.BoardTitle{ path := []pptx.BoardTitle{
{Name: "root", BoardID: "root", LinkToSlide: boardIdToIndex["root"] + 1}, {Name: "root", BoardID: "root", LinkToSlide: boardIdToIndex["root"] + 1},
} }
svg, err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, outputPath, page, diagram, path, boardIdToIndex) svg, err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, inputPath, outputPath, page, diagram, path, boardIdToIndex)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -808,7 +808,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
start := time.Now() start := time.Now()
out, err := _render(ctx, ms, plugin, opts, boardOutputPath, bundle, forceAppendix, page, ruler, diagram) out, err := _render(ctx, ms, plugin, opts, inputPath, boardOutputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil { if err != nil {
return boards, err return boards, err
} }
@ -824,7 +824,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) { func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) {
start := time.Now() start := time.Now()
out, err := _render(ctx, ms, plugin, opts, outputPath, bundle, forceAppendix, page, ruler, diagram) out, err := _render(ctx, ms, plugin, opts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil { if err != nil {
return [][]byte{}, err return [][]byte{}, err
} }
@ -835,7 +835,7 @@ func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration
return [][]byte{out}, nil return [][]byte{out}, nil
} }
func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) {
toPNG := getExportExtension(outputPath) == PNG toPNG := getExportExtension(outputPath) == PNG
var scale *float64 var scale *float64
if opts.Scale != nil { if opts.Scale != nil {
@ -865,7 +865,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
if bundle { if bundle {
var bundleErr2 error var bundleErr2 error
svg, bundleErr2 = imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 = imgbundler.BundleRemote(ctx, l, svg, cacheImages)
@ -915,7 +915,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
return svg, nil return svg, nil
} }
func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, doc *pdf.GoFPDF, boardPath []pdf.BoardTitle, pageMap map[string]int, includeNav bool) (svg []byte, err error) { func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, doc *pdf.GoFPDF, boardPath []pdf.BoardTitle, pageMap map[string]int, includeNav bool) (svg []byte, err error) {
var isRoot bool var isRoot bool
if doc == nil { if doc == nil {
doc = pdf.Init() doc = pdf.Init()
@ -940,6 +940,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Sketch: opts.Sketch, Sketch: opts.Sketch,
Center: opts.Center, Center: opts.Center,
Scale: scale, Scale: scale,
ThemeID: opts.ThemeID,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -952,7 +953,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -985,7 +986,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -995,7 +996,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1005,7 +1006,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1021,7 +1022,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
return svg, nil return svg, nil
} }
func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []pptx.BoardTitle, boardIDToIndex map[string]int) ([]byte, error) { func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, inputPath, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []pptx.BoardTitle, boardIDToIndex map[string]int) ([]byte, error) {
var svg []byte var svg []byte
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
// gofpdf will print the png img with a slight filter // gofpdf will print the png img with a slight filter
@ -1054,7 +1055,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -1119,7 +1120,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1131,7 +1132,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1143,7 +1144,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1275,7 +1276,7 @@ func buildBoardIDToIndex(diagram *d2target.Diagram, dictionary map[string]int, p
return dictionary return dictionary
} }
func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) { func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, inputPath string, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) {
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
var scale *float64 var scale *float64
@ -1301,7 +1302,7 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -1318,21 +1319,21 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
} }
for _, dl := range diagram.Layers { for _, dl := range diagram.Layers {
_, layerPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, layerPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pngs = append(pngs, layerPNGs...) pngs = append(pngs, layerPNGs...)
} }
for _, dl := range diagram.Scenarios { for _, dl := range diagram.Scenarios {
_, scenarioPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, scenarioPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pngs = append(pngs, scenarioPNGs...) pngs = append(pngs, scenarioPNGs...)
} }
for _, dl := range diagram.Steps { for _, dl := range diagram.Steps {
_, stepsPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, stepsPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -69,6 +69,7 @@ func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
g := d2graph.NewGraph() g := d2graph.NewGraph()
g.AST = ast g.AST = ast
g.BaseAST = ast
c.compileBoard(g, m) c.compileBoard(g, m)
if len(c.err.Errors) > 0 { if len(c.err.Errors) > 0 {
return nil, c.err return nil, c.err
@ -90,6 +91,7 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
c.validateLabels(g) c.validateLabels(g)
c.validateNear(g) c.validateNear(g)
c.validateEdges(g) c.validateEdges(g)
c.validatePositionsCompatibility(g)
c.compileBoardsField(g, ir, "layers") c.compileBoardsField(g, ir, "layers")
c.compileBoardsField(g, ir, "scenarios") c.compileBoardsField(g, ir, "scenarios")
@ -121,7 +123,7 @@ func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName
g2 := d2graph.NewGraph() g2 := d2graph.NewGraph()
g2.Parent = g g2.Parent = g
g2.AST = f.Map().AST().(*d2ast.Map) g2.AST = f.Map().AST().(*d2ast.Map)
g2.BaseAST = findFieldAST(g.AST, f) g2.BaseAST = findFieldAST(g.BaseAST, f)
c.compileBoard(g2, f.Map()) c.compileBoard(g2, f.Map())
g2.Name = f.Name g2.Name = f.Name
switch fieldName { switch fieldName {
@ -337,11 +339,11 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
} }
if obj.Parent != nil { if obj.Parent != nil {
if obj.Parent.Shape.Value == d2target.ShapeSQLTable { if strings.EqualFold(obj.Parent.Shape.Value, d2target.ShapeSQLTable) {
c.errorf(f.LastRef().AST(), "sql_table columns cannot have children") c.errorf(f.LastRef().AST(), "sql_table columns cannot have children")
return return
} }
if obj.Parent.Shape.Value == d2target.ShapeClass { if strings.EqualFold(obj.Parent.Shape.Value, d2target.ShapeClass) {
c.errorf(f.LastRef().AST(), "class fields cannot have children") c.errorf(f.LastRef().AST(), "class fields cannot have children")
return return
} }
@ -510,7 +512,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
return return
} }
attrs.Shape.Value = scalar.ScalarString() attrs.Shape.Value = scalar.ScalarString()
if attrs.Shape.Value == d2target.ShapeCode { if strings.EqualFold(attrs.Shape.Value, d2target.ShapeCode) {
// Explicit code shape is plaintext. // Explicit code shape is plaintext.
attrs.Language = d2target.ShapeText attrs.Language = d2target.ShapeText
} }
@ -1008,12 +1010,12 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
} }
} }
if obj.Style.DoubleBorder != nil { if obj.Style.DoubleBorder != nil {
if obj.Shape.Value != "" && obj.Shape.Value != d2target.ShapeSquare && obj.Shape.Value != d2target.ShapeRectangle && obj.Shape.Value != d2target.ShapeCircle && obj.Shape.Value != d2target.ShapeOval { if obj.Shape.Value != "" && !strings.EqualFold(obj.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeCircle) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeOval) {
c.errorf(obj.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`) c.errorf(obj.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`)
} }
} }
case "shape": case "shape":
if obj.Shape.Value == d2target.ShapeImage && obj.Icon == nil { if strings.EqualFold(obj.Shape.Value, d2target.ShapeImage) && obj.Icon == nil {
c.errorf(f.LastPrimaryKey(), `image shape must include an "icon" field`) c.errorf(f.LastPrimaryKey(), `image shape must include an "icon" field`)
} }
@ -1023,14 +1025,14 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Shape.Value)) c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Shape.Value))
} }
case "constraint": case "constraint":
if obj.Shape.Value != d2target.ShapeSQLTable { if !strings.EqualFold(obj.Shape.Value, d2target.ShapeSQLTable) {
c.errorf(f.LastPrimaryKey(), `"constraint" keyword can only be used in "sql_table" shapes`) c.errorf(f.LastPrimaryKey(), `"constraint" keyword can only be used in "sql_table" shapes`)
} }
} }
return return
} }
if obj.Shape.Value == d2target.ShapeImage { if strings.EqualFold(obj.Shape.Value, d2target.ShapeImage) {
c.errorf(f.LastRef().AST(), "image shapes cannot have children.") c.errorf(f.LastRef().AST(), "image shapes cannot have children.")
return return
} }
@ -1043,7 +1045,7 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
func (c *compiler) validateLabels(g *d2graph.Graph) { func (c *compiler) validateLabels(g *d2graph.Graph) {
for _, obj := range g.Objects { for _, obj := range g.Objects {
if obj.Shape.Value != d2target.ShapeText { if !strings.EqualFold(obj.Shape.Value, d2target.ShapeText) {
continue continue
} }
if obj.Attributes.Language != "" { if obj.Attributes.Language != "" {
@ -1097,6 +1099,14 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
continue continue
} }
} }
if nearObj.ClosestGridDiagram() != nil {
c.errorf(obj.NearKey, "near keys cannot be set to descendants of special objects, like grid cells")
continue
}
if nearObj.OuterSequenceDiagram() != nil {
c.errorf(obj.NearKey, "near keys cannot be set to descendants of special objects, like sequence diagram actors")
continue
}
} else if isConst { } else if isConst {
if obj.Parent != g.Root { if obj.Parent != g.Root {
c.errorf(obj.NearKey, "constant near keys can only be set on root level shapes") c.errorf(obj.NearKey, "constant near keys can only be set on root level shapes")
@ -1122,6 +1132,26 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
} }
func (c *compiler) validatePositionsCompatibility(g *d2graph.Graph) {
for _, o := range g.Objects {
for _, pos := range []*d2graph.Scalar{o.Top, o.Left} {
if pos != nil {
if o.Parent != nil {
if strings.EqualFold(o.Parent.Shape.Value, d2target.ShapeHierarchy) {
c.errorf(pos.MapKey, `position keywords cannot be used with shape "hierarchy"`)
}
if o.OuterSequenceDiagram() != nil {
c.errorf(pos.MapKey, `position keywords cannot be used inside shape "sequence_diagram"`)
}
if o.Parent.GridColumns != nil || o.Parent.GridRows != nil {
c.errorf(pos.MapKey, `position keywords cannot be used with grids`)
}
}
}
}
}
}
func (c *compiler) validateEdges(g *d2graph.Graph) { func (c *compiler) validateEdges(g *d2graph.Graph) {
for _, edge := range g.Edges { for _, edge := range g.Edges {
// edges from a grid to something outside is ok // edges from a grid to something outside is ok

View file

@ -10,6 +10,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/util-go/mapfs"
"oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2compiler"
"oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2format"
@ -23,6 +24,8 @@ func TestCompile(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
text string text string
// For tests that use imports, define `index.d2` as text and other files here
files map[string]string
expErr string expErr string
assertions func(t *testing.T, g *d2graph.Graph) assertions func(t *testing.T, g *d2graph.Graph)
@ -259,7 +262,7 @@ containers: {
} }
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/invalid-fill-pattern.d2:3:19: expected "fill-pattern" to be one of: dots, lines, grain, paper`, expErr: `d2/testdata/d2compiler/TestCompile/invalid-fill-pattern.d2:3:19: expected "fill-pattern" to be one of: none, dots, lines, grain, paper`,
}, },
{ {
name: "shape_unquoted_hex", name: "shape_unquoted_hex",
@ -1607,6 +1610,17 @@ d2/testdata/d2compiler/TestCompile/near-invalid.d2:14:9: near keys cannot be set
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:9: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`, expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:9: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`,
}, },
{
name: "near_special",
text: `x.near: z.x
z: {
grid-rows: 1
x
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/near_special.d2:1:9: near keys cannot be set to descendants of special objects, like grid cells`,
},
{ {
name: "near_bad_connected", name: "near_bad_connected",
@ -2837,15 +2851,96 @@ y.source-arrowhead.shape: cf-one
expErr: `d2/testdata/d2compiler/TestCompile/no_arrowheads_in_shape.d2:1:3: "target-arrowhead" can only be used on connections expErr: `d2/testdata/d2compiler/TestCompile/no_arrowheads_in_shape.d2:1:3: "target-arrowhead" can only be used on connections
d2/testdata/d2compiler/TestCompile/no_arrowheads_in_shape.d2:2:3: "source-arrowhead" can only be used on connections`, d2/testdata/d2compiler/TestCompile/no_arrowheads_in_shape.d2:2:3: "source-arrowhead" can only be used on connections`,
}, },
{
name: "shape-hierarchy",
text: `x: {
shape: hierarchy
a -> b
}
`,
},
{
name: "fixed-pos-shape-hierarchy",
text: `x: {
shape: hierarchy
a -> b
a.top: 20
a.left: 20
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/fixed-pos-shape-hierarchy.d2:4:2: position keywords cannot be used with shape "hierarchy"
d2/testdata/d2compiler/TestCompile/fixed-pos-shape-hierarchy.d2:5:2: position keywords cannot be used with shape "hierarchy"`,
},
{
name: "vars-in-imports",
text: `dev: {
vars: {
env: Dev
}
...@template.d2
}
qa: {
vars: {
env: Qa
}
...@template.d2
}
`,
files: map[string]string{
"template.d2": `env: {
label: ${env} Environment
vm: {
label: My Virtual machine!
}
}`,
},
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, "dev.env", g.Objects[1].AbsID())
tassert.Equal(t, "Dev Environment", g.Objects[1].Label.Value)
tassert.Equal(t, "qa.env", g.Objects[2].AbsID())
tassert.Equal(t, "Qa Environment", g.Objects[2].Label.Value)
},
},
{
name: "spread-import-link",
text: `k
layers: {
x: {...@x}
}`,
files: map[string]string{
"x.d2": `a.link: layers.b
layers: {
b: {
d
}
}`,
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
opts := &d2compiler.CompileOptions{}
if tc.files != nil {
tc.files["index.d2"] = tc.text
renamed := make(map[string]string)
for file, content := range tc.files {
renamed[fmt.Sprintf("d2/testdata/d2compiler/TestCompile/%v", file)] = content
}
fs, err := mapfs.New(renamed)
assert.Success(t, err)
t.Cleanup(func() {
err = fs.Close()
assert.Success(t, err)
})
opts.FS = fs
}
d2Path := fmt.Sprintf("d2/testdata/d2compiler/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2compiler/%v.d2", t.Name())
g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil) g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), opts)
if tc.expErr != "" { if tc.expErr != "" {
if err == nil { if err == nil {
t.Fatalf("expected error with: %q", tc.expErr) t.Fatalf("expected error with: %q", tc.expErr)
@ -2991,6 +3086,22 @@ steps: {
assert.True(t, g.IsFolderOnly) assert.True(t, g.IsFolderOnly)
}, },
}, },
{
name: "no-inherit-label",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
label: hi
steps: {
1: {
RJ
}
}
`, "")
assert.True(t, g.Root.Label.MapKey != nil)
assert.True(t, g.Steps[0].Root.Label.MapKey == nil)
},
},
{ {
name: "scenarios_edge_index", name: "scenarios_edge_index",
run: func(t *testing.T) { run: func(t *testing.T) {
@ -3239,6 +3350,17 @@ y: null
assert.Equal(t, 0, len(g.Edges)) assert.Equal(t, 0, len(g.Edges))
}, },
}, },
{
name: "delete-nested-connection",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
a -> b.c
b.c: null
`, "")
assert.Equal(t, 2, len(g.Objects))
assert.Equal(t, 0, len(g.Edges))
},
},
{ {
name: "delete-multiple-connections", name: "delete-multiple-connections",
run: func(t *testing.T) { run: func(t *testing.T) {
@ -3424,6 +3546,24 @@ hi: "1 ${x} 2"
assert.Equal(t, "1 im a var 2", g.Objects[0].Label.Value) assert.Equal(t, "1 im a var 2", g.Objects[0].Label.Value)
}, },
}, },
{
name: "double-border",
run: func(t *testing.T) {
assertCompile(t, `
a.shape: Circle
a.style.double-border: true
`, "")
},
},
{
name: "invalid-double-border",
run: func(t *testing.T) {
assertCompile(t, `
a.shape: hexagon
a.style.double-border: true
`, `d2/testdata/d2compiler/TestCompile2/vars/basic/invalid-double-border.d2:3:1: key "double-border" can only be applied to squares, rectangles, circles, ovals`)
},
},
{ {
name: "single-quoted", name: "single-quoted",
run: func(t *testing.T) { run: func(t *testing.T) {
@ -4329,6 +4469,29 @@ container_2: {
assert.Equal(t, 4, len(g.Objects)) assert.Equal(t, 4, len(g.Objects))
}, },
}, },
{
name: "override-edge/1",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
(* -> *)[*].style.stroke: red
(* -> *)[*].style.stroke: green
a -> b
`, ``)
assert.Equal(t, "green", g.Edges[0].Attributes.Style.Stroke.Value)
},
},
{
name: "override-edge/2",
run: func(t *testing.T) {
g, _ := assertCompile(t, `
(* -> *)[*].style.stroke: red
a -> b: {style.stroke: green}
a -> b
`, ``)
assert.Equal(t, "green", g.Edges[0].Attributes.Style.Stroke.Value)
assert.Equal(t, "red", g.Edges[1].Attributes.Style.Stroke.Value)
},
},
} }
for _, tc := range tca { for _, tc := range tca {

View file

@ -345,6 +345,9 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
if edge.Style.Bold != nil { if edge.Style.Bold != nil {
connection.Bold, _ = strconv.ParseBool(edge.Style.Bold.Value) connection.Bold, _ = strconv.ParseBool(edge.Style.Bold.Value)
} }
if edge.Style.Underline != nil {
connection.Underline, _ = strconv.ParseBool(edge.Style.Underline.Value)
}
if theme != nil && theme.SpecialRules.Mono { if theme != nil && theme.SpecialRules.Mono {
connection.FontFamily = "mono" connection.FontFamily = "mono"
} }

View file

@ -470,7 +470,7 @@ func (obj *Object) GetFill() string {
return color.N7 return color.N7
} }
if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) { if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) || strings.EqualFold(shape, d2target.ShapeHierarchy) {
if level == 1 { if level == 1 {
if !obj.IsContainer() { if !obj.IsContainer() {
return color.B6 return color.B6
@ -1048,15 +1048,6 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
// resizes the object to fit content of the given width and height in its inner box with the given padding. // resizes the object to fit content of the given width and height in its inner box with the given padding.
// this accounts for the shape of the object, and if there is a desired width or height set for the object // this accounts for the shape of the object, and if there is a desired width or height set for the object
func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY float64) { func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY float64) {
var desiredWidth int
var desiredHeight int
if obj.WidthAttr != nil {
desiredWidth, _ = strconv.Atoi(obj.WidthAttr.Value)
}
if obj.HeightAttr != nil {
desiredHeight, _ = strconv.Atoi(obj.HeightAttr.Value)
}
dslShape := strings.ToLower(obj.Shape.Value) dslShape := strings.ToLower(obj.Shape.Value)
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape] shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape]
s := shape.NewShape(shapeType, geo.NewBox(geo.NewPoint(0, 0), contentWidth, contentHeight)) s := shape.NewShape(shapeType, geo.NewBox(geo.NewPoint(0, 0), contentWidth, contentHeight))
@ -1068,8 +1059,28 @@ func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY
} else { } else {
fitWidth, fitHeight = s.GetDimensionsToFit(contentWidth, contentHeight, paddingX, paddingY) fitWidth, fitHeight = s.GetDimensionsToFit(contentWidth, contentHeight, paddingX, paddingY)
} }
var desiredWidth int
if obj.WidthAttr != nil {
desiredWidth, _ = strconv.Atoi(obj.WidthAttr.Value)
obj.Width = float64(desiredWidth)
} else {
obj.Width = fitWidth
}
var desiredHeight int
if obj.HeightAttr != nil {
desiredHeight, _ = strconv.Atoi(obj.HeightAttr.Value)
obj.Height = float64(desiredHeight)
} else {
obj.Height = fitHeight
}
if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" {
obj.Width = math.Max(float64(desiredWidth), fitWidth) obj.Width = math.Max(float64(desiredWidth), fitWidth)
obj.Height = math.Max(float64(desiredHeight), fitHeight) obj.Height = math.Max(float64(desiredHeight), fitHeight)
}
if s.AspectRatio1() { if s.AspectRatio1() {
sideLength := math.Max(obj.Width, obj.Height) sideLength := math.Max(obj.Width, obj.Height)
obj.Width = sideLength obj.Width = sideLength
@ -1816,6 +1827,7 @@ var LabelPositionsMapping = map[string]label.Position{
} }
var FillPatterns = []string{ var FillPatterns = []string{
"none",
"dots", "dots",
"lines", "lines",
"grain", "grain",

View file

@ -28,7 +28,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error {
} }
var root Object var root Object
convert(sg.Root, &root) Convert(sg.Root, &root)
g.Root = &root g.Root = &root
root.Graph = g root.Graph = g
g.RootLevel = sg.RootLevel g.RootLevel = sg.RootLevel
@ -38,7 +38,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error {
var objects []*Object var objects []*Object
for _, so := range sg.Objects { for _, so := range sg.Objects {
var o Object var o Object
if err := convert(so, &o); err != nil { if err := Convert(so, &o); err != nil {
return err return err
} }
o.Graph = g o.Graph = g
@ -67,7 +67,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error {
var edges []*Edge var edges []*Edge
for _, se := range sg.Edges { for _, se := range sg.Edges {
var e Edge var e Edge
if err := convert(se, &e); err != nil { if err := Convert(se, &e); err != nil {
return err return err
} }
@ -108,7 +108,7 @@ func SerializeGraph(g *Graph) ([]byte, error) {
var sedges []SerializedEdge var sedges []SerializedEdge
for _, e := range g.Edges { for _, e := range g.Edges {
se, err := toSerializedEdge(e) se, err := ToSerializedEdge(e)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,7 +121,7 @@ func SerializeGraph(g *Graph) ([]byte, error) {
func toSerializedObject(o *Object) (SerializedObject, error) { func toSerializedObject(o *Object) (SerializedObject, error) {
var so SerializedObject var so SerializedObject
if err := convert(o, &so); err != nil { if err := Convert(o, &so); err != nil {
return nil, err return nil, err
} }
@ -138,9 +138,9 @@ func toSerializedObject(o *Object) (SerializedObject, error) {
return so, nil return so, nil
} }
func toSerializedEdge(e *Edge) (SerializedEdge, error) { func ToSerializedEdge(e *Edge) (SerializedEdge, error) {
var se SerializedEdge var se SerializedEdge
if err := convert(e, &se); err != nil { if err := Convert(e, &se); err != nil {
return nil, err return nil, err
} }
@ -154,7 +154,7 @@ func toSerializedEdge(e *Edge) (SerializedEdge, error) {
return se, nil return se, nil
} }
func convert[T, Q any](from T, to *Q) error { func Convert[T, Q any](from T, to *Q) error {
b, err := json.Marshal(from) b, err := json.Marshal(from)
if err != nil { if err != nil {
return err return err

View file

@ -31,8 +31,7 @@ type compiler struct {
imports []string imports []string
// importStack is used to detect cyclic imports. // importStack is used to detect cyclic imports.
importStack []string importStack []string
// importCache enables reuse of files imported multiple times. seenImports map[string]struct{}
importCache map[string]*Map
utf16Pos bool utf16Pos bool
// Stack of globs that must be recomputed at each new object in and below the current scope. // Stack of globs that must be recomputed at each new object in and below the current scope.
@ -62,7 +61,7 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, []string, error) {
err: &d2parser.ParseError{}, err: &d2parser.ParseError{},
fs: opts.FS, fs: opts.FS,
importCache: make(map[string]*Map), seenImports: make(map[string]struct{}),
utf16Pos: opts.UTF16Pos, utf16Pos: opts.UTF16Pos,
} }
m := &Map{} m := &Map{}
@ -127,14 +126,21 @@ func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
varsStack = append([]*Map{f.Map()}, varsStack...) varsStack = append([]*Map{f.Map()}, varsStack...)
} }
} }
for _, f := range m.Fields { for i := 0; i < len(m.Fields); i++ {
f := m.Fields[i]
if f.Primary() != nil { if f.Primary() != nil {
c.resolveSubstitutions(varsStack, f) removed := c.resolveSubstitutions(varsStack, f)
if removed {
i--
}
} }
if arr, ok := f.Composite.(*Array); ok { if arr, ok := f.Composite.(*Array); ok {
for _, val := range arr.Values { for _, val := range arr.Values {
if scalar, ok := val.(*Scalar); ok { if scalar, ok := val.(*Scalar); ok {
c.resolveSubstitutions(varsStack, scalar) removed := c.resolveSubstitutions(varsStack, scalar)
if removed {
i--
}
} }
} }
} else if f.Map() != nil { } else if f.Map() != nil {
@ -213,7 +219,7 @@ func (c *compiler) validateConfigs(configs *Field) {
} }
} }
func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) { func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedField bool) {
var subbed bool var subbed bool
var resolvedField *Field var resolvedField *Field
@ -264,6 +270,7 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) {
for i, f2 := range m.Fields { for i, f2 := range m.Fields {
if n == f2 { if n == f2 {
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...) m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
removedField = true
break break
} }
} }
@ -334,6 +341,7 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) {
s.Coalesce() s.Coalesce()
} }
} }
return removedField
} }
func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field { func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
@ -361,6 +369,9 @@ func (c *compiler) overlay(base *Map, f *Field) {
return return
} }
base = base.CopyBase(f) base = base.CopyBase(f)
// Certain fields should never carry forward.
// If you give your scenario a label, you don't want all steps in a scenario to be labeled the same.
base.DeleteField("label")
OverlayMap(base, f.Map()) OverlayMap(base, f.Map())
f.Composite = base f.Composite = base
} }
@ -381,6 +392,9 @@ func (g *globContext) prefixed(dst *Map) *globContext {
if len(prefix.Path) > 0 { if len(prefix.Path) > 0 {
g2.refctx.Key.Key = prefix g2.refctx.Key.Key = prefix
} }
if !g2.refctx.Key.HasTripleGlob() && g2.refctx.Key.EdgeKey != nil {
prefix.Path = append(prefix.Path, g2.refctx.Key.EdgeKey.Path...)
}
return g2 return g2
} }
@ -410,6 +424,7 @@ func (c *compiler) ampersandFilterMap(dst *Map, ast, scopeAST *d2ast.Map) bool {
ks = d2format.Format(d2ast.MakeKeyPath(BoardIDA(dst))) ks = d2format.Format(d2ast.MakeKeyPath(BoardIDA(dst)))
} }
delete(gctx.appliedFields, ks) delete(gctx.appliedFields, ks)
delete(gctx.appliedEdges, ks)
return false return false
} }
} }
@ -497,6 +512,7 @@ func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
} }
OverlayMap(dst, impn.Map()) OverlayMap(dst, impn.Map())
c.updateLinks(dst)
if impnf, ok := impn.(*Field); ok { if impnf, ok := impn.(*Field); ok {
if impnf.Primary_ != nil { if impnf.Primary_ != nil {
@ -979,7 +995,7 @@ func (c *compiler) compileEdges(refctx *RefContext) {
func (c *compiler) _compileEdges(refctx *RefContext) { 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 !eid.Glob && (refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil) {
refctx.ScopeMap.DeleteEdge(eid) refctx.ScopeMap.DeleteEdge(eid)
continue continue
} }
@ -997,6 +1013,10 @@ func (c *compiler) _compileEdges(refctx *RefContext) {
continue continue
} }
for _, e := range ea { for _, e := range ea {
if refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil {
refctx.ScopeMap.DeleteEdge(e.ID)
continue
}
e.References = append(e.References, &EdgeReference{ e.References = append(e.References, &EdgeReference{
Context_: refctx, Context_: refctx,
DueToGlob_: len(c.globRefContextStack) > 0, DueToGlob_: len(c.globRefContextStack) > 0,

View file

@ -950,14 +950,21 @@ func (m *Map) DeleteField(ida ...string) *Field {
} }
if len(rest) == 0 { if len(rest) == 0 {
for _, fr := range f.References { for _, fr := range f.References {
for _, e := range m.Edges { currM := m
for currM != nil {
for _, e := range currM.Edges {
for _, er := range e.References { for _, er := range e.References {
if er.Context_ == fr.Context_ { if er.Context_ == fr.Context_ {
m.DeleteEdge(e.ID) currM.DeleteEdge(e.ID)
break break
} }
} }
} }
if NodeBoardKind(currM) != "" {
break
}
currM = ParentMap(currM)
}
} }
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...) m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
@ -1087,7 +1094,7 @@ func (m *Map) getEdges(eid *EdgeID, refctx *RefContext, gctx *globContext, ea *[
} }
gctx.appliedEdges[ks] = struct{}{} gctx.appliedEdges[ks] = struct{}{}
} }
*ea = append(*ea, ea2...) *ea = append(*ea, e)
} }
} }
} }
@ -1348,11 +1355,8 @@ func (m *Map) AST() d2ast.Node {
if m == nil { if m == nil {
return nil return nil
} }
astMap := &d2ast.Map{} astMap := &d2ast.Map{
if m.Root() { Range: d2ast.MakeRange(",0:0:0-1:0:0"),
astMap.Range = d2ast.MakeRange(",0:0:0-1:0:0")
} else {
astMap.Range = d2ast.MakeRange(",1:0:0-2:0:0")
} }
for _, f := range m.Fields { for _, f := range m.Fields {
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.AST().(d2ast.MapNode))) astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.AST().(d2ast.MapNode)))

View file

@ -150,6 +150,21 @@ x -> y
assertQuery(t, m, 0, 0, 0.1, "(x -> y)[1].style.opacity") assertQuery(t, m, 0, 0, 0.1, "(x -> y)[1].style.opacity")
}, },
}, },
{
name: "label-filter/3",
run: func(t testing.TB) {
m, err := compile(t, `
(* -> *)[*]: {
&label: hi
style.opacity: 0.1
}
x -> y: hi
`)
assert.Success(t, err)
assertQuery(t, m, 0, 0, 0.1, "(x -> y)[0].style.opacity")
},
},
{ {
name: "lazy-filter", name: "lazy-filter",
run: func(t testing.TB) { run: func(t testing.TB) {

View file

@ -82,16 +82,11 @@ func (c *compiler) __import(imp *d2ast.Import) (*Map, bool) {
// Only get immediate imports. // Only get immediate imports.
if len(c.importStack) == 2 { if len(c.importStack) == 2 {
if _, ok := c.importCache[impPath]; !ok { if _, ok := c.seenImports[impPath]; !ok {
c.imports = append(c.imports, imp.PathWithPre()) c.imports = append(c.imports, imp.PathWithPre())
} }
} }
ir, ok := c.importCache[impPath]
if ok {
return ir, true
}
var f fs.File var f fs.File
var err error var err error
if c.fs == nil { if c.fs == nil {
@ -113,13 +108,13 @@ func (c *compiler) __import(imp *d2ast.Import) (*Map, bool) {
return nil, false return nil, false
} }
ir = &Map{} ir := &Map{}
ir.initRoot() ir.initRoot()
ir.parent.(*Field).References[0].Context_.Scope = ast ir.parent.(*Field).References[0].Context_.Scope = ast
c.compileMap(ir, ast, ast) c.compileMap(ir, ast, ast)
c.importCache[impPath] = ir c.seenImports[impPath] = struct{}{}
return ir, true return ir, true
} }

View file

@ -232,7 +232,7 @@ label: meow`,
_, err := compileFS(t, "index.d2", map[string]string{ _, err := compileFS(t, "index.d2", map[string]string{
"index.d2": "...@'./../x.d2'", "index.d2": "...@'./../x.d2'",
}) })
assert.ErrorString(t, err, `index.d2:1:1: failed to import "../x.d2": stat ../x.d2: invalid argument`) assert.ErrorString(t, err, `index.d2:1:1: failed to import "../x.d2": open ../x.d2: invalid argument`)
}, },
}, },
{ {

View file

@ -310,6 +310,79 @@ layers.x: { wrapper.p }
assertQuery(t, m, 0, 0, nil, "layers.x.wrapper.p") assertQuery(t, m, 0, 0, nil, "layers.x.wrapper.p")
}, },
}, },
{
name: "edge-glob-null",
run: func(t testing.TB) {
m, err := compile(t, `a -> b
(* -> *)[*]: null
x -> y
`)
assert.Success(t, err)
// 4 fields and 0 edges
assertQuery(t, m, 4, 0, nil, "")
},
},
{
name: "field-glob-style-inherit",
run: func(t testing.TB) {
m, err := compile(t, `*.style.opacity: 0
x: {
style.opacity: 1
}
scenarios: {
1: {
x
}
}
`)
assert.Success(t, err)
assertQuery(t, m, 0, 0, 1, "x.style.opacity")
assertQuery(t, m, 0, 0, 1, "scenarios.1.x.style.opacity")
},
},
{
name: "edge-glob-style-inherit/1",
run: func(t testing.TB) {
m, err := compile(t, `(* -> *)[*].style.opacity: 0
x -> y: {
style.opacity: 1
}
scenarios: {
1: {
x
}
}
`)
assert.Success(t, err)
assertQuery(t, m, 0, 0, 1, "(x -> y)[0].style.opacity")
assertQuery(t, m, 0, 0, 1, "scenarios.1.(x -> y)[0].style.opacity")
},
},
{
name: "edge-glob-style-inherit/2",
run: func(t testing.TB) {
m, err := compile(t, `*.style.opacity: 0
(* -> *)[*].style.opacity: 0
x -> y
steps: {
1: {
x.style.opacity: 1
}
2: {
(x -> y)[0].style.opacity: 1
}
3: {
y.style.opacity: 1
}
}
`)
assert.Success(t, err)
assertQuery(t, m, 0, 0, 1, "steps.3.(x -> y)[0].style.opacity")
},
},
{ {
name: "double-glob/edge/1", name: "double-glob/edge/1",
run: func(t testing.TB) { run: func(t testing.TB) {

View file

@ -570,6 +570,13 @@ func positionLabelsIcons(obj *d2graph.Object) {
} else { } else {
obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String()) obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
} }
if float64(obj.LabelDimensions.Width) > obj.Width || float64(obj.LabelDimensions.Height) > obj.Height {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
} else {
obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
}
}
} }
} }

View file

@ -1148,5 +1148,12 @@ func positionLabelsIcons(obj *d2graph.Object) {
} else { } else {
obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String()) obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
} }
if float64(obj.LabelDimensions.Width) > obj.Width || float64(obj.LabelDimensions.Height) > obj.Height {
if len(obj.ChildrenArray) > 0 {
obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
} else {
obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
}
}
} }
} }

View file

@ -199,7 +199,7 @@ func ReconnectEdge(g *d2graph.Graph, boardPath []string, edgeKey string, srcKey,
refs := edge.References refs := edge.References
if baseAST != g.AST { if baseAST != g.AST {
refs = getWriteableEdgeRefs(edge, baseAST) refs = GetWriteableEdgeRefs(edge, baseAST)
if len(refs) == 0 || refs[0].ScopeAST != baseAST { if len(refs) == 0 || refs[0].ScopeAST != baseAST {
// TODO null // TODO null
return nil, OutsideScopeError{} return nil, OutsideScopeError{}
@ -383,11 +383,11 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
break break
} }
obj = o obj = o
imported = IsImported(baseAST, obj) imported = IsImportedObj(baseAST, obj)
var maybeNewScope *d2ast.Map var maybeNewScope *d2ast.Map
if baseAST != g.AST || imported { if baseAST != g.AST || imported {
writeableRefs := getWriteableRefs(obj, baseAST) writeableRefs := GetWriteableRefs(obj, baseAST)
for _, ref := range writeableRefs { for _, ref := range writeableRefs {
if ref.MapKey != nil && ref.MapKey.Value.Map != nil { if ref.MapKey != nil && ref.MapKey.Value.Map != nil {
maybeNewScope = ref.MapKey.Value.Map maybeNewScope = ref.MapKey.Value.Map
@ -414,7 +414,7 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
writeableLabelMK := true writeableLabelMK := true
var objK *d2ast.Key var objK *d2ast.Key
if baseAST != g.AST || imported { if baseAST != g.AST || imported {
writeableRefs := getWriteableRefs(obj, baseAST) writeableRefs := GetWriteableRefs(obj, baseAST)
if len(writeableRefs) > 0 { if len(writeableRefs) > 0 {
objK = writeableRefs[0].MapKey objK = writeableRefs[0].MapKey
} }
@ -429,6 +429,19 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
break break
} }
} }
} else {
// Even if not imported or different board, a label can be not writeable if it's in a class or var or glob
// In those cases, the label is not a direct object reference
found := false
for _, ref := range obj.References {
if ref.MapKey == obj.Label.MapKey {
found = true
break
}
}
if !found {
writeableLabelMK = false
}
} }
var m *d2ast.Map var m *d2ast.Map
if objK != nil { if objK != nil {
@ -481,12 +494,17 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
if !ok { if !ok {
return errors.New("edge not found") return errors.New("edge not found")
} }
imported = IsImportedEdge(baseAST, edge)
refs := edge.References refs := edge.References
if baseAST != g.AST { if baseAST != g.AST || imported {
refs = getWriteableEdgeRefs(edge, baseAST) refs = GetWriteableEdgeRefs(edge, baseAST)
} }
onlyInChain := true onlyInChain := true
for _, ref := range refs { var earliestRef *d2graph.EdgeReference
for i, ref := range refs {
if earliestRef == nil || ref.MapKey.Range.Before(earliestRef.MapKey.Range) {
earliestRef = &refs[i]
}
// TODO merge flat edgekeys // TODO merge flat edgekeys
// E.g. this can group into a map // E.g. this can group into a map
// (y -> z)[0].style.opacity: 0.4 // (y -> z)[0].style.opacity: 0.4
@ -496,6 +514,8 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
onlyInChain = false onlyInChain = false
} }
} }
if ref.MapKey.EdgeIndex == nil || !ref.MapKey.EdgeIndex.Glob {
// If a ref has an exact match on this key, just change the value // If a ref has an exact match on this key, just change the value
tmp1 := *ref.MapKey tmp1 := *ref.MapKey
tmp2 := *mk tmp2 := *mk
@ -508,8 +528,17 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
return nil return nil
} }
} }
}
if onlyInChain { if onlyInChain {
if earliestRef != nil && scope.Range.Before(earliestRef.MapKey.Range) {
// Since the original mk was trimmed to common, we set to the edge that
// the ref's scope is in
mk.Edges[0] = earliestRef.Edge
// We can't reference an edge before it's been defined
earliestRef.Scope.InsertAfter(earliestRef.MapKey, mk)
} else {
appendMapKey(scope, mk) appendMapKey(scope, mk)
}
return nil return nil
} }
attrs = edge.Attributes attrs = edge.Attributes
@ -533,7 +562,7 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
foundMap = true foundMap = true
scope = ref.MapKey.Value.Map scope = ref.MapKey.Value.Map
for _, n := range scope.Nodes { for _, n := range scope.Nodes {
if n.MapKey.Value.Map == nil { if n.MapKey == nil || n.MapKey.Value.Map == nil {
continue continue
} }
if n.MapKey.Key == nil || len(n.MapKey.Key.Path) != 1 { if n.MapKey.Key == nil || len(n.MapKey.Key.Path) != 1 {
@ -574,6 +603,10 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
if s.MapKey.Range.Path != baseAST.Range.Path { if s.MapKey.Range.Path != baseAST.Range.Path {
return false return false
} }
// Globs are also not writeable
if s.MapKey.HasGlob() {
return false
}
} }
return s != nil && s.MapKey != nil && !ir.InClass(s.MapKey) return s != nil && s.MapKey != nil && !ir.InClass(s.MapKey)
} }
@ -646,14 +679,20 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
case "source-arrowhead", "target-arrowhead": case "source-arrowhead", "target-arrowhead":
var arrowhead *d2graph.Attributes var arrowhead *d2graph.Attributes
if reservedKey == "source-arrowhead" { if reservedKey == "source-arrowhead" {
if edge.SrcArrowhead != nil {
attrs = *edge.SrcArrowhead
}
arrowhead = edge.SrcArrowhead arrowhead = edge.SrcArrowhead
} else { } else {
if edge.DstArrowhead != nil {
attrs = *edge.DstArrowhead
}
arrowhead = edge.DstArrowhead arrowhead = edge.DstArrowhead
} }
if arrowhead != nil { if arrowhead != nil {
if reservedTargetKey == "" { if reservedTargetKey == "" {
if len(mk.Key.Path[reservedIndex:]) != 2 { if len(mk.Key.Path[reservedIndex:]) < 2 {
return errors.New("malformed style setting, expected 2 part path") return errors.New("malformed style setting, expected >= 2 part path")
} }
reservedTargetKey = mk.Key.Path[reservedIndex+1].Unbox().ScalarString() reservedTargetKey = mk.Key.Path[reservedIndex+1].Unbox().ScalarString()
} }
@ -668,6 +707,12 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
arrowhead.Label.MapKey.SetScalar(mk.Value.ScalarBox()) arrowhead.Label.MapKey.SetScalar(mk.Value.ScalarBox())
return nil return nil
} }
case "style":
reservedTargetKey = mk.Key.Path[len(mk.Key.Path)-1].Unbox().ScalarString()
if inlined(attrs.Style.Filled) {
attrs.Style.Filled.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
} }
} }
case "style": case "style":
@ -770,12 +815,23 @@ func _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string)
} }
} }
case "label": case "label":
if len(mk.Key.Path[reservedIndex:]) > 1 {
reservedTargetKey = mk.Key.Path[reservedIndex+1].Unbox().ScalarString()
switch reservedTargetKey {
case "near":
if inlined(attrs.LabelPosition) {
attrs.LabelPosition.MapKey.SetScalar(mk.Value.ScalarBox())
return nil
}
}
} else {
if inlined(&attrs.Label) { if inlined(&attrs.Label) {
attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox()) attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
return nil return nil
} }
} }
} }
}
} else if attrs.Label.MapKey != nil { } else if attrs.Label.MapKey != nil {
attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox()) attrs.Label.MapKey.SetScalar(mk.Value.ScalarBox())
return nil return nil
@ -846,7 +902,7 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
baseAST = boardG.BaseAST baseAST = boardG.BaseAST
} }
g2, err := deleteReserved(g, mk) g2, err := deleteReserved(g, boardPath, baseAST, mk)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -868,9 +924,15 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
return g, nil return g, nil
} }
imported := IsImportedEdge(baseAST, e)
if imported {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
appendMapKey(baseAST, mk)
} else {
refs := e.References refs := e.References
if len(boardPath) > 0 { if len(boardPath) > 0 {
refs := getWriteableEdgeRefs(e, baseAST) refs := GetWriteableEdgeRefs(e, baseAST)
if len(refs) != len(e.References) { if len(refs) != len(e.References) {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{}) mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
} }
@ -887,8 +949,11 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
for i := len(e.References) - 1; i >= 0; i-- { for i := len(e.References) - 1; i >= 0; i-- {
ref := e.References[i] ref := e.References[i]
// Leave glob setters alone
if !(ref.MapKey.EdgeIndex != nil && ref.MapKey.EdgeIndex.Glob) {
deleteEdge(g, ref.Scope, ref.MapKey, ref.MapKeyEdgeIndex) deleteEdge(g, ref.Scope, ref.MapKey, ref.MapKeyEdgeIndex)
} }
}
edges, ok := obj.FindEdges(mk) edges, ok := obj.FindEdges(mk)
if ok { if ok {
@ -908,6 +973,7 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
// NOTE: it only needs to be after the last ref, but perhaps simplest and cleanest to append all nulls at the end // NOTE: it only needs to be after the last ref, but perhaps simplest and cleanest to append all nulls at the end
appendMapKey(baseAST, mk) appendMapKey(baseAST, mk)
} }
}
if len(boardPath) > 0 { if len(boardPath) > 0 {
replaced := ReplaceBoardNode(g.AST, baseAST, boardPath) replaced := ReplaceBoardNode(g.AST, baseAST, boardPath)
if !replaced { if !replaced {
@ -918,17 +984,19 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
return recompile(boardG) return recompile(boardG)
} }
prevG, _ := recompile(boardG) prevG, err := recompile(boardG)
if err != nil {
return nil, err
}
obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key)) obj, ok := boardG.Root.HasChild(d2graph.Key(mk.Key))
if !ok { if !ok {
return g, nil return g, nil
} }
imported := IsImported(baseAST, obj) imported := IsImportedObj(baseAST, obj)
if imported { if imported {
println(d2format.Format(boardG.AST))
mk.Value = d2ast.MakeValueBox(&d2ast.Null{}) mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
appendMapKey(baseAST, mk) appendMapKey(baseAST, mk)
} else { } else {
@ -941,7 +1009,7 @@ func Delete(g *d2graph.Graph, boardPath []string, key string) (_ *d2graph.Graph,
return g, nil return g, nil
} }
if len(boardPath) > 0 { if len(boardPath) > 0 {
writeableRefs := getWriteableRefs(obj, baseAST) writeableRefs := GetWriteableRefs(obj, baseAST)
if len(writeableRefs) != len(obj.References) { if len(writeableRefs) != len(obj.References) {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{}) mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
} }
@ -997,7 +1065,7 @@ func bumpChildrenUnderscores(m *d2ast.Map) {
} }
func hoistRefChildren(g *d2graph.Graph, key *d2ast.KeyPath, ref d2graph.Reference) { func hoistRefChildren(g *d2graph.Graph, key *d2ast.KeyPath, ref d2graph.Reference) {
if ref.MapKey.Value.Map == nil { if ref.MapKey == nil || ref.MapKey.Value.Map == nil {
return return
} }
@ -1053,7 +1121,7 @@ func renameConflictsToParent(g *d2graph.Graph, key *d2ast.KeyPath) (*d2graph.Gra
var absKeys []*d2ast.KeyPath var absKeys []*d2ast.KeyPath
if len(ref.Key.Path)-1 == ref.KeyPathIndex { if len(ref.Key.Path)-1 == ref.KeyPathIndex {
if ref.MapKey.Value.Map == nil { if ref.MapKey == nil || ref.MapKey.Value.Map == nil {
continue continue
} }
var mapKeys []*d2ast.KeyPath var mapKeys []*d2ast.KeyPath
@ -1178,7 +1246,7 @@ func renameConflictsToParent(g *d2graph.Graph, key *d2ast.KeyPath) (*d2graph.Gra
return g, nil return g, nil
} }
func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) { func deleteReserved(g *d2graph.Graph, boardPath []string, baseAST *d2ast.Map, mk *d2ast.Key) (*d2graph.Graph, error) {
targetKey := mk.Key targetKey := mk.Key
if len(mk.Edges) == 1 { if len(mk.Edges) == 1 {
if mk.EdgeKey == nil { if mk.EdgeKey == nil {
@ -1193,10 +1261,17 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) {
var e *d2graph.Edge var e *d2graph.Edge
obj := g.Root obj := g.Root
if len(boardPath) > 0 {
boardG := GetBoardGraph(g, boardPath)
if boardG == nil {
return nil, fmt.Errorf("board %v not found", boardPath)
}
obj = boardG.Root
}
if len(mk.Edges) == 1 { if len(mk.Edges) == 1 {
if mk.Key != nil { if mk.Key != nil {
var ok bool var ok bool
obj, ok = g.Root.HasChild(d2graph.Key(mk.Key)) obj, ok = obj.HasChild(d2graph.Key(mk.Key))
if !ok { if !ok {
return g, nil return g, nil
} }
@ -1205,26 +1280,45 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) {
if !ok { if !ok {
return g, nil return g, nil
} }
imported := IsImportedEdge(baseAST, e)
if err := deleteEdgeField(g, e, targetKey.Path[len(targetKey.Path)-1].Unbox().ScalarString()); err != nil { deleted, err := deleteEdgeField(g, baseAST, e, targetKey.Path[len(targetKey.Path)-1].Unbox().ScalarString())
if err != nil {
return nil, err return nil, err
} }
if !deleted && imported {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
appendMapKey(baseAST, mk)
}
return recompile(g) return recompile(g)
} }
isStyleKey := false isNestedKey := false
for _, id := range d2graph.Key(targetKey) { imported := false
parts := d2graph.Key(targetKey)
for i, id := range parts {
_, ok := d2graph.ReservedKeywords[id] _, ok := d2graph.ReservedKeywords[id]
if ok { if ok {
if id == "style" { if id == "style" {
isStyleKey = true isNestedKey = true
continue continue
} }
if isStyleKey { if id == "label" || id == "icon" {
err := deleteObjField(g, obj, id) if i < len(parts)-1 {
isNestedKey = true
continue
}
}
if isNestedKey {
deleted, err := deleteObjField(g, baseAST, obj, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !deleted && imported {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
appendMapKey(baseAST, mk)
}
continue
} }
if id == "near" || if id == "near" ||
@ -1235,10 +1329,15 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) {
id == "left" || id == "left" ||
id == "top" || id == "top" ||
id == "link" { id == "link" {
err := deleteObjField(g, obj, id) deleted, err := deleteObjField(g, baseAST, obj, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !deleted && imported {
mk.Value = d2ast.MakeValueBox(&d2ast.Null{})
appendMapKey(baseAST, mk)
} else {
}
} }
break break
} }
@ -1246,59 +1345,84 @@ func deleteReserved(g *d2graph.Graph, mk *d2ast.Key) (*d2graph.Graph, error) {
if !ok { if !ok {
return nil, fmt.Errorf("object not found") return nil, fmt.Errorf("object not found")
} }
imported = IsImportedObj(baseAST, obj)
} }
return recompile(g) return recompile(g)
} }
func deleteMapField(m *d2ast.Map, field string) { func deleteMapField(m *d2ast.Map, field string) (deleted bool) {
for i := 0; i < len(m.Nodes); i++ { for i := 0; i < len(m.Nodes); i++ {
n := m.Nodes[i] n := m.Nodes[i]
if n.MapKey != nil && n.MapKey.Key != nil { if n.MapKey != nil && n.MapKey.Key != nil {
if n.MapKey.Key.Path[0].Unbox().ScalarString() == field { if n.MapKey.Key.Path[0].Unbox().ScalarString() == field {
deleteFromMap(m, n.MapKey) deleteFromMap(m, n.MapKey)
} else if n.MapKey.Key.Path[0].Unbox().ScalarString() == "style" || } else if n.MapKey.Key.Path[0].Unbox().ScalarString() == "style" ||
n.MapKey.Key.Path[0].Unbox().ScalarString() == "label" ||
n.MapKey.Key.Path[0].Unbox().ScalarString() == "icon" ||
n.MapKey.Key.Path[0].Unbox().ScalarString() == "source-arrowhead" || n.MapKey.Key.Path[0].Unbox().ScalarString() == "source-arrowhead" ||
n.MapKey.Key.Path[0].Unbox().ScalarString() == "target-arrowhead" { n.MapKey.Key.Path[0].Unbox().ScalarString() == "target-arrowhead" {
if n.MapKey.Value.Map != nil { if n.MapKey.Value.Map != nil {
deleteMapField(n.MapKey.Value.Map, field) deleted2 := deleteMapField(n.MapKey.Value.Map, field)
if deleted2 {
deleted = true
}
if len(n.MapKey.Value.Map.Nodes) == 0 { if len(n.MapKey.Value.Map.Nodes) == 0 {
deleteFromMap(m, n.MapKey) deleted2 := deleteFromMap(m, n.MapKey)
if deleted2 {
deleted = true
}
} }
} else if len(n.MapKey.Key.Path) == 2 && n.MapKey.Key.Path[1].Unbox().ScalarString() == field { } else if len(n.MapKey.Key.Path) == 2 && n.MapKey.Key.Path[1].Unbox().ScalarString() == field {
deleteFromMap(m, n.MapKey) deleted2 := deleteFromMap(m, n.MapKey)
if deleted2 {
deleted = true
} }
} }
} }
} }
} }
return deleted
}
func deleteEdgeField(g *d2graph.Graph, e *d2graph.Edge, field string) error { func deleteEdgeField(g *d2graph.Graph, ast *d2ast.Map, e *d2graph.Edge, field string) (deleted bool, _ error) {
for _, ref := range e.References { for _, ref := range e.References {
// Edge chains can't have fields // Edge chains can't have fields
if len(ref.MapKey.Edges) > 1 { if len(ref.MapKey.Edges) > 1 {
continue continue
} }
if ref.MapKey.Range.Path != ast.Range.Path {
continue
}
if ref.MapKey.Value.Map != nil { if ref.MapKey.Value.Map != nil {
deleteMapField(ref.MapKey.Value.Map, field) deleted2 := deleteMapField(ref.MapKey.Value.Map, field)
if deleted2 {
deleted = true
}
} else if ref.MapKey.EdgeKey != nil && ref.MapKey.EdgeKey.Path[len(ref.MapKey.EdgeKey.Path)-1].Unbox().ScalarString() == field { } else if ref.MapKey.EdgeKey != nil && ref.MapKey.EdgeKey.Path[len(ref.MapKey.EdgeKey.Path)-1].Unbox().ScalarString() == field {
// It's always safe to delete, since edge references must coexist with edge definition elsewhere // It's always safe to delete, since edge references must coexist with edge definition elsewhere
deleteFromMap(ref.Scope, ref.MapKey) deleted2 := deleteFromMap(ref.Scope, ref.MapKey)
if deleted2 {
deleted = true
} }
} }
return nil }
return deleted, nil
} }
func deleteObjField(g *d2graph.Graph, obj *d2graph.Object, field string) error { func deleteObjField(g *d2graph.Graph, ast *d2ast.Map, obj *d2graph.Object, field string) (deleted bool, _ error) {
objK, err := d2parser.ParseKey(obj.AbsID()) objK, err := d2parser.ParseKey(obj.AbsID())
if err != nil { if err != nil {
return err return false, err
} }
objGK := d2graph.Key(objK) objGK := d2graph.Key(objK)
for _, ref := range obj.References { for _, ref := range obj.References {
if ref.InEdge() { if ref.InEdge() {
continue continue
} }
if ref.Key.Range.Path != ast.Range.Path {
continue
}
if ref.MapKey.Value.Map != nil { if ref.MapKey.Value.Map != nil {
deleteMapField(ref.MapKey.Value.Map, field) deleteMapField(ref.MapKey.Value.Map, field)
} else if (len(ref.Key.Path) >= 2 && } else if (len(ref.Key.Path) >= 2 &&
@ -1306,15 +1430,20 @@ func deleteObjField(g *d2graph.Graph, obj *d2graph.Object, field string) error {
ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == obj.ID) || ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == obj.ID) ||
(len(ref.Key.Path) >= 3 && (len(ref.Key.Path) >= 3 &&
ref.Key.Path[len(ref.Key.Path)-1].Unbox().ScalarString() == field && ref.Key.Path[len(ref.Key.Path)-1].Unbox().ScalarString() == field &&
ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == "style" && (ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == "style" ||
ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == "label" ||
ref.Key.Path[len(ref.Key.Path)-2].Unbox().ScalarString() == "icon") &&
ref.Key.Path[len(ref.Key.Path)-3].Unbox().ScalarString() == obj.ID) { ref.Key.Path[len(ref.Key.Path)-3].Unbox().ScalarString() == obj.ID) {
tmpNodes := make([]d2ast.MapNodeBox, len(ref.Scope.Nodes)) tmpNodes := make([]d2ast.MapNodeBox, len(ref.Scope.Nodes))
copy(tmpNodes, ref.Scope.Nodes) copy(tmpNodes, ref.Scope.Nodes)
// If I delete this, will the object still exist? // If I delete this, will the object still exist?
deleteFromMap(ref.Scope, ref.MapKey) deleted2 := deleteFromMap(ref.Scope, ref.MapKey)
if deleted2 {
deleted = true
}
g2, err := recompile(g) g2, err := recompile(g)
if err != nil { if err != nil {
return err return false, err
} }
if _, ok := g2.Root.HasChild(objGK); !ok { if _, ok := g2.Root.HasChild(objGK); !ok {
// Nope, so can't delete it, just remove the field then // Nope, so can't delete it, just remove the field then
@ -1325,7 +1454,7 @@ func deleteObjField(g *d2graph.Graph, obj *d2graph.Object, field string) error {
} }
} }
return nil return deleted, nil
} }
func deleteObject(g *d2graph.Graph, baseAST *d2ast.Map, key *d2ast.KeyPath, obj *d2graph.Object) (*d2graph.Graph, error) { func deleteObject(g *d2graph.Graph, baseAST *d2ast.Map, key *d2ast.KeyPath, obj *d2graph.Object) (*d2graph.Graph, error) {
@ -1635,7 +1764,10 @@ func move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDesce
return recompile(g) return recompile(g)
} }
prevG, _ := recompile(boardG) prevG, err := recompile(boardG)
if err != nil {
return nil, err
}
ak := d2graph.Key(mk.Key) ak := d2graph.Key(mk.Key)
ak2 := d2graph.Key(mk2.Key) ak2 := d2graph.Key(mk2.Key)
@ -1655,7 +1787,7 @@ func move(g *d2graph.Graph, boardPath []string, key, newKey string, includeDesce
} }
if len(boardPath) > 0 { if len(boardPath) > 0 {
writeableRefs := getWriteableRefs(obj, baseAST) writeableRefs := GetWriteableRefs(obj, baseAST)
if len(writeableRefs) != len(obj.References) { if len(writeableRefs) != len(obj.References) {
return nil, OutsideScopeError{} return nil, OutsideScopeError{}
} }
@ -2159,8 +2291,17 @@ func updateNear(prevG, g *d2graph.Graph, from, to *string, includeDescendants bo
if len(n.MapKey.Key.Path) == 0 { if len(n.MapKey.Key.Path) == 0 {
continue continue
} }
if len(n.MapKey.Key.Path) > 1 {
if n.MapKey.Key.Path[len(n.MapKey.Key.Path)-2].Unbox().ScalarString() == "label" ||
n.MapKey.Key.Path[len(n.MapKey.Key.Path)-2].Unbox().ScalarString() == "icon" {
continue
}
}
if n.MapKey.Key.Path[len(n.MapKey.Key.Path)-1].Unbox().ScalarString() == "near" { if n.MapKey.Key.Path[len(n.MapKey.Key.Path)-1].Unbox().ScalarString() == "near" {
k := n.MapKey.Value.ScalarBox().Unbox().ScalarString() k := n.MapKey.Value.ScalarBox().Unbox().ScalarString()
if _, ok := d2graph.NearConstants[k]; ok {
continue
}
if strings.EqualFold(k, *from) && to == nil { if strings.EqualFold(k, *from) && to == nil {
deleteFromMap(obj.Map, n.MapKey) deleteFromMap(obj.Map, n.MapKey)
} else { } else {
@ -3122,21 +3263,3 @@ func filterReservedPath(path []*d2ast.StringBox) (filtered []*d2ast.StringBox) {
} }
return return
} }
func getWriteableRefs(obj *d2graph.Object, writeableAST *d2ast.Map) (out []d2graph.Reference) {
for i, ref := range obj.References {
if ref.ScopeAST == writeableAST && ref.Key.Range.Path == writeableAST.Range.Path {
out = append(out, obj.References[i])
}
}
return
}
func getWriteableEdgeRefs(edge *d2graph.Edge, writeableAST *d2ast.Map) (out []d2graph.EdgeReference) {
for i, ref := range edge.References {
if ref.ScopeAST == writeableAST {
out = append(out, edge.References[i])
}
}
return
}

View file

@ -1177,6 +1177,83 @@ b
} }
} }
b: {style.fill: green} b: {style.fill: green}
`,
},
{
name: "class-with-label",
text: `classes: {
user: {
label: ""
}
}
a.class: user
`,
key: `a.style.opacity`,
value: go2.Pointer(`0.5`),
exp: `classes: {
user: {
label: ""
}
}
a.class: user
a.style.opacity: 0.5
`,
},
{
name: "edge-class-with-label",
text: `classes: {
user: {
label: ""
}
}
a -> b: {
class: user
}
`,
key: `(a -> b)[0].style.opacity`,
value: go2.Pointer(`0.5`),
exp: `classes: {
user: {
label: ""
}
}
a -> b: {
class: user
style.opacity: 0.5
}
`,
},
{
name: "var-with-label",
text: `vars: {
user: ""
}
a: ${user}
`,
key: `a.style.opacity`,
value: go2.Pointer(`0.5`),
exp: `vars: {
user: ""
}
a: ${user} {style.opacity: 0.5}
`,
},
{
name: "glob-with-label",
text: `*.label: ""
a
`,
key: `a.style.opacity`,
value: go2.Pointer(`0.5`),
exp: `*.label: ""
a
a.style.opacity: 0.5
`, `,
}, },
{ {
@ -1518,6 +1595,86 @@ a.b -> a.c: {style.animated: true}
value: go2.Pointer(`diamond`), value: go2.Pointer(`diamond`),
exp: `x -> y: {target-arrowhead.shape: diamond} exp: `x -> y: {target-arrowhead.shape: diamond}
`,
},
{
name: "edge-arrowhead-filled/1",
text: `x -> y
`,
key: `(x -> y)[0].target-arrowhead.style.filled`,
value: go2.Pointer(`true`),
exp: `x -> y: {target-arrowhead.style.filled: true}
`,
},
{
name: "edge-arrowhead-filled/2",
text: `x -> y: {
target-arrowhead: * {
shape: diamond
}
}
`,
key: `(x -> y)[0].target-arrowhead.style.filled`,
value: go2.Pointer(`true`),
exp: `x -> y: {
target-arrowhead: * {
shape: diamond
style.filled: true
}
}
`,
},
{
name: "edge-arrowhead-filled/3",
text: `x -> y: {
target-arrowhead.shape: diamond
}
`,
key: `(x -> y)[0].target-arrowhead.style.filled`,
value: go2.Pointer(`true`),
exp: `x -> y: {
target-arrowhead.shape: diamond
target-arrowhead.style.filled: true
}
`,
},
{
name: "edge-arrowhead-filled/4",
text: `x -> y: {
target-arrowhead.shape: diamond
target-arrowhead.style.filled: true
}
`,
key: `(x -> y)[0].target-arrowhead.style.filled`,
value: go2.Pointer(`false`),
exp: `x -> y: {
target-arrowhead.shape: diamond
target-arrowhead.style.filled: false
}
`,
},
{
name: "edge-arrowhead-filled/5",
text: `x -> y: {
target-arrowhead.shape: diamond
target-arrowhead.style: {
filled: false
}
}
`,
key: `(x -> y)[0].target-arrowhead.style.filled`,
value: go2.Pointer(`true`),
exp: `x -> y: {
target-arrowhead.shape: diamond
target-arrowhead.style: {
filled: true
}
}
`, `,
}, },
{ {
@ -2171,6 +2328,252 @@ layers: {
b.style.fill: red b.style.fill: red
} }
} }
`,
},
{
name: "import/9",
text: `...@yo
`,
fsTexts: map[string]string{
"yo.d2": `a -> b`,
},
key: `(a -> b)[0].style.stroke`,
value: go2.Pointer(`red`),
exp: `...@yo
(a -> b)[0].style.stroke: red
`,
},
{
name: "label-near/1",
text: `x
`,
key: `x.label.near`,
value: go2.Pointer(`bottom-right`),
exp: `x: {label.near: bottom-right}
`,
},
{
name: "label-near/2",
text: `x.label.near: bottom-left
`,
key: `x.label.near`,
value: go2.Pointer(`bottom-right`),
exp: `x.label.near: bottom-right
`,
},
{
name: "label-near/3",
text: `x: {
label.near: bottom-left
}
`,
key: `x.label.near`,
value: go2.Pointer(`bottom-right`),
exp: `x: {
label.near: bottom-right
}
`,
},
{
name: "label-near/4",
text: `x: {
label: hi {
near: bottom-left
}
}
`,
key: `x.label.near`,
value: go2.Pointer(`bottom-right`),
exp: `x: {
label: hi {
near: bottom-right
}
}
`,
},
{
name: "label-near/5",
text: `x: hi {
label: {
near: bottom-left
}
}
`,
key: `x.label.near`,
value: go2.Pointer(`bottom-right`),
exp: `x: hi {
label: {
near: bottom-right
}
}
`,
},
{
name: "glob-field/1",
text: `*.style.fill: red
a
b
`,
key: `a.style.fill`,
value: go2.Pointer(`blue`),
exp: `*.style.fill: red
a: {style.fill: blue}
b
`,
},
{
name: "glob-field/2",
text: `(* -> *)[*].style.stroke: red
a -> b
a -> b
`,
key: `(a -> b)[0].style.stroke`,
value: go2.Pointer(`blue`),
exp: `(* -> *)[*].style.stroke: red
a -> b: {style.stroke: blue}
a -> b
`,
},
{
name: "glob-field/3",
text: `(* -> *)[*].style.stroke: red
a -> b: {style.stroke: blue}
a -> b
`,
key: `(a -> b)[0].style.stroke`,
value: go2.Pointer(`green`),
exp: `(* -> *)[*].style.stroke: red
a -> b: {style.stroke: green}
a -> b
`,
},
{
name: "nested-edge-chained/1",
text: `a: {
b: {
c
}
}
x -> a.b -> a.b.c
`,
key: `(a.b -> a.b.c)[0].style.stroke`,
value: go2.Pointer(`green`),
exp: `a: {
b: {
c
}
}
x -> a.b -> a.b.c
(a.b -> a.b.c)[0].style.stroke: green
`,
},
{
name: "nested-edge-chained/2",
text: `z: {
a: {
b: {
c
}
}
x -> a.b -> a.b.c
}
`,
key: `(z.a.b -> z.a.b.c)[0].style.stroke`,
value: go2.Pointer(`green`),
exp: `z: {
a: {
b: {
c
}
}
x -> a.b -> a.b.c
(a.b -> a.b.c)[0].style.stroke: green
}
`,
},
{
name: "edge-comment",
text: `x -> y: {
# hi
style.stroke: blue
}
`,
key: `(x -> y)[0].style.stroke`,
value: go2.Pointer(`green`),
exp: `x -> y: {
# hi
style.stroke: green
}
`,
},
{
name: "scenario-child",
text: `a -> b
scenarios: {
x: {
hi
}
}
`,
key: `(a -> b)[0].style.stroke-width`,
value: go2.Pointer(`3`),
boardPath: []string{"x"},
exp: `a -> b
scenarios: {
x: {
hi
(a -> b)[0].style.stroke-width: 3
}
}
`,
},
{
name: "scenario-grandchild",
text: `a -> b
scenarios: {
x: {
scenarios: {
c: {
(a -> b)[0].style.bold: true
}
}
}
}
`,
key: `(a -> b)[0].style.stroke-width`,
value: go2.Pointer(`3`),
boardPath: []string{"x", "c"},
exp: `a -> b
scenarios: {
x: {
scenarios: {
c: {
(a -> b)[0].style.bold: true
(a -> b)[0].style.stroke-width: 3
}
}
}
}
`, `,
}, },
} }
@ -7205,6 +7608,305 @@ scenarios: {
x: null x: null
} }
} }
`,
},
{
name: "import/3",
text: `...@meow
`,
fsTexts: map[string]string{
"meow.d2": `a -> b
`,
},
key: `(a -> b)[0]`,
exp: `...@meow
(a -> b)[0]: null
`,
},
{
name: "import/4",
text: `...@meow
`,
fsTexts: map[string]string{
"meow.d2": `a.link: https://google.com
`,
},
key: `a.link`,
exp: `...@meow
a.link: null
`,
},
{
name: "import/5",
text: `...@meow
`,
fsTexts: map[string]string{
"meow.d2": `a -> b: {
target-arrowhead: 1
}
`,
},
key: `(a -> b)[0].target-arrowhead`,
exp: `...@meow
(a -> b)[0].target-arrowhead: null
`,
},
{
name: "import/6",
text: `...@meow
`,
fsTexts: map[string]string{
"meow.d2": `a.style.fill: red
`,
},
key: `a.style.fill`,
exp: `...@meow
a.style.fill: null
`,
},
{
name: "import/7",
text: `...@meow
a.label.near: center-center
`,
fsTexts: map[string]string{
"meow.d2": `a
`,
},
key: `a.label.near`,
exp: `...@meow
`,
},
{
name: "import/8",
text: `...@meow
(a -> b)[0].style.stroke: red
`,
fsTexts: map[string]string{
"meow.d2": `a -> b
`,
},
key: `(a -> b)[0].style.stroke`,
exp: `...@meow
`,
},
{
name: "label-near/1",
text: `yes: {label.near: center-center}
`,
key: `yes.label.near`,
exp: `yes
`,
},
{
name: "label-near/2",
text: `yes.label.near: center-center
`,
key: `yes.label.near`,
exp: `yes
`,
},
{
name: "connection-glob",
text: `* -> *
a
b
`,
key: `(a -> b)[0]`,
exp: `* -> *
a
b
(a -> b)[0]: null
`,
},
{
name: "glob-child/1",
text: `*.b
a
`,
key: `a.b`,
exp: `*.b
a
a.b: null
`,
},
{
name: "delete-imported-layer-obj",
text: `layers: {
x: {
...@meow
}
}
`,
fsTexts: map[string]string{
"meow.d2": `a
`,
},
boardPath: []string{"x"},
key: `a`,
exp: `layers: {
x: {
...@meow
a: null
}
}
`,
},
{
name: "delete-not-layer-obj",
text: `b.style.fill: red
layers: {
x: {
a
}
}
`,
key: `b.style.fill`,
exp: `b
layers: {
x: {
a
}
}
`,
},
{
name: "delete-layer-obj",
text: `layers: {
x: {
a
}
}
`,
boardPath: []string{"x"},
key: `a`,
exp: `layers: {
x
}
`,
},
{
name: "delete-layer-style",
text: `layers: {
x: {
a.style.fill: red
}
}
`,
boardPath: []string{"x"},
key: `a.style.fill`,
exp: `layers: {
x: {
a
}
}
`,
},
{
name: "edge-out-layer",
text: `x: {
a -> b
}
`,
key: `x.(a -> b)[0].style.stroke`,
exp: `x: {
a -> b
}
`,
},
{
name: "edge-in-layer",
text: `layers: {
test: {
x: {
a -> b
}
}
}
`,
boardPath: []string{"test"},
key: `x.(a -> b)[0].style.stroke`,
exp: `layers: {
test: {
x: {
a -> b
}
}
}
`,
},
{
name: "label-near-in-layer",
text: `layers: {
x: {
y: {
label.near: center-center
}
a
}
}
`,
boardPath: []string{"x"},
key: `y`,
exp: `layers: {
x: {
a
}
}
`,
},
{
name: "update-near-in-layer",
text: `layers: {
x: {
y: {
near: a
}
a
}
}
`,
boardPath: []string{"x"},
key: `y`,
exp: `layers: {
x: {
a
}
}
`,
},
{
name: "edge-with-glob",
text: `x -> y
y
(* -> *)[*].style.opacity: 0.8
`,
key: `(x -> y)[0]`,
exp: `x
y
(* -> *)[*].style.opacity: 0.8
`, `,
}, },
} }

View file

@ -140,8 +140,11 @@ func GetParentID(g *d2graph.Graph, boardPath []string, absID string) (string, er
return obj.Parent.AbsID(), nil return obj.Parent.AbsID(), nil
} }
func IsImported(ast *d2ast.Map, obj *d2graph.Object) bool { func IsImportedObj(ast *d2ast.Map, obj *d2graph.Object) bool {
for _, ref := range obj.References { for _, ref := range obj.References {
if ref.Key.HasGlob() {
return true
}
if ref.Key.Range.Path != ast.Range.Path { if ref.Key.Range.Path != ast.Range.Path {
return true return true
} }
@ -150,6 +153,22 @@ func IsImported(ast *d2ast.Map, obj *d2graph.Object) bool {
return false return false
} }
// Glob creations count as imported for now
// TODO Probably rename later
func IsImportedEdge(ast *d2ast.Map, edge *d2graph.Edge) bool {
for _, ref := range edge.References {
// If edge index, the glob is just setting something, not responsible for creating the edge
if (ref.Edge.Src.HasGlob() || ref.Edge.Dst.HasGlob()) && ref.MapKey.EdgeIndex == nil {
return true
}
if ref.Edge.Range.Path != ast.Range.Path {
return true
}
}
return false
}
func GetObj(g *d2graph.Graph, boardPath []string, absID string) *d2graph.Object { func GetObj(g *d2graph.Graph, boardPath []string, absID string) *d2graph.Object {
g = GetBoardGraph(g, boardPath) g = GetBoardGraph(g, boardPath)
if g == nil { if g == nil {
@ -227,3 +246,21 @@ func GetID(key string) string {
return d2format.Format(d2ast.RawString(mk.Key.Path[len(mk.Key.Path)-1].Unbox().ScalarString(), true)) return d2format.Format(d2ast.RawString(mk.Key.Path[len(mk.Key.Path)-1].Unbox().ScalarString(), true))
} }
func GetWriteableRefs(obj *d2graph.Object, writeableAST *d2ast.Map) (out []d2graph.Reference) {
for i, ref := range obj.References {
if ref.ScopeAST == writeableAST && ref.Key.Range.Path == writeableAST.Range.Path {
out = append(out, obj.References[i])
}
}
return
}
func GetWriteableEdgeRefs(edge *d2graph.Edge, writeableAST *d2ast.Map) (out []d2graph.EdgeReference) {
for i, ref := range edge.References {
if ref.ScopeAST == writeableAST {
out = append(out, edge.References[i])
}
}
return
}

View file

@ -201,3 +201,59 @@ func (p *execPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error)
return stdout, nil return stdout, nil
} }
func (p *execPlugin) RouteEdges(ctx context.Context, g *d2graph.Graph, edges []*d2graph.Edge) error {
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
defer cancel()
graphBytes, err := d2graph.SerializeGraph(g)
if err != nil {
return err
}
var g2 d2graph.Graph
err = d2graph.DeserializeGraph(graphBytes, &g2)
if err != nil {
return fmt.Errorf("failed to unmarshal json: %w", err)
}
g2.Edges = edges
graphBytes2, err := d2graph.SerializeGraph(&g2)
if err != nil {
return err
}
in := routeEdgesInput{
G: graphBytes,
GEdges: graphBytes2,
}
b, err := json.Marshal(in)
if err != nil {
return err
}
args := []string{"routeedges"}
for k, v := range p.opts {
args = append(args, fmt.Sprintf("--%s", k), v)
}
cmd := exec.CommandContext(ctx, p.path, args...)
buffer := bytes.Buffer{}
buffer.Write(b)
cmd.Stdin = &buffer
stdout, err := cmd.Output()
if err != nil {
ee := &exec.ExitError{}
if errors.As(err, &ee) && len(ee.Stderr) > 0 {
return fmt.Errorf("%v\nstderr:\n%s", ee, ee.Stderr)
}
return err
}
err = d2graph.DeserializeGraph(stdout, g)
if err != nil {
return fmt.Errorf("failed to unmarshal json: %w", err)
}
return nil
}

View file

@ -85,6 +85,11 @@ type RoutingPlugin interface {
RouteEdges(context.Context, *d2graph.Graph, []*d2graph.Edge) error RouteEdges(context.Context, *d2graph.Graph, []*d2graph.Edge) error
} }
type routeEdgesInput struct {
G []byte `json:"g"`
GEdges []byte `json:"gEdges"`
}
// PluginInfo is the current info information of a plugin. // PluginInfo is the current info information of a plugin.
// note: The two fields Type and Path are not set by the plugin // note: The two fields Type and Path are not set by the plugin
// itself but only in ListPlugins. // itself but only in ListPlugins.

View file

@ -58,6 +58,12 @@ func Serve(p Plugin) xmain.RunFunc {
return layout(ctx, p, ms) return layout(ctx, p, ms)
case "postprocess": case "postprocess":
return postProcess(ctx, p, ms) return postProcess(ctx, p, ms)
case "routeedges":
routingPlugin, ok := p.(RoutingPlugin)
if !ok {
return fmt.Errorf("plugin has routing feature but does not implement RoutingPlugin")
}
return routeEdges(ctx, routingPlugin, ms)
default: default:
return xmain.UsageErrorf("unrecognized command: %s", subcmd) return xmain.UsageErrorf("unrecognized command: %s", subcmd)
} }
@ -137,3 +143,41 @@ func postProcess(ctx context.Context, p Plugin, ms *xmain.State) error {
} }
return nil return nil
} }
func routeEdges(ctx context.Context, p RoutingPlugin, ms *xmain.State) error {
inRaw, err := io.ReadAll(ms.Stdin)
if err != nil {
return err
}
var in routeEdgesInput
err = json.Unmarshal(inRaw, &in)
if err != nil {
return err
}
var g d2graph.Graph
if err := d2graph.DeserializeGraph(in.G, &g); err != nil {
return fmt.Errorf("failed to unmarshal input graph to graph: %s", in)
}
var gedges d2graph.Graph
if err := d2graph.DeserializeGraph(in.GEdges, &gedges); err != nil {
return fmt.Errorf("failed to unmarshal input edges graph to graph: %s", in)
}
err = p.RouteEdges(ctx, &g, gedges.Edges)
if err != nil {
return err
}
b, err := d2graph.SerializeGraph(&g)
if err != nil {
return err
}
_, err = ms.Stdout.Write(b)
if err != nil {
return err
}
return nil
}

View file

@ -321,17 +321,57 @@ func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) {
} }
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) { func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
animatedClass := ""
if connection.Animated {
animatedClass = " animated-connection"
}
if connection.Animated {
// If connection is animated and bidirectional
if (connection.DstArrow == d2target.NoArrowhead && connection.SrcArrow == d2target.NoArrowhead) || (connection.DstArrow != d2target.NoArrowhead && connection.SrcArrow != d2target.NoArrowhead) {
// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
path1, path2, err := svg.SplitPath(path, 0.5)
if err != nil {
return "", err
}
pathEl1 := d2themes.NewThemableElement("path")
pathEl1.D = path1
pathEl1.Fill = color.None
pathEl1.Stroke = connection.Stroke
pathEl1.ClassName = fmt.Sprintf("connection%s", animatedClass)
pathEl1.Style = connection.CSSStyle()
pathEl1.Style += "animation-direction: reverse;"
pathEl1.Attributes = attrs
pathEl2 := d2themes.NewThemableElement("path")
pathEl2.D = path2
pathEl2.Fill = color.None
pathEl2.Stroke = connection.Stroke
pathEl2.ClassName = fmt.Sprintf("connection%s", animatedClass)
pathEl2.Style = connection.CSSStyle()
pathEl2.Attributes = attrs
return pathEl1.Render() + " " + pathEl2.Render(), nil
} else {
pathEl := d2themes.NewThemableElement("path")
pathEl.D = path
pathEl.Fill = color.None
pathEl.Stroke = connection.Stroke
pathEl.ClassName = fmt.Sprintf("connection%s", animatedClass)
pathEl.Style = connection.CSSStyle()
pathEl.Attributes = attrs
return pathEl.Render(), nil
}
} else {
roughness := 0.5 roughness := 0.5
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness) js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
paths, err := computeRoughPathData(r, js) paths, err := computeRoughPathData(r, js)
if err != nil { if err != nil {
return "", err return "", err
} }
output := "" output := ""
animatedClass := ""
if connection.Animated {
animatedClass = " animated-connection"
}
pathEl := d2themes.NewThemableElement("path") pathEl := d2themes.NewThemableElement("path")
pathEl.Fill = color.None pathEl.Fill = color.None
@ -345,6 +385,7 @@ func Connection(r *Runner, connection d2target.Connection, path, attrs string) (
} }
return output, nil return output, nil
} }
}
// TODO cleanup // TODO cleanup
func Table(r *Runner, shape d2target.Shape) (string, error) { func Table(r *Runner, shape d2target.Shape) (string, error) {
@ -802,6 +843,13 @@ func ArrowheadJS(r *Runner, arrowhead d2target.Arrowhead, stroke string, strokeW
stroke, stroke,
BG_COLOR, BG_COLOR,
) )
case d2target.CircleArrowhead:
arrowJS = fmt.Sprintf(
`node = rc.circle(-2, -1, 8, { strokeWidth: %d, stroke: "%s", fill: "%s", fillStyle: "solid", fillWeight: 1, seed: 5 })`,
strokeWidth,
stroke,
BG_COLOR,
)
} }
return return
} }

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2513870599 .text-bold { .d2-2513870599 .text-bold {
font-family: "d2-2513870599-font-bold"; font-family: "d2-2513870599-font-bold";
} }

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2513870599 .text-bold { .d2-2513870599 .text-bold {
font-family: "d2-2513870599-font-bold"; font-family: "d2-2513870599-font-bold";
} }

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3148583989 .text-bold { .d2-3148583989 .text-bold {
font-family: "d2-3148583989-font-bold"; font-family: "d2-3148583989-font-bold";
} }

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3148583989 .text-bold { .d2-3148583989 .text-bold {
font-family: "d2-3148583989-font-bold"; font-family: "d2-3148583989-font-bold";
} }

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View file

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

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View file

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

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-914436609 .text { .d2-914436609 .text {
font-family: "d2-914436609-font-regular"; font-family: "d2-914436609-font-regular";
} }

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-914436609 .text { .d2-914436609 .text {
font-family: "d2-914436609-font-regular"; font-family: "d2-914436609-font-regular";
} }

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View file

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

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1467 386"><svg id="d2-svg" class="d2-206057491" width="1467" height="386" viewBox="-101 -101 1467 386"><rect x="-101.000000" y="-101.000000" width="1467.000000" height="386.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1467 386"><svg id="d2-svg" class="d2-206057491" width="1467" height="386" viewBox="-101 -101 1467 386"><rect x="-101.000000" y="-101.000000" width="1467.000000" height="386.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-206057491 .text { .d2-206057491 .text {
font-family: "d2-206057491-font-regular"; font-family: "d2-206057491-font-regular";
} }

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View file

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

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2029734873 .text-bold { .d2-2029734873 .text-bold {
font-family: "d2-2029734873-font-bold"; font-family: "d2-2029734873-font-bold";
} }

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2029734873 .text-bold { .d2-2029734873 .text-bold {
font-family: "d2-2029734873-font-bold"; font-family: "d2-2029734873-font-bold";
} }

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1740 600"><svg id="d2-svg" class="d2-341527886" width="1740" height="600" viewBox="-101 -101 1740 600"><rect x="-101.000000" y="-101.000000" width="1740.000000" height="600.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1740 600"><svg id="d2-svg" class="d2-341527886" width="1740" height="600" viewBox="-101 -101 1740 600"><rect x="-101.000000" y="-101.000000" width="1740.000000" height="600.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-341527886 .text-bold { .d2-341527886 .text-bold {
font-family: "d2-341527886-font-bold"; font-family: "d2-341527886-font-bold";
} }

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1740 600"><svg id="d2-svg" class="d2-341527886" width="1740" height="600" viewBox="-101 -101 1740 600"><rect x="-101.000000" y="-101.000000" width="1740.000000" height="600.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1740 600"><svg id="d2-svg" class="d2-341527886" width="1740" height="600" viewBox="-101 -101 1740 600"><rect x="-101.000000" y="-101.000000" width="1740.000000" height="600.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-341527886 .text-bold { .d2-341527886 .text-bold {
font-family: "d2-341527886-font-bold"; font-family: "d2-341527886-font-bold";
} }

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 398 285"><svg id="d2-svg" class="d2-1379818723" width="398" height="285" viewBox="-101 -115 398 285"><rect x="-101.000000" y="-115.000000" width="398.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 398 285"><svg id="d2-svg" class="d2-1379818723" width="398" height="285" viewBox="-101 -115 398 285"><rect x="-101.000000" y="-115.000000" width="398.000000" height="285.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1379818723 .text-bold { .d2-1379818723 .text-bold {
font-family: "d2-1379818723-font-bold"; font-family: "d2-1379818723-font-bold";
} }

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-144203892" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-144203892" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-144203892 .text-bold { .d2-144203892 .text-bold {
font-family: "d2-144203892-font-bold"; font-family: "d2-144203892-font-bold";
} }

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1441 721"><svg id="d2-svg" class="d2-819970511" width="1441" height="721" viewBox="-101 -112 1441 721"><rect x="-101.000000" y="-112.000000" width="1441.000000" height="721.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1441 721"><svg id="d2-svg" class="d2-819970511" width="1441" height="721" viewBox="-101 -112 1441 721"><rect x="-101.000000" y="-112.000000" width="1441.000000" height="721.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-819970511 .text-bold { .d2-819970511 .text-bold {
font-family: "d2-819970511-font-bold"; font-family: "d2-819970511-font-bold";
} }

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1052 863"><svg id="d2-svg" class="d2-611371411" width="1052" height="863" viewBox="-61 -1 1052 863"><rect x="-61.000000" y="-1.000000" width="1052.000000" height="863.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1052 863"><svg id="d2-svg" class="d2-611371411" width="1052" height="863" viewBox="-61 -1 1052 863"><rect x="-61.000000" y="-1.000000" width="1052.000000" height="863.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-611371411 .text-mono { .d2-611371411 .text-mono {
font-family: "d2-611371411-font-mono"; font-family: "d2-611371411-font-mono";
} }

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 922 408"><svg id="d2-svg" class="d2-2864094478" width="922" height="408" viewBox="-91 -141 922 408"><rect x="-91.000000" y="-141.000000" width="922.000000" height="408.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 922 408"><svg id="d2-svg" class="d2-2864094478" width="922" height="408" viewBox="-91 -141 922 408"><rect x="-91.000000" y="-141.000000" width="922.000000" height="408.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2864094478 .text { .d2-2864094478 .text {
font-family: "d2-2864094478-font-regular"; font-family: "d2-2864094478-font-regular";
} }

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 337 560"><svg id="d2-svg" class="d2-2874225056" width="337" height="560" viewBox="-89 -89 337 560"><rect x="-89.000000" y="-89.000000" width="337.000000" height="560.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 337 560"><svg id="d2-svg" class="d2-2874225056" width="337" height="560" viewBox="-89 -89 337 560"><rect x="-89.000000" y="-89.000000" width="337.000000" height="560.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2874225056 .text-bold { .d2-2874225056 .text-bold {
font-family: "d2-2874225056-font-bold"; font-family: "d2-2874225056-font-bold";
} }

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 676 434"><svg id="d2-svg" class="d2-2558489054" width="676" height="434" viewBox="-101 -101 676 434"><rect x="-101.000000" y="-101.000000" width="676.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 676 434"><svg id="d2-svg" class="d2-2558489054" width="676" height="434" viewBox="-101 -101 676 434"><rect x="-101.000000" y="-101.000000" width="676.000000" height="434.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2558489054 .text-bold { .d2-2558489054 .text-bold {
font-family: "d2-2558489054-font-bold"; font-family: "d2-2558489054-font-bold";
} }

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1098532122 .text { .d2-1098532122 .text {
font-family: "d2-1098532122-font-regular"; font-family: "d2-1098532122-font-regular";
} }

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1098532122 .text { .d2-1098532122 .text {
font-family: "d2-1098532122-font-regular"; font-family: "d2-1098532122-font-regular";
} }

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View file

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

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 516 636"><svg id="d2-svg" class="d2-3736831304" width="516" height="636" viewBox="-78 -122 516 636"><rect x="-78.000000" y="-122.000000" width="516.000000" height="636.000000" rx="0.000000" fill="#947A6D" stroke-width="0" /><rect x="-78.000000" y="-122.000000" width="516.000000" height="636.000000" rx="0.000000" class="paper-overlay" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 516 636"><svg id="d2-svg" class="d2-3736831304" width="516" height="636" viewBox="-78 -122 516 636"><rect x="-78.000000" y="-122.000000" width="516.000000" height="636.000000" rx="0.000000" fill="#947A6D" stroke-width="0" /><rect x="-78.000000" y="-122.000000" width="516.000000" height="636.000000" rx="0.000000" class="paper-overlay" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3736831304 .text-mono { .d2-3736831304 .text-mono {
font-family: "d2-3736831304-font-mono"; font-family: "d2-3736831304-font-mono";
} }

Before

Width:  |  Height:  |  Size: 497 KiB

After

Width:  |  Height:  |  Size: 497 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1192 1156"><svg id="d2-svg" class="d2-1077382347" width="1192" height="1156" viewBox="-106 -157 1192 1156"><rect x="-106.000000" y="-157.000000" width="1192.000000" height="1156.000000" rx="0.000000" stroke="LightSteelBlue" fill="honeydew" stroke-width="0" /><rect x="-101.000000" y="-152.000000" width="1182.000000" height="1146.000000" rx="0.000000" stroke="LightSteelBlue" fill="transparent" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1192 1156"><svg id="d2-svg" class="d2-1077382347" width="1192" height="1156" viewBox="-106 -157 1192 1156"><rect x="-106.000000" y="-157.000000" width="1192.000000" height="1156.000000" rx="0.000000" stroke="LightSteelBlue" fill="honeydew" stroke-width="0" /><rect x="-101.000000" y="-152.000000" width="1182.000000" height="1146.000000" rx="0.000000" stroke="LightSteelBlue" fill="transparent" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1077382347 .text { .d2-1077382347 .text {
font-family: "d2-1077382347-font-regular"; font-family: "d2-1077382347-font-regular";
} }

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2504113906 .text { .d2-2504113906 .text {
font-family: "d2-2504113906-font-regular"; font-family: "d2-2504113906-font-regular";
} }

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2504113906 .text { .d2-2504113906 .text {
font-family: "d2-2504113906-font-regular"; font-family: "d2-2504113906-font-regular";
} }

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 832 1627"><svg id="d2-svg" class="d2-790545213" width="832" height="1627" viewBox="-92 -101 832 1627"><rect x="-92.000000" y="-101.000000" width="832.000000" height="1627.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 832 1627"><svg id="d2-svg" class="d2-790545213" width="832" height="1627" viewBox="-92 -101 832 1627"><rect x="-92.000000" y="-101.000000" width="832.000000" height="1627.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-790545213 .text-mono { .d2-790545213 .text-mono {
font-family: "d2-790545213-font-mono"; font-family: "d2-790545213-font-mono";
} }

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-103900560" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-103900560" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-103900560 .text { .d2-103900560 .text {
font-family: "d2-103900560-font-regular"; font-family: "d2-103900560-font-regular";
} }

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-103900560" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-103900560" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-103900560 .text { .d2-103900560 .text {
font-family: "d2-103900560-font-regular"; font-family: "d2-103900560-font-regular";
} }

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 477 394"><svg id="d2-svg" class="d2-1237400798" width="477" height="394" viewBox="-101 -101 477 394"><rect x="-101.000000" y="-101.000000" width="477.000000" height="394.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 477 394"><svg id="d2-svg" class="d2-1237400798" width="477" height="394" viewBox="-101 -101 477 394"><rect x="-101.000000" y="-101.000000" width="477.000000" height="394.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1237400798 .text-bold { .d2-1237400798 .text-bold {
font-family: "d2-1237400798-font-bold"; font-family: "d2-1237400798-font-bold";
} }

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1431 1588"><svg id="d2-svg" class="d2-4070036272" width="1431" height="1588" viewBox="-192 -70 1431 1588"><rect x="-192.000000" y="-70.000000" width="1431" height="1588" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1431 1588"><svg id="d2-svg" class="d2-4070036272" width="1431" height="1588" viewBox="-192 -70 1431 1588"><rect x="-192.000000" y="-70.000000" width="1431" height="1588" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-4070036272 .text { .d2-4070036272 .text {
font-family: "d2-4070036272-font-regular"; font-family: "d2-4070036272-font-regular";
} }

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-1118191387" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 407"><svg id="d2-svg" class="d2-1118191387" 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));
} }

Before

Width:  |  Height:  |  Size: 657 KiB

After

Width:  |  Height:  |  Size: 657 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2990259904" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-2990259904" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon { .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));
} }

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-3058966282" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 564 682"><svg id="d2-svg" class="d2-3058966282" width="564" height="682" viewBox="-101 -118 564 682"><rect x="-101.000000" y="-118.000000" width="564" height="682" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon { .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));
} }

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-3026443247" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" fill="PaleVioletRed" stroke-width="0" /><style type="text/css"><![CDATA[ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-3026443247" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" fill="PaleVioletRed" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon { .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));
} }

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-2257413360" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 565 630"><svg id="d2-svg" class="d2-2257413360" width="565" height="630" viewBox="-101 -118 565 630"><rect x="-101.000000" y="-118.000000" width="565" height="630" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.appendix-icon { .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));
} }

Before

Width:  |  Height:  |  Size: 661 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -549,6 +549,7 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
srcAdj, dstAdj := getArrowheadAdjustments(connection, idToShape) srcAdj, dstAdj := getArrowheadAdjustments(connection, idToShape)
path := pathData(connection, srcAdj, dstAdj) path := pathData(connection, srcAdj, dstAdj)
mask := fmt.Sprintf(`mask="url(#%s)"`, labelMaskID) mask := fmt.Sprintf(`mask="url(#%s)"`, labelMaskID)
if sketchRunner != nil { if sketchRunner != nil {
out, err := d2sketch.Connection(sketchRunner, connection, path, mask) out, err := d2sketch.Connection(sketchRunner, connection, path, mask)
if err != nil { if err != nil {
@ -568,6 +569,34 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
animatedClass = " animated-connection" animatedClass = " animated-connection"
} }
// If connection is animated and bidirectional
if connection.Animated && ((connection.DstArrow == d2target.NoArrowhead && connection.SrcArrow == d2target.NoArrowhead) || (connection.DstArrow != d2target.NoArrowhead && connection.SrcArrow != d2target.NoArrowhead)) {
// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
path1, path2, err := svg.SplitPath(path, 0.5)
if err != nil {
return "", err
}
pathEl1 := d2themes.NewThemableElement("path")
pathEl1.D = path1
pathEl1.Fill = color.None
pathEl1.Stroke = connection.Stroke
pathEl1.ClassName = fmt.Sprintf("connection%s", animatedClass)
pathEl1.Style = connection.CSSStyle()
pathEl1.Style += "animation-direction: reverse;"
pathEl1.Attributes = fmt.Sprintf("%s%s", markerStart, mask)
fmt.Fprint(writer, pathEl1.Render())
pathEl2 := d2themes.NewThemableElement("path")
pathEl2.D = path2
pathEl2.Fill = color.None
pathEl2.Stroke = connection.Stroke
pathEl2.ClassName = fmt.Sprintf("connection%s", animatedClass)
pathEl2.Style = connection.CSSStyle()
pathEl2.Attributes = fmt.Sprintf("%s%s", markerEnd, mask)
fmt.Fprint(writer, pathEl2.Render())
} else {
pathEl := d2themes.NewThemableElement("path") pathEl := d2themes.NewThemableElement("path")
pathEl.D = path pathEl.D = path
pathEl.Fill = color.None pathEl.Fill = color.None
@ -577,6 +606,7 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
pathEl.Attributes = fmt.Sprintf("%s%s%s", markerStart, markerEnd, mask) pathEl.Attributes = fmt.Sprintf("%s%s%s", markerStart, markerEnd, mask)
fmt.Fprint(writer, pathEl.Render()) fmt.Fprint(writer, pathEl.Render())
} }
}
if connection.Label != "" { if connection.Label != "" {
fontClass := "text" fontClass := "text"
@ -588,6 +618,9 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
} else if connection.Italic { } else if connection.Italic {
fontClass += "-italic" fontClass += "-italic"
} }
if connection.Underline {
fontClass += " text-underline"
}
if connection.Fill != color.Empty { if connection.Fill != color.Empty {
rectEl := d2themes.NewThemableElement("rect") rectEl := d2themes.NewThemableElement("rect")
rectEl.X, rectEl.Y = labelTL.X, labelTL.Y rectEl.X, rectEl.Y = labelTL.X, labelTL.Y
@ -1033,7 +1066,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
fmt.Fprint(writer, el.Render()) fmt.Fprint(writer, el.Render())
// TODO should standardize "" to rectangle // TODO should standardize "" to rectangle
case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, "": case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "":
borderRadius := math.MaxFloat64 borderRadius := math.MaxFloat64
if targetShape.BorderRadius != 0 { if targetShape.BorderRadius != 0 {
borderRadius = float64(targetShape.BorderRadius) borderRadius = float64(targetShape.BorderRadius)
@ -1210,7 +1243,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
// Closes the class=shape // Closes the class=shape
fmt.Fprint(writer, `</g>`) fmt.Fprint(writer, `</g>`)
if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage { if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage && targetShape.Opacity != 0 {
iconPosition := label.FromString(targetShape.IconPosition) iconPosition := label.FromString(targetShape.IconPosition)
var box *geo.Box var box *geo.Box
if iconPosition.IsOutside() { if iconPosition.IsOutside() {
@ -1231,7 +1264,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
) )
} }
if targetShape.Label != "" { if targetShape.Label != "" && targetShape.Opacity != 0 {
labelPosition := label.FromString(targetShape.LabelPosition) labelPosition := label.FromString(targetShape.LabelPosition)
var box *geo.Box var box *geo.Box
if labelPosition.IsOutside() { if labelPosition.IsOutside() {
@ -1367,7 +1400,9 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
if targetShape.FontSize != textmeasure.MarkdownFontSize { if targetShape.FontSize != textmeasure.MarkdownFontSize {
styles = append(styles, fmt.Sprintf("font-size:%vpx", targetShape.FontSize)) styles = append(styles, fmt.Sprintf("font-size:%vpx", targetShape.FontSize))
} }
if targetShape.Fill != "" && targetShape.Fill != "transparent" {
styles = append(styles, fmt.Sprintf(`background-color:%s`, targetShape.Fill))
}
if !color.IsThemeColor(targetShape.Color) { if !color.IsThemeColor(targetShape.Color) {
styles = append(styles, fmt.Sprintf(`color:%s`, targetShape.Color)) styles = append(styles, fmt.Sprintf(`color:%s`, targetShape.Color))
} }

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1400 710"><svg id="d2-svg" class="d2-2513870599" width="1400" height="710" viewBox="-101 -101 1400 710"><rect x="-101.000000" y="-101.000000" width="1400.000000" height="710.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2513870599 .text-bold { .d2-2513870599 .text-bold {
font-family: "d2-2513870599-font-bold"; font-family: "d2-2513870599-font-bold";
} }

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 392 820"><svg id="d2-svg" class="d2-2916329547" width="392" height="820" viewBox="-91 -121 392 820"><rect x="-91.000000" y="-121.000000" width="392.000000" height="820.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 392 820"><svg id="d2-svg" class="d2-2916329547" width="392" height="820" viewBox="-91 -121 392 820"><rect x="-91.000000" y="-121.000000" width="392.000000" height="820.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2916329547 .text { .d2-2916329547 .text {
font-family: "d2-2916329547-font-regular"; font-family: "d2-2916329547-font-regular";
} }

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1245 615"><svg id="d2-svg" class="d2-3148583989" width="1245" height="615" viewBox="-91 -81 1245 615"><rect x="-91.000000" y="-81.000000" width="1245.000000" height="615.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-3148583989 .text-bold { .d2-3148583989 .text-bold {
font-family: "d2-3148583989-font-bold"; font-family: "d2-3148583989-font-bold";
} }

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

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

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 634"><svg id="d2-svg" class="d2-914436609" width="350" height="634" viewBox="-91 -121 350 634"><rect x="-91.000000" y="-121.000000" width="350.000000" height="634.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-914436609 .text { .d2-914436609 .text {
font-family: "d2-914436609-font-regular"; font-family: "d2-914436609-font-regular";
} }

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

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

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 985 417"><svg id="d2-svg" class="d2-1533752388" width="985" height="417" viewBox="-101 -101 985 417"><rect x="-101.000000" y="-101.000000" width="985.000000" height="417.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 985 417"><svg id="d2-svg" class="d2-1533752388" width="985" height="417" viewBox="-101 -101 985 417"><rect x="-101.000000" y="-101.000000" width="985.000000" height="417.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1533752388 .text { .d2-1533752388 .text {
font-family: "d2-1533752388-font-regular"; font-family: "d2-1533752388-font-regular";
} }

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 257 455"><svg id="d2-svg" class="d2-2029734873" width="257" height="455" viewBox="-101 -101 257 455"><rect x="-101.000000" y="-101.000000" width="257.000000" height="455.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2029734873 .text-bold { .d2-2029734873 .text-bold {
font-family: "d2-2029734873-font-bold"; font-family: "d2-2029734873-font-bold";
} }

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1178 477"><svg id="d2-svg" class="d2-1098532122" width="1178" height="477" viewBox="-100 -101 1178 477"><rect x="-100.000000" y="-101.000000" width="1178.000000" height="477.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1098532122 .text { .d2-1098532122 .text {
font-family: "d2-1098532122-font-regular"; font-family: "d2-1098532122-font-regular";
} }

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View file

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

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 1068 662"><svg id="d2-svg" class="d2-2504113906" width="1068" height="662" viewBox="-101 -101 1068 662"><rect x="-101.000000" y="-101.000000" width="1068.000000" height="662.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2504113906 .text { .d2-2504113906 .text {
font-family: "d2-2504113906-font-regular"; font-family: "d2-2504113906-font-regular";
} }

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-274840898" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 2801 2344"><svg id="d2-svg" class="d2-274840898" width="2801" height="2344" viewBox="-101 -101 2801 2344"><rect x="-101.000000" y="-101.000000" width="2801.000000" height="2344.000000" rx="0.000000" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-274840898 .text { .d2-274840898 .text {
font-family: "d2-274840898-font-regular"; font-family: "d2-274840898-font-regular";
} }

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -313,9 +313,6 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
if targetShape.Label != "" { if targetShape.Label != "" {
labelPosition := label.FromString(targetShape.LabelPosition) labelPosition := label.FromString(targetShape.LabelPosition)
if !labelPosition.IsOutside() {
continue
}
shapeType := DSL_SHAPE_TO_SHAPE_TYPE[targetShape.Type] shapeType := DSL_SHAPE_TO_SHAPE_TYPE[targetShape.Type]
s := shape.NewShape(shapeType, s := shape.NewShape(shapeType,
@ -881,6 +878,7 @@ const (
ShapeSQLTable = "sql_table" ShapeSQLTable = "sql_table"
ShapeImage = "image" ShapeImage = "image"
ShapeSequenceDiagram = "sequence_diagram" ShapeSequenceDiagram = "sequence_diagram"
ShapeHierarchy = "hierarchy"
) )
var Shapes = []string{ var Shapes = []string{
@ -907,6 +905,7 @@ var Shapes = []string{
ShapeSQLTable, ShapeSQLTable,
ShapeImage, ShapeImage,
ShapeSequenceDiagram, ShapeSequenceDiagram,
ShapeHierarchy,
} }
func IsShape(s string) bool { func IsShape(s string) bool {
@ -974,6 +973,7 @@ var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{
ShapeSQLTable: shape.TABLE_TYPE, ShapeSQLTable: shape.TABLE_TYPE,
ShapeImage: shape.IMAGE_TYPE, ShapeImage: shape.IMAGE_TYPE,
ShapeSequenceDiagram: shape.SQUARE_TYPE, ShapeSequenceDiagram: shape.SQUARE_TYPE,
ShapeHierarchy: shape.SQUARE_TYPE,
} }
var SHAPE_TYPE_TO_DSL_SHAPE map[string]string var SHAPE_TYPE_TO_DSL_SHAPE map[string]string

View file

@ -215,7 +215,7 @@ func (el *ThemableElement) Render() string {
} }
out += " />" out += " />"
if el.FillPattern != "" { if el.FillPattern != "" && el.FillPattern != "none" {
patternEl := el.Copy() patternEl := el.Copy()
patternEl.Fill = "" patternEl.Fill = ""
patternEl.Stroke = "" patternEl.Stroke = ""

View file

@ -68,7 +68,7 @@ git submodule update --recursive
## Logistics ## Logistics
- Use Go 1.20. - Use Go 1.22.
- Please sign your commits - Please sign your commits
([https://github.com/terrastruct/d2/pull/557#issuecomment-1367468730](https://github.com/terrastruct/d2/pull/557#issuecomment-1367468730)). ([https://github.com/terrastruct/d2/pull/557#issuecomment-1367468730](https://github.com/terrastruct/d2/pull/557#issuecomment-1367468730)).
- D2 uses Issues as TODOs. No auto-closing on staleness. - D2 uses Issues as TODOs. No auto-closing on staleness.
@ -100,7 +100,7 @@ running:
./ci/e2ereport.sh -delta ./ci/e2ereport.sh -delta
``` ```
This gives me a nice HMTL output of what the test expected vs what it got (this was a PR This gives me a nice HTML output of what the test expected vs what it got (this was a PR
fixing multi-byte character labels): fixing multi-byte character labels):
![screencapture-file-Users-alexanderwang-dev-alixander-d2-e2etests-out-e2e-report-html-2023-02-14-10_15_07](https://user-images.githubusercontent.com/3120367/218822836-bcc517f2-ae3e-4e0d-83f6-2cbaa2fd9275.png) ![screencapture-file-Users-alexanderwang-dev-alixander-d2-e2etests-out-e2e-report-html-2023-02-14-10_15_07](https://user-images.githubusercontent.com/3120367/218822836-bcc517f2-ae3e-4e0d-83f6-2cbaa2fd9275.png)

232
docs/assets/example.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View file

@ -765,6 +765,18 @@ i used to read
assert.Testdata(t, ".svg", svg) assert.Testdata(t, ".svg", svg)
}, },
}, },
{
name: "theme-pdf",
skipCI: true,
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "in.d2", `x -> y`)
err := runTestMain(t, ctx, dir, env, "--theme=5", "in.d2", "out.pdf")
assert.Success(t, err)
pdf := readFile(t, dir, "out.pdf")
testdataIgnoreDiff(t, ".pdf", pdf)
},
},
{ {
name: "renamed-board", name: "renamed-board",
skipCI: true, skipCI: true,

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-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[ <?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.5-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-1843626214 .text-bold { .d2-1843626214 .text-bold {
font-family: "d2-1843626214-font-bold"; font-family: "d2-1843626214-font-bold";
} }

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-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.5-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-4130279961 .text { .d2-4130279961 .text {
font-family: "d2-4130279961-font-regular"; font-family: "d2-4130279961-font-regular";
} }

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-3109420268" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 290 268"><svg id="d2-svg" class="d2-3109420268" 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-3109420268 .text-bold { .d2-3109420268 .text-bold {
font-family: "d2-3109420268-font-bold"; font-family: "d2-3109420268-font-bold";
} }

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-4088621414" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 305 285"><svg id="d2-svg" class="d2-4088621414" 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));
} }

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-1416247347" 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.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 304 285"><svg id="d2-svg" class="d2-1416247347" 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));
} }

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-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[ <?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.5-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-1843626214 .text-bold { .d2-1843626214 .text-bold {
font-family: "d2-1843626214-font-bold"; font-family: "d2-1843626214-font-bold";
} }

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-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[ <?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.5-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-3054270525 .text-bold { .d2-3054270525 .text-bold {
font-family: "d2-3054270525-font-bold"; font-family: "d2-3054270525-font-bold";
} }

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.3-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 368 766"><svg id="d2-svg" width="368" height="766" viewBox="-101 -101 368 766"><style type="text/css"><![CDATA[ <?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" d2Version="v0.6.5-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 368 766"><svg id="d2-svg" width="368" height="766" viewBox="-101 -101 368 766"><style type="text/css"><![CDATA[
.d2-1574744994 .text-bold { .d2-1574744994 .text-bold {
font-family: "d2-1574744994-font-bold"; font-family: "d2-1574744994-font-bold";
} }

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

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