diff --git a/README.md b/README.md index c2b69f7d6..3293a0cdd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ A modern diagram scripting language that turns text to diagrams. -[Language docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) + +[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) [![ci](https://github.com/terrastruct/d2/actions/workflows/ci.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/ci.yml) [![daily](https://github.com/terrastruct/d2/actions/workflows/daily.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/daily.yml) @@ -13,6 +14,10 @@ [![twitter](https://img.shields.io/twitter/follow/terrastruct?style=social)](https://twitter.com/terrastruct) [![license](https://img.shields.io/github/license/terrastruct/d2?color=9cf)](./LICENSE.txt) + +D2 Playground button + + https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8f85-0bbfca0c6e2a.mp4 @@ -33,9 +38,8 @@ https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8 - Contributing - License - Related - - VSCode extension - - Vim extension - - Language docs + - Official plugins + - Community plugins - Misc - FAQ @@ -206,13 +210,11 @@ let us know and we'll be happy to include it here! - **Telegram bot**: [https://github.com/meinside/telegram-d2-bot](https://github.com/meinside/telegram-d2-bot) - **Postgres importer**: [https://github.com/zekenie/d2-erd-from-postgres](https://github.com/zekenie/d2-erd-from-postgres) -### Language docs - -[https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs) - ### Misc -- [https://github.com/terrastruct/text-to-diagram-site](https://github.com/terrastruct/text-to-diagram-site) +- **Comparison site**: [https://github.com/terrastruct/text-to-diagram-site](https://github.com/terrastruct/text-to-diagram-site) +- **Playground**: [https://github.com/terrastruct/d2-playground](https://github.com/terrastruct/d2-playground) +- **Language docs**: [https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs) ## FAQ diff --git a/ci/release/Dockerfile b/ci/release/Dockerfile new file mode 100644 index 000000000..d195fa05b --- /dev/null +++ b/ci/release/Dockerfile @@ -0,0 +1,18 @@ +# https://hub.docker.com/repository/docker/terrastruct/d2 +FROM debian:latest + +ARG TARGETARCH + +COPY ./d2-*-linux-$TARGETARCH.tar.gz /tmp +RUN mkdir -p /usr/local/lib/d2 \ + && tar -C /usr/local/lib/d2 -xzf /tmp/d2-*-linux-"$TARGETARCH".tar.gz \ + && /usr/local/lib/d2/d2-*/scripts/install.sh \ + && rm -Rf /tmp/d2-*-linux-"$TARGETARCH".tar.gz + +WORKDIR /root/src +EXPOSE 8080 +ENV PORT 8080 +ENV HOST 0.0.0.0 +ENV BROWSER false + +ENTRYPOINT ["/usr/local/bin/d2"] diff --git a/ci/release/README.md b/ci/release/README.md index 4530983e4..4f8b1a56d 100644 --- a/ci/release/README.md +++ b/ci/release/README.md @@ -2,7 +2,7 @@ ## _install.sh -The template for the install script in the root of the repository. +The template for the install script in the root of the d2 repository. ### gen_install.sh @@ -23,12 +23,12 @@ it depends on from ../sub/lib. > variables as we must compile d2 directly on each release target to include dagre. > See https://github.com/terrastruct/d2/issues/31 -Use `--host-only` to build only the release for the host's OS-ARCH pair. +Use `--host-only` to build only the release for the host's `$OS-$ARCH` pair. ### build_docker.sh Helper script called by build.sh to build D2 on each linux runner inside Docker. -The Dockerfile is in ./builders/Dockerfile +The Dockerfile is in ./linux/Dockerfile ### _build.sh diff --git a/ci/release/aws/ensure.sh b/ci/release/aws/ensure.sh new file mode 100755 index 000000000..d78d58162 --- /dev/null +++ b/ci/release/aws/ensure.sh @@ -0,0 +1,545 @@ +#!/bin/sh +set -eu +. "$(dirname "$0")/../../../ci/sub/lib.sh" +cd -- "$(dirname "$0")/../../.." + +help() { + cat </dev/null \ + | jq -r .SecurityGroups[0].GroupId) + if [ -z "$SG_ID" ]; then + SG_ID=$(sh_c aws ec2 create-security-group \ + --group-name ssh \ + --description ssh \ + --vpc-id "$VPC_ID" | jq -r .GroupId) + fi + + header security-group-ingress + SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names ssh \ + | jq -r '.SecurityGroups[0].IpPermissions | length') + if [ "$SG_RULES_COUNT" -eq 0 ]; then + sh_c aws ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 >/dev/null + fi + + header windows-security-group + SG_ID=$(aws ec2 describe-security-groups --group-names windows 2>/dev/null \ + | jq -r .SecurityGroups[0].GroupId) + if [ -z "$SG_ID" ]; then + SG_ID=$(sh_c aws ec2 create-security-group \ + --group-name windows \ + --description windows \ + --vpc-id "$VPC_ID" | jq -r .GroupId) + fi + + header windows-security-group-ingress + SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names windows \ + | jq -r '.SecurityGroups[0].IpPermissions | length') + if [ "$SG_RULES_COUNT" -ne 2 ]; then + sh_c aws ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 >/dev/null + sh_c aws ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --protocol tcp \ + --port 3389 \ + --cidr 0.0.0.0/0 >/dev/null + fi +} + +create_linux_amd64() { + header linux-amd64 + REMOTE_NAME=ci-d2-linux-amd64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-linux-amd64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0ecc74eca1d66d8a6 \ + --count=1 \ + --instance-type=t3.small \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \ + --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-linux-amd64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-linux-amd64}]"' >/dev/null + fi + wait_remote_host_ip + log "CI_D2_LINUX_AMD64=ubuntu@$ip" + export CI_D2_LINUX_AMD64=ubuntu@$ip +} + +create_linux_arm64() { + header linux-arm64 + REMOTE_NAME=ci-d2-linux-arm64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-linux-arm64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-06e2dea2cdda3acda \ + --count=1 \ + --instance-type=t4g.small \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \ + --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-linux-arm64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-linux-arm64}]"' >/dev/null + fi + wait_remote_host_ip + log "CI_D2_LINUX_ARM64=ubuntu@$ip" + export CI_D2_LINUX_ARM64=ubuntu@$ip +} + +create_macos_amd64() { + header macos-amd64-host + MACOS_AMD64_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=ci-d2-macos-amd64' | jq -r '.Hosts[].HostId') + if [ -z "$MACOS_AMD64_ID" ]; then + MACOS_AMD64_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac1.metal --quantity 1 --availability-zone us-west-2a \ + --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' \ + | jq -r .HostIds[0]) + fi + + header macos-amd64 + REMOTE_NAME=ci-d2-macos-amd64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-macos-amd64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0dd2ded7568750663 \ + --count=1 \ + --instance-type=mac1.metal \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \ + --placement "Tenancy=host,HostId=$MACOS_AMD64_ID" \ + --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=100,VolumeType=gp3}"' \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' >/dev/null + fi + wait_remote_host_ip + log "CI_D2_MACOS_AMD64=ec2-user@$ip" + export CI_D2_MACOS_AMD64=ec2-user@$ip +} + +create_macos_arm64() { + header macos-arm64-host + MACOS_ARM64_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=ci-d2-macos-arm64' | jq -r '.Hosts[].HostId') + if [ -z "$MACOS_ARM64_ID" ]; then + MACOS_ARM64_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac2.metal --quantity 1 --availability-zone us-west-2a \ + --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' \ + | jq -r .HostIds[0]) + fi + + header macos-arm64 + REMOTE_NAME=ci-d2-macos-arm64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-macos-arm64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0af0516ff2c43dbbe \ + --count=1 \ + --instance-type=mac2.metal \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \ + --placement "Tenancy=host,HostId=$MACOS_ARM64_ID" \ + --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=100,VolumeType=gp3}"' \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' >/dev/null + fi + wait_remote_host_ip + log "CI_D2_MACOS_ARM64=ec2-user@$ip" + export CI_D2_MACOS_ARM64=ec2-user@$ip +} + +create_windows_amd64() { + header windows-amd64 + REMOTE_NAME=ci-d2-windows-amd64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0c5300e833c2b32f3 \ + --count=1 \ + --instance-type=t3.medium \ + --security-groups=windows \ + "--key-name=$KEY_NAME_WINDOWS" \ + --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \ + --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \ + --tag-specifications "'ResourceType=instance,Tags=[{Key=Name,Value=$REMOTE_NAME}]'" \ + "'ResourceType=volume,Tags=[{Key=Name,Value=$REMOTE_NAME}]'" >/dev/null + fi + wait_remote_host_ip + log "CI_D2_WINDOWS_AMD64=Administrator@$ip" + export CI_D2_WINDOWS_AMD64=Administrator@$ip +} + +wait_remote_host_ip() { + while true; do + ip=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ + | jq -r '.Reservations[].Instances[].PublicIpAddress') + if [ -n "$ip" ]; then + alloc_static_ip + ip=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ + | jq -r '.Reservations[].Instances[].PublicIpAddress') + ssh-keygen -R "$ip" + break + fi + sleep 5 + done +} + +alloc_static_ip() { + allocation_id=$(aws ec2 describe-addresses --filters "Name=tag:Name,Values=$REMOTE_NAME" | jq -r '.Addresses[].AllocationId') + if [ -z "$allocation_id" ]; then + sh_c aws ec2 allocate-address --tag-specifications "'ResourceType=elastic-ip,Tags=[{Key=Name,Value=$REMOTE_NAME}]'" + allocation_id=$(aws ec2 describe-addresses --filters "Name=tag:Name,Values=$REMOTE_NAME" | jq -r '.Addresses[].AllocationId') + fi + + instance_id=$(aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ + | jq -r '.Reservations[].Instances[].InstanceId') + aws ec2 associate-address --instance-id "$instance_id" --allocation-id "$allocation_id" +} + +init_remote_hosts() { + bigheader init_remote_hosts + + JOBNAME=$JOBNAME/linux/amd64 runjob_filter REMOTE_HOST=$CI_D2_LINUX_AMD64 REMOTE_NAME=ci-d2-linux-amd64 init_remote_linux + JOBNAME=$JOBNAME/linux/arm64 runjob_filter REMOTE_HOST=$CI_D2_LINUX_ARM64 REMOTE_NAME=ci-d2-linux-arm64 init_remote_linux + JOBNAME=$JOBNAME/macos/amd64 runjob_filter REMOTE_HOST=$CI_D2_MACOS_AMD64 REMOTE_NAME=ci-d2-macos-amd64 init_remote_macos + JOBNAME=$JOBNAME/macos/arm64 runjob_filter REMOTE_HOST=$CI_D2_MACOS_ARM64 REMOTE_NAME=ci-d2-macos-arm64 init_remote_macos + JOBNAME=$JOBNAME/windows/amd64 runjob_filter REMOTE_HOST=$CI_D2_WINDOWS_AMD64 REMOTE_NAME=ci-d2-windows-amd64 init_remote_windows + + # Windows and AWS SSM both defeated me. + FGCOLOR=3 bigheader "WARNING: WINDOWS INITIALIZATION MUST BE COMPLETED MANUALLY OVER RDP AND POWERSHELL!" +} + +init_remote_linux() { + header "$REMOTE_NAME" + wait_remote_host + + sh_c ssh_copy_id -i="$ID_PUB_PATH" "$REMOTE_HOST" + + sh_c ssh "$REMOTE_HOST" sh -s -- < /dev/null +sudo -E apt-get update -y +sudo -E apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +sudo groupadd docker || true +sudo usermod -aG docker \$USER + +mkdir -p \$HOME/.local/bin +mkdir -p \$HOME/.local/share/man +EOF + init_remote_env + + sh_c ssh "$REMOTE_HOST" sh -s -- <> ~/.zshrc\"" + fi + if ! sh_c ssh "$REMOTE_HOST" "'grep -qF \\\$HOME/.local ~/.zshrc'"; then + sh_c ssh "$REMOTE_HOST" "\"(echo && cat) >> ~/.zshrc\"" <\$HOME/.ssh/environment"' + sh_c ssh "$REMOTE_HOST" '"echo MANPATH=\$(echo \"echo \\\$MANPATH\" | \"\$SHELL\" -ils) >>\$HOME/.ssh/environment"' + + sh_c ssh "$REMOTE_HOST" "sudo sed -i.bak '\"s/#PermitUserEnvironment no/PermitUserEnvironment yes/\"' /etc/ssh/sshd_config" + + if sh_c ssh "$REMOTE_HOST" uname | grep -qF Darwin; then + sh_c ssh "$REMOTE_HOST" "sudo launchctl stop com.openssh.sshd" + else + sh_c ssh "$REMOTE_HOST" "sudo systemctl restart sshd" + # ubuntu has $PATH hard coded in /etc/environment for some reason. It takes precedence + # over ~/.ssh/environment. + sh_c ssh "$REMOTE_HOST" "sudo rm /etc/environment" + fi +} + +wait_remote_host() { + while true; do + if sh_c ssh "$REMOTE_HOST" true; then + break + fi + sleep 5 + done +} + +wait_remote_host_windows() { + instance_id=$(aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \ + | jq -r '.Reservations[].Instances[].InstanceId') + + while true; do + if sh_c aws ssm start-session --target "$instance_id" \ + --document-name 'AWS-StartNonInteractiveCommand' \ + --parameters "'{\"command\": [\"echo true\"]}'"; then + break + fi + sleep 5 + done +} + +init_remote_windows() { + header "$REMOTE_NAME" + wait_remote_host_windows + + init_ps1=$(cat < utf8: https://stackoverflow.com/a/34969243/4283659 +\$null = New-Item -Force "\$env:ProgramData\ssh\administrators_authorized_keys" -Value (Get-Content -Path "\$env:ProgramData\ssh\administrators_authorized_keys" | Out-String) +get-acl "\$env:ProgramData\ssh\ssh_host_rsa_key" | set-acl "\$env:ProgramData\ssh\administrators_authorized_keys" + +if (-Not (Test-Path -Path C:\msys64)) { + Invoke-WebRequest -Uri "https://github.com/msys2/msys2-installer/releases/download/2022-10-28/msys2-x86_64-20221028.exe" -OutFile "./msys2-x86_64.exe" + ./msys2-x86_64.exe install --default-answer --confirm-command --root C:\msys64 +} +C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'pacman -Sy --noconfirm base-devel vim rsync man' +C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'curl -fsSL https://d2lang.com/install.sh | sh -s -- --tala' +C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'd2 --version' + +\$path = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path +if (\$path -notlike '*C:\msys64\usr\bin*') { + \$path = "\$path;C:\msys64\usr\bin" + Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path -Value \$path +} +if (\$path -notlike '*C:\msys64\usr\local\bin*') { + \$path = "\$path;C:\msys64\usr\local\bin" + Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path -Value \$path +} +(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path + +Restart-Computer +EOF + +# To run a POSIX script: +# ssh "$CI_D2_WINDOWS_AMD64" sh -s -- < utf8: https://stackoverflow.com/a/34969243/4283659 +# \$null = New-Item -Force C:\msys64\sshd_default_shell.cmd -Value (Get-Content -Path C:\msys64\sshd_default_shell.cmd | Out-String) +# Set-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell -Value C:\msys64\sshd_default_shell.cmd +# EOF +# +# To undo: +# <&2 + warn "2. RDP into $REMOTE_HOST and open PowerShell." + warn '3. Generate and execute C:\Users\Administrator\Desktop\init.ps1 with:' + printf '%s\n' "$gen_init_ps1" >&2 + warn '4. Run the following to be notified once installation is successful:' + cat <$rsync_files + sh_c rsync --archive --human-readable --files-from "$rsync_files" "'$REMOTE_HOST:windows\\'" "./ci/release/build/$VERSION/d2-$VERSION-$OS-$ARCH.msi" } main "$@" diff --git a/ci/release/build/.dockerignore b/ci/release/build/.dockerignore new file mode 100644 index 000000000..73f0caa06 --- /dev/null +++ b/ci/release/build/.dockerignore @@ -0,0 +1 @@ +**/d2*/ diff --git a/ci/release/build_docker.sh b/ci/release/build_in_docker.sh similarity index 81% rename from ci/release/build_docker.sh rename to ci/release/build_in_docker.sh index 2939c5dd0..b8a4f1f6c 100755 --- a/ci/release/build_docker.sh +++ b/ci/release/build_in_docker.sh @@ -5,7 +5,7 @@ cd -- "$(dirname "$0")/../.." tag="$(sh_c docker build \ --build-arg GOVERSION="1.19.3.linux-$ARCH" \ - -qf ./ci/release/builders/Dockerfile ./ci/release/builders)" + -qf ./ci/release/linux/Dockerfile ./ci/release/linux)" docker_run \ -e DRY_RUN \ -e HW_BUILD_DIR \ diff --git a/ci/release/builders/Dockerfile-centos b/ci/release/builders/Dockerfile-centos deleted file mode 100644 index 66aaf6f17..000000000 --- a/ci/release/builders/Dockerfile-centos +++ /dev/null @@ -1,16 +0,0 @@ -FROM centos:7 - -ARG GOVERSION= - -RUN curl -fsSL "https://go.dev/dl/go$GOVERSION.tar.gz" >/tmp/go.tar.gz -RUN tar -C /usr/local -xzf /tmp/go.tar.gz - -ENV PATH="/usr/local/go/bin:$PATH" - -RUN yum install -y rsync wget -RUN yum groupinstall -y 'Development Tools' - -RUN curl -fsSL https://ftp.gnu.org/gnu/gcc/gcc-5.2.0/gcc-5.2.0.tar.gz >/tmp/gcc.tar.gz -RUN tar -C /usr/local -xzf /tmp/gcc.tar.gz -RUN cd /usr/local/gcc-5.2.0 && ./contrib/download_prerequisites && mkdir -p build \ - && cd build && ../configure --disable-multilib && make && make install diff --git a/ci/release/builders/aws_copy_keys.sh b/ci/release/builders/aws_copy_keys.sh deleted file mode 100755 index cc0d548ef..000000000 --- a/ci/release/builders/aws_copy_keys.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -set -eu -cd -- "$(dirname "$0")/../../.." -. ./ci/sub/lib.sh - -help() { - cat < .ssh/authorized_keys.dedup' - sh_c ssh "$REMOTE_HOST" 'cp .ssh/authorized_keys.dedup .ssh/authorized_keys' - sh_c ssh "$REMOTE_HOST" 'rm .ssh/authorized_keys.dedup' -} - -main "$@" diff --git a/ci/release/builders/aws_ensure.sh b/ci/release/builders/aws_ensure.sh deleted file mode 100755 index 63c117abc..000000000 --- a/ci/release/builders/aws_ensure.sh +++ /dev/null @@ -1,294 +0,0 @@ -#!/bin/sh -set -eu -cd -- "$(dirname "$0")/../../.." -. ./ci/sub/lib.sh - -help() { - cat </dev/null \ - | jq -r .SecurityGroups[0].GroupId) - if [ -z "$SG_ID" ]; then - SG_ID=$(sh_c aws ec2 create-security-group \ - --group-name ssh \ - --description ssh \ - --vpc-id "$VPC_ID" | jq -r .GroupId) - fi - - header security-group-ingress - SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names ssh \ - | jq -r '.SecurityGroups[0].IpPermissions | length') - if [ "$SG_RULES_COUNT" -eq 0 ]; then - sh_c aws ec2 authorize-security-group-ingress \ - --group-id "$SG_ID" \ - --protocol tcp \ - --port 22 \ - --cidr 0.0.0.0/0 >/dev/null - fi - - header linux-amd64 - state=$(aws ec2 describe-instances --filters \ - 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-amd64' \ - | jq -r '.Reservations[].Instances[].State.Name') - if [ -z "$state" ]; then - sh_c aws ec2 run-instances \ - --image-id=ami-071e6cafc48327ca2 \ - --count=1 \ - --instance-type=t2.small \ - --security-groups=ssh \ - "--key-name=$KEY_NAME" \ - --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-linux-amd64}]"' \ - '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-linux-amd64}]"' >/dev/null - fi - while true; do - dnsname=$(sh_c aws ec2 describe-instances \ - --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-amd64' \ - | jq -r '.Reservations[].Instances[].PublicDnsName') - if [ -n "$dnsname" ]; then - log "TSTRUCT_LINUX_AMD64_BUILDER=admin@$dnsname" - export TSTRUCT_LINUX_AMD64_BUILDER=admin@$dnsname - break - fi - sleep 5 - done - - header linux-arm64 - state=$(aws ec2 describe-instances --filters \ - 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-arm64' \ - | jq -r '.Reservations[].Instances[].State.Name') - if [ -z "$state" ]; then - sh_c aws ec2 run-instances \ - --image-id=ami-0e67506f183e5ab60 \ - --count=1 \ - --instance-type=t4g.small \ - --security-groups=ssh \ - "--key-name=$KEY_NAME" \ - --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-linux-arm64}]"' \ - '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-linux-arm64}]"' >/dev/null - fi - while true; do - dnsname=$(sh_c aws ec2 describe-instances \ - --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-arm64' \ - | jq -r '.Reservations[].Instances[].PublicDnsName') - if [ -n "$dnsname" ]; then - log "TSTRUCT_LINUX_ARM64_BUILDER=admin@$dnsname" - export TSTRUCT_LINUX_ARM64_BUILDER=admin@$dnsname - break - fi - sleep 5 - done - - header "macos-amd64-host" - MACOS_AMD64_HOST_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=d2-builder-macos-amd64' | jq -r '.Hosts[].HostId') - if [ -z "$MACOS_AMD64_HOST_ID" ]; then - MACOS_AMD64_HOST_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac1.metal --quantity 1 --availability-zone us-west-2a \ - --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ - | jq -r .HostIds[0]) - fi - - header "macos-arm64-host" - MACOS_ARM64_HOST_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=d2-builder-macos-arm64' | jq -r '.Hosts[].HostId') - if [ -z "$MACOS_ARM64_HOST_ID" ]; then - MACOS_ARM64_HOST_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac2.metal --quantity 1 --availability-zone us-west-2a \ - --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ - | jq -r .HostIds[0]) - fi - - header macos-amd64 - state=$(aws ec2 describe-instances --filters \ - 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-amd64' \ - | jq -r '.Reservations[].Instances[].State.Name') - if [ -z "$state" ]; then - sh_c aws ec2 run-instances \ - --image-id=ami-0dd2ded7568750663 \ - --count=1 \ - --instance-type=mac1.metal \ - --security-groups=ssh \ - "--key-name=$KEY_NAME" \ - --placement "Tenancy=host,HostId=$MACOS_AMD64_HOST_ID" \ - --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ - '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' >/dev/null - fi - while true; do - dnsname=$(sh_c aws ec2 describe-instances \ - --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-amd64' \ - | jq -r '.Reservations[].Instances[].PublicDnsName') - if [ -n "$dnsname" ]; then - log "TSTRUCT_MACOS_AMD64_BUILDER=ec2-user@$dnsname" - export TSTRUCT_MACOS_AMD64_BUILDER=ec2-user@$dnsname - break - fi - sleep 5 - done - - header macos-arm64 - state=$(aws ec2 describe-instances --filters \ - 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-arm64' \ - | jq -r '.Reservations[].Instances[].State.Name') - if [ -z "$state" ]; then - sh_c aws ec2 run-instances \ - --image-id=ami-0af0516ff2c43dbbe \ - --count=1 \ - --instance-type=mac2.metal \ - --security-groups=ssh \ - "--key-name=$KEY_NAME" \ - --placement "Tenancy=host,HostId=$MACOS_ARM64_HOST_ID" \ - --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-macos-arm64}]"' \ - '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-macos-arm64}]"' >/dev/null - fi - while true; do - dnsname=$(sh_c aws ec2 describe-instances \ - --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-arm64' \ - | jq -r '.Reservations[].Instances[].PublicDnsName') - if [ -n "$dnsname" ]; then - log "TSTRUCT_MACOS_ARM64_BUILDER=ec2-user@$dnsname" - export TSTRUCT_MACOS_ARM64_BUILDER=ec2-user@$dnsname - break - fi - sleep 5 - done -} - -init_remote_hosts() { - header linux-amd64 - REMOTE_HOST=$TSTRUCT_LINUX_AMD64_BUILDER init_remote_linux - header linux-arm64 - REMOTE_HOST=$TSTRUCT_LINUX_ARM64_BUILDER init_remote_linux - header macos-amd64 - REMOTE_HOST=$TSTRUCT_MACOS_AMD64_BUILDER init_remote_macos - header macos-arm64 - REMOTE_HOST=$TSTRUCT_MACOS_ARM64_BUILDER init_remote_macos - - FGCOLOR=2 header summary - log "export TSTRUCT_LINUX_AMD64_BUILDER=$TSTRUCT_LINUX_AMD64_BUILDER" - log "export TSTRUCT_LINUX_ARM64_BUILDER=$TSTRUCT_LINUX_ARM64_BUILDER" - log "export TSTRUCT_MACOS_AMD64_BUILDER=$TSTRUCT_MACOS_AMD64_BUILDER" - log "export TSTRUCT_MACOS_ARM64_BUILDER=$TSTRUCT_MACOS_ARM64_BUILDER" -} - -init_remote_linux() { - wait_remote_host - - sh_c ssh "$REMOTE_HOST" sh -s -- < /dev/null -sudo -E apt-get update -y -sudo -E apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin -sudo groupadd docker || true -sudo usermod -aG docker \$USER - -mkdir -p \$HOME/.local/bin -mkdir -p \$HOME/.local/share/man -EOF - init_remote_env - - sh_c ssh "$REMOTE_HOST" 'sudo reboot' || true -} - -init_remote_macos() { - wait_remote_host - - sh_c ssh "$REMOTE_HOST" '"/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""' - - if sh_c ssh "$REMOTE_HOST" uname -m | grep -qF arm64; then - shellenv=$(sh_c ssh "$REMOTE_HOST" /opt/homebrew/bin/brew shellenv) - else - shellenv=$(sh_c ssh "$REMOTE_HOST" /usr/local/bin/brew shellenv) - fi - if ! echo "$shellenv" | sh_c ssh "$REMOTE_HOST" "IFS= read -r regex\; \"grep -qF \\\"\\\$regex\\\" ~/.zshrc\""; then - echo "$shellenv" | sh_c ssh "$REMOTE_HOST" "\"(echo && cat) >> ~/.zshrc\"" - fi - if ! sh_c ssh "$REMOTE_HOST" "'grep -qF \\\$HOME/.local ~/.zshrc'"; then - sh_c ssh "$REMOTE_HOST" "\"(echo && cat) >> ~/.zshrc\"" <\$HOME/.ssh/environment"' - sh_c ssh "$REMOTE_HOST" '"echo MANPATH=\$(echo \"echo \\\$MANPATH\" | \"\$SHELL\" -ils) >>\$HOME/.ssh/environment"' - - sh_c ssh "$REMOTE_HOST" "sudo sed -i.bak '\"s/#PermitUserEnvironment no/PermitUserEnvironment yes/\"' /etc/ssh/sshd_config" - - if sh_c ssh "$REMOTE_HOST" uname | grep -qF Darwin; then - sh_c ssh "$REMOTE_HOST" "sudo launchctl stop com.openssh.sshd" - else - sh_c ssh "$REMOTE_HOST" "sudo systemctl restart sshd" - fi -} - -wait_remote_host() { - while true; do - if sh_c ssh "$REMOTE_HOST" true; then - break - fi - sleep 5 - done -} - -main "$@" diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 5ea2ca695..28e0ab2af 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,18 +1,20 @@ #### Features ๐Ÿš€ -- Diagram padding can now can be configured in the CLI (default 100px). - [https://github.com/terrastruct/d2/pull/431](https://github.com/terrastruct/d2/pull/431) +- Diagram padding can now can be configured in the CLI (default 100px). [https://github.com/terrastruct/d2/pull/431](https://github.com/terrastruct/d2/pull/431) +- Connection label backgrounds can now be set with the `style.fill` keyword. [https://github.com/terrastruct/d2/pull/452](https://github.com/terrastruct/d2/pull/452) +- Add official Docker image. See [./docs/INSTALL.md#docker](./docs/INSTALL.md#docker). [#76](https://github.com/terrastruct/d2/issues/76) +- Add `.msi` installer for convenient installation on Windows. [#379](https://github.com/terrastruct/d2/issues/379) #### Improvements ๐Ÿงน -- Fmt now preserves leading comment spacing. - [#400](https://github.com/terrastruct/d2/issues/400) +- `d2 fmt` now preserves leading comment spacing. [#400](https://github.com/terrastruct/d2/issues/400) +- Markdown can now be styled with keywords stroke and fill. [https://github.com/terrastruct/d2/pull/460](https://github.com/terrastruct/d2/pull/460) #### Bugfixes โ›‘๏ธ -- Fixed crash when sequence diagrams had no messages. - [https://github.com/terrastruct/d2/pull/427](https://github.com/terrastruct/d2/pull/427) -- Fixed `constraint` keyword setting label. - [https://github.com/terrastruct/d2/issues/415](https://github.com/terrastruct/d2/issues/415) -- Fixed serialization affecting binary plugins (TALA). - [https://github.com/terrastruct/d2/pull/426](https://github.com/terrastruct/d2/pull/426) +- Fixed crash when sequence diagrams had no messages. [https://github.com/terrastruct/d2/pull/427](https://github.com/terrastruct/d2/pull/427) +- Fixed `constraint` keyword setting label. [https://github.com/terrastruct/d2/issues/415](https://github.com/terrastruct/d2/issues/415) +- Fixed serialization affecting binary plugins (TALA). [https://github.com/terrastruct/d2/pull/426](https://github.com/terrastruct/d2/pull/426) +- Fixed connections in elk layouts not going all the way to shape borders. [https://github.com/terrastruct/d2/pull/459](https://github.com/terrastruct/d2/pull/459) +- Fixed a connection rendering bug that could happen in firefox when there were no connection labels. [https://github.com/terrastruct/d2/pull/453](https://github.com/terrastruct/d2/pull/453) +- Fixed a crash when external connection IDs were prefixes of a sequence diagram ID. [https://github.com/terrastruct/d2/pull/462](https://github.com/terrastruct/d2/pull/462) diff --git a/ci/release/builders/Dockerfile b/ci/release/linux/Dockerfile similarity index 77% rename from ci/release/builders/Dockerfile rename to ci/release/linux/Dockerfile index 5adc0bd25..5bc66a568 100644 --- a/ci/release/builders/Dockerfile +++ b/ci/release/linux/Dockerfile @@ -8,5 +8,5 @@ RUN curl -fsSL "https://go.dev/dl/go$GOVERSION.tar.gz" >/tmp/go.tar.gz RUN tar -C /usr/local -xzf /tmp/go.tar.gz ENV PATH="/usr/local/go/bin:$PATH" -RUN apt-get install -y build-essential -RUN apt-get install -y rsync +RUN apt-get install -y build-essential \ + rsync diff --git a/ci/release/template/README.md.sh b/ci/release/template/README.md.sh index 7ccea6fb2..d08a71bf6 100755 --- a/ci/release/template/README.md.sh +++ b/ci/release/template/README.md.sh @@ -20,27 +20,11 @@ EOF if [ "$OS" = windows ]; then cat <\` - -For installation you'll have to add the \`./bin/d2.exe\` binary to your \`\$PATH\`. Or add -the \`./bin\` directory of this release to your \`\$PATH\`. - -See https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows - -Then you'll be able to call \`d2\` from the commandline in \`cmd.exe\` or \`pwsh.exe\`. - -We intend to have a \`.msi\` release installer sometime soon that handles putting \`d2.exe\` into +You may find our \`.msi\` installer more convenient as it handles putting \`d2.exe\` into your \`\$PATH\` for you. -You can also use \`make install\` to install on Windows after first installing -[MSYS2](https://www.msys2.org/) which emulates a Linux shell for Windows. Its terminal -also enables \`d2\` to show colors in its output. The manpage will also become accessible -with \`man d2\`. - See https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md#windows EOF fi diff --git a/ci/release/template/scripts/lib.sh b/ci/release/template/scripts/lib.sh index 62ade2f89..699795908 100644 --- a/ci/release/template/scripts/lib.sh +++ b/ci/release/template/scripts/lib.sh @@ -273,7 +273,7 @@ header() { bigheader() { set -- "$(echo "$*" | sed "s/^/ * /")" - FGCOLOR=${FGCOLOR:-3} logp "/**************************************************************** + FGCOLOR=${FGCOLOR:-6} logp "/**************************************************************** $* ****************************************************************/" } diff --git a/ci/release/windows/.gitignore b/ci/release/windows/.gitignore new file mode 100644 index 000000000..55ae023fc --- /dev/null +++ b/ci/release/windows/.gitignore @@ -0,0 +1 @@ +d2.exe diff --git a/ci/release/windows/d2.ico b/ci/release/windows/d2.ico new file mode 100644 index 000000000..bc8335225 Binary files /dev/null and b/ci/release/windows/d2.ico differ diff --git a/ci/release/windows/d2.png b/ci/release/windows/d2.png new file mode 100644 index 000000000..ba83fed7a Binary files /dev/null and b/ci/release/windows/d2.png differ diff --git a/ci/release/windows/d2.wxs b/ci/release/windows/d2.wxs new file mode 100644 index 000000000..1b7b46631 --- /dev/null +++ b/ci/release/windows/d2.wxs @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ci/sub b/ci/sub index 7923b353a..2db232e4a 160000 --- a/ci/sub +++ b/ci/sub @@ -1 +1 @@ -Subproject commit 7923b353a829cf289171baff0d0d017ae3d32278 +Subproject commit 2db232e4aa5225e22f95ff91914f91b59fe48d07 diff --git a/d2exporter/export.go b/d2exporter/export.go index 1f2a15b8d..952a85662 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -45,6 +45,8 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) { } if obj.Attributes.Style.Fill != nil { shape.Fill = obj.Attributes.Style.Fill.Value + } else if obj.Attributes.Shape.Value == d2target.ShapeText { + shape.Fill = "transparent" } if obj.Attributes.Style.Stroke != nil { shape.Stroke = obj.Attributes.Style.Stroke.Value @@ -200,6 +202,10 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection connection.StrokeWidth, _ = strconv.Atoi(edge.Attributes.Style.StrokeWidth.Value) } + if edge.Attributes.Style.Fill != nil { + connection.Fill = edge.Attributes.Style.Fill.Value + } + connection.FontSize = text.FontSize if edge.Attributes.Style.FontSize != nil { connection.FontSize, _ = strconv.Atoi(edge.Attributes.Style.FontSize.Value) diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 838ffe6d7..528df55c1 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -397,7 +397,10 @@ func (obj *Object) GetFill(theme *d2themes.Theme) string { func (obj *Object) GetStroke(theme *d2themes.Theme, dashGapSize interface{}) string { shape := obj.Attributes.Shape.Value - if strings.EqualFold(shape, d2target.ShapeCode) || strings.EqualFold(shape, d2target.ShapeClass) || strings.EqualFold(shape, d2target.ShapeSQLTable) { + if strings.EqualFold(shape, d2target.ShapeCode) || + strings.EqualFold(shape, d2target.ShapeClass) || + strings.EqualFold(shape, d2target.ShapeSQLTable) || + strings.EqualFold(shape, d2target.ShapeText) { return theme.Colors.Neutrals.N1 } if dashGapSize != 0.0 { diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 63b26c95f..dce31bc8a 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -10,6 +10,7 @@ import ( "encoding/json" "fmt" "math" + "strings" "github.com/dop251/goja" @@ -21,6 +22,7 @@ import ( "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/d2/lib/shape" ) //go:embed elk.js @@ -289,6 +291,14 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { }) } + startIndex, endIndex := 0, len(points)-1 + srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Attributes.Shape.Value)], edge.Src.Box) + dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Attributes.Shape.Value)], edge.Dst.Box) + + // trace the edge to the specific shape's border + points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1]) + points[endIndex] = shape.TraceToShapeBorder(dstShape, points[endIndex], points[endIndex-1]) + if edge.Attributes.Label.Value != "" { edge.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 42efeb174..55192ceab 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -88,7 +88,7 @@ func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) (*sequenceDiag var edges []*d2graph.Edge for _, edge := range g.Edges { // both Src and Dst must be inside the sequence diagram - if strings.HasPrefix(edge.Src.AbsID(), obj.AbsID()) && strings.HasPrefix(edge.Dst.AbsID(), obj.AbsID()) { + if obj == g.Root || (strings.HasPrefix(edge.Src.AbsID(), obj.AbsID()+".") && strings.HasPrefix(edge.Dst.AbsID(), obj.AbsID()+".")) { edges = append(edges, edge) } } diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go index b05ea10a6..258428975 100644 --- a/d2layouts/d2sequence/sequence_diagram.go +++ b/d2layouts/d2sequence/sequence_diagram.go @@ -466,8 +466,8 @@ func (sd *sequenceDiagram) routeMessages() error { } else { return fmt.Errorf("could not find center of %s", message.Dst.AbsID()) } - isToDescendant := strings.HasPrefix(message.Dst.AbsID(), message.Src.AbsID()) - isFromDescendant := strings.HasPrefix(message.Src.AbsID(), message.Dst.AbsID()) + isToDescendant := strings.HasPrefix(message.Dst.AbsID(), message.Src.AbsID()+".") + isFromDescendant := strings.HasPrefix(message.Src.AbsID(), message.Dst.AbsID()+".") isSelfMessage := message.Src == message.Dst if isSelfMessage || isToDescendant || isFromDescendant { diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 022fcf66d..41a0c63a6 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -432,6 +432,11 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co if connection.Color != "" { fontColor = connection.Color } + + if connection.Fill != "" { + fmt.Fprintf(writer, ``, + labelTL.X, labelTL.Y, connection.LabelWidth, connection.LabelHeight, connection.Fill) + } textStyle := fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "middle", connection.FontSize, fontColor) x := labelTL.X + float64(connection.LabelWidth)/2 y := labelTL.Y + float64(connection.FontSize) @@ -765,7 +770,16 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, ) // we need the self closing form in this svg/xhtml context render = strings.ReplaceAll(render, "
", "
") - fmt.Fprintf(writer, `
%v
`, render) + + var mdStyle string + if targetShape.Fill != "" { + mdStyle = fmt.Sprintf("background-color:%s;", targetShape.Fill) + } + if targetShape.Stroke != "" { + mdStyle += fmt.Sprintf("color:%s;", targetShape.Stroke) + } + + fmt.Fprintf(writer, `
%v
`, mdStyle, render) fmt.Fprint(writer, ``) } default: @@ -1015,23 +1029,22 @@ func Render(diagram *d2target.Diagram, pad int) ([]byte, error) { labelMasks = append(labelMasks, labelMask) } } else { - return nil, fmt.Errorf("unknow object of type %T", obj) + return nil, fmt.Errorf("unknown object of type %T", obj) } } - if len(labelMasks) > 0 { - fmt.Fprint(buf, strings.Join([]string{ - fmt.Sprintf(``, - labelMaskID, w, h, - ), - fmt.Sprintf(``, - w, - h, - ), - strings.Join(labelMasks, "\n"), - ``, - }, "\n")) - } + // Note: we always want this since we reference it on connections even if there end up being no masked labels + fmt.Fprint(buf, strings.Join([]string{ + fmt.Sprintf(``, + labelMaskID, w, h, + ), + fmt.Sprintf(``, + w, + h, + ), + strings.Join(labelMasks, "\n"), + ``, + }, "\n")) embedFonts(buf) diff --git a/d2target/d2target.go b/d2target/d2target.go index 41dc4c718..ae098fd28 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -207,6 +207,7 @@ type Connection struct { StrokeDash float64 `json:"strokeDash"` StrokeWidth int `json:"strokeWidth"` Stroke string `json:"stroke"` + Fill string `json:"fill,omitempty"` Text LabelPosition string `json:"labelPosition"` diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index f5b08f6b1..801917b2b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,19 +1,14 @@ # Contributing - -- [CI](#ci) -- [Flow](#flow) -- [Logistics](#logistics) -- [Dev](#dev) - * [Content](#content) - * [Tests](#tests) - + [Running tests](#running-tests) - + [Chaos tests](#chaos-tests) - * [Documentation](#documentation) - * [Questions](#questions) - - +- CI +- Flow +- Logistics +- Dev + - Content + - Tests + - Documentation + - Questions ## CI diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 30e31d7cd..410c95f77 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -14,8 +14,9 @@ You may install `d2` through any of the following methods. - From source - Source Release - Windows - - MSYS2 + - Release archives - WSL +- Docker - Coming soon ## install.sh @@ -157,6 +158,8 @@ You can always install from source: go install oss.terrastruct.com/d2@latest ``` +You need at least Go v1.18 + ### Source Release To install a release from source clone the repository and then: @@ -172,33 +175,23 @@ fonts and icons. Furthermore, when installing a non versioned commit, installing will ensure that `d2 --version` works correctly by embedding the commit hash into the `d2` binary. +Remember, you need at least Go v1.18 + ## Windows -d2 builds and runs on Windows: +We have prebuilt releases of d2 available for Windows via `.msi` installers. The installer +will add the `d2` binary to your `$PATH` so that you can execute `d2` in `cmd.exe` or +`pwsh.exe`. -We have prebuilt standalone releases for Windows though they're structured in the same way -as our Unix releases. +### Release archives -Easiest way to use d2 on Windows is to just `chdir` into the bin directory of the release -and invoke d2 like `./d2 ` - -For installation, you'll have to put the `bin/d2.exe` binary into your `$PATH` or add the -`bin` directory of the release into your `$PATH`. - -See https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows - -Then you'll be able to call `d2` from the commandline in `cmd.exe` or `pwsh.exe`. - -We intend to have a `.msi` release installer sometime soon that handles putting `d2` into -your `$PATH` for you. - -### MSYS2 +We also have release archives for Windows structured in the same way as our Unix releases +for use with MSYS2. Screenshot 2022-12-06 at 2 55 27 AM -We recommend using [MSYS2](https://www.msys2.org/) or [Git -Bash](https://gitforwindows.org/#bash) (Git Bash is based on MSYS2) for an improved -terminal experience. +See [MSYS2](https://www.msys2.org/) or [Git Bash](https://gitforwindows.org/#bash) (Git +Bash is based on MSYS2). MSYS2 provides a unix style shell environment that is native to Windows (unlike [Cygwin](https://www.cygwin.com/)). MSYS2 allows `install.sh` to work, enables automatic @@ -216,9 +209,23 @@ under plain Windows. aka Windows Subsystem for Linux if that's what you prefer. Installation is just like any other Linux system. +## Docker + +https://hub.docker.com/repository/docker/terrastruct/d2 + +We publish `amd64` and `arm64` images based on `debian:latest` for each release. + +Example usage: + +```sh +echo 'x -> y' >helloworld.d2 +docker run --rm -it -u "$(id -u):$(id -g)" -v "$PWD:/root/src" \ + -p 127.0.0.1:8080:8080 terrastruct/d2 --watch helloworld.d2 +# Visit http://127.0.0.1:8080 +``` + ## Coming soon -- Docker image - rpm and deb packages - with repositories and standalone - homebrew core diff --git a/docs/assets/playground_button.png b/docs/assets/playground_button.png new file mode 100644 index 000000000..d4e496af0 Binary files /dev/null and b/docs/assets/playground_button.png differ diff --git a/e2etests/regression_test.go b/e2etests/regression_test.go index 7c627be74..b9c2399f1 100644 --- a/e2etests/regression_test.go +++ b/e2etests/regression_test.go @@ -40,6 +40,18 @@ b.1 -> b.1`, a: A b: B`, }, + { + name: "sequence_diagram_name_crash", + script: `foo: { + shape: sequence_diagram + a -> b +} +foobar: { + shape: sequence_diagram + c -> d +} +foo -> foobar`, + }, } runa(t, tcs) diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 29eeb130b..76f4cf12a 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -917,12 +917,13 @@ y: { } } -x -> y: { +x -> y: in style { style: { stroke: green opacity: 0.5 stroke-width: 2 stroke-dash: 5 + fill: lavender } } `, @@ -1041,6 +1042,7 @@ size S -> size M: custom 15 { } size XXXL -> custom 64: custom 48 { style.font-size: 48 + style.fill: lavender } `, }, { @@ -1481,6 +1483,36 @@ a.note: "just\na\nlong\nnote\nhere"`, script: `shape: sequence_diagram alice -> bob: what does it mean to be well-adjusted bob -> alice: The ability to play bridge or golf as if they were games +`, + }, + { + name: "markdown_stroke_fill", + script: ` +container.md: |md +# a header + +a line of text and an + + { + indented: "block", + of: "json", + } + +walk into a bar. +| { + style.stroke: darkorange +} + +container -> no container + +no container: |md +they did it in style +| + +no container.style: { + stroke: red + fill: "#CEEDEE" +} `, }, } diff --git a/e2etests/testdata/regression/dagre_special_ids/dagre/sketch.exp.svg b/e2etests/testdata/regression/dagre_special_ids/dagre/sketch.exp.svg index 1fcf71c22..f7a7922d5 100644 --- a/e2etests/testdata/regression/dagre_special_ids/dagre/sketch.exp.svg +++ b/e2etests/testdata/regression/dagre_special_ids/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="1427" height="568" viewBox="-100 -100 1427 568">ninetynineeighty eightseventy sevena\yodetherea\"odea\node ninetynineeighty eightseventy sevena\yodetherea\"odea\node + + +ninetynineeighty eightseventy sevena\yodetherea\"odea\node ninetynineeighty eightseventy sevena\yodetherea\"odea\node + + +hellogoodbye hellogoodbye + + +hellogoodbye hellogoodbye + + +foofoobarabcd + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json b/e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json new file mode 100644 index 000000000..49186256a --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json @@ -0,0 +1,520 @@ +{ + "name": "", + "shapes": [ + { + "id": "foo", + "type": "sequence_diagram", + "pos": { + "x": 12, + "y": 12 + }, + "width": 448, + "height": 520, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "foo", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 43, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "foobar", + "type": "sequence_diagram", + "pos": { + "x": 12, + "y": 632 + }, + "width": 448, + "height": 520, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "foobar", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 84, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "foo.a", + "type": "", + "pos": { + "x": 36, + "y": 122 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 12, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "foo.b", + "type": "", + "pos": { + "x": 286, + "y": 122 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "foobar.c", + "type": "", + "pos": { + "x": 36, + "y": 742 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 12, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "foobar.d", + "type": "", + "pos": { + "x": 286, + "y": 742 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "foo.(a -> b)[0]", + "src": "foo.a", + "srcArrow": "none", + "srcLabel": "", + "dst": "foo.b", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 111, + "y": 378 + }, + { + "x": 361, + "y": 378 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 4 + }, + { + "id": "foobar.(c -> d)[0]", + "src": "foobar.c", + "srcArrow": "none", + "srcLabel": "", + "dst": "foobar.d", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 111, + "y": 998 + }, + { + "x": 361, + "y": 998 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 4 + }, + { + "id": "(foo -> foobar)[0]", + "src": "foo", + "srcArrow": "none", + "srcLabel": "", + "dst": "foobar", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 236, + "y": 532 + }, + { + "x": 236, + "y": 632 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(foo.a -- )[0]", + "src": "foo.a", + "srcArrow": "none", + "srcLabel": "", + "dst": "a-lifeline-end-2251863791", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 6, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 111, + "y": 248 + }, + { + "x": 111, + "y": 508 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + }, + { + "id": "(foo.b -- )[0]", + "src": "foo.b", + "srcArrow": "none", + "srcLabel": "", + "dst": "b-lifeline-end-668380428", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 6, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 361, + "y": 248 + }, + { + "x": 361, + "y": 508 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + }, + { + "id": "(foobar.c -- )[0]", + "src": "foobar.c", + "srcArrow": "none", + "srcLabel": "", + "dst": "c-lifeline-end-955173837", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 6, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 111, + "y": 868 + }, + { + "x": 111, + "y": 1128 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + }, + { + "id": "(foobar.d -- )[0]", + "src": "foobar.d", + "srcArrow": "none", + "srcLabel": "", + "dst": "d-lifeline-end-2106864010", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 6, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 361, + "y": 868 + }, + { + "x": 361, + "y": 1128 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + } + ] +} diff --git a/e2etests/testdata/regression/sequence_diagram_name_crash/elk/sketch.exp.svg b/e2etests/testdata/regression/sequence_diagram_name_crash/elk/sketch.exp.svg new file mode 100644 index 000000000..724c43730 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_name_crash/elk/sketch.exp.svg @@ -0,0 +1,31 @@ + +foofoobarabcd + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sequence_diagram_no_message/dagre/sketch.exp.svg b/e2etests/testdata/regression/sequence_diagram_no_message/dagre/sketch.exp.svg index 04f0e8b4e..c1a851444 100644 --- a/e2etests/testdata/regression/sequence_diagram_no_message/dagre/sketch.exp.svg +++ b/e2etests/testdata/regression/sequence_diagram_no_message/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="600" height="456" viewBox="-76 -26 600 456">ABAB + + +ABAB + + +b b + + +b b + + +abc abc + + +abc abc + + +ab ab + + +ab ab + + +acbd acbd + + +acbd acbd + + + \ No newline at end of file + + + + \ No newline at end of file diff --git a/e2etests/testdata/sanity/empty/elk/sketch.exp.svg b/e2etests/testdata/sanity/empty/elk/sketch.exp.svg index 9563e1411..20d55bdc1 100644 --- a/e2etests/testdata/sanity/empty/elk/sketch.exp.svg +++ b/e2etests/testdata/sanity/empty/elk/sketch.exp.svg @@ -18,4 +18,7 @@ width="200" height="200" viewBox="-100 -100 200 200"> \ No newline at end of file + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg index 864693eac..095790f60 100644 --- a/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="1539" height="824" viewBox="-100 -100 1539 824">rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud + + +rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud + + +rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud + + +rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud + + +cba * cba * + + +cba * cba * + + +abcdefghijklmno abcdefghijklmno + + +abcdefghijklmno abcdefghijklmno + + +aaadddeeebbbccc111 222 +aaadddeeebbbccc111 222 - + aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff1122 334455667788 +aabbllmm

nn

+
oocciikkdd

gg

+
hhjj

ee

+
ff1122 334455667788 diff --git a/e2etests/testdata/stable/chaos2/elk/board.exp.json b/e2etests/testdata/stable/chaos2/elk/board.exp.json index 1aa503f04..f99715058 100644 --- a/e2etests/testdata/stable/chaos2/elk/board.exp.json +++ b/e2etests/testdata/stable/chaos2/elk/board.exp.json @@ -174,8 +174,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -253,8 +253,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -532,8 +532,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -704,7 +704,7 @@ "labelPercentage": 0, "route": [ { - "x": 552.6666666666666, + "x": 553, "y": 796 }, { @@ -798,8 +798,8 @@ "labelPercentage": 0, "route": [ { - "x": 566.5, - "y": 213 + "x": 567, + "y": 209 }, { "x": 566.5, @@ -853,8 +853,8 @@ "labelPercentage": 0, "route": [ { - "x": 487.90000000000003, - "y": 213 + "x": 488, + "y": 209 }, { "x": 487.90000000000003, @@ -900,7 +900,7 @@ "labelPercentage": 0, "route": [ { - "x": 540.3000000000001, + "x": 540, "y": 213 }, { @@ -1010,7 +1010,7 @@ "labelPercentage": 0, "route": [ { - "x": 514.1, + "x": 514, "y": 213 }, { @@ -1022,8 +1022,8 @@ "y": 223 }, { - "x": 508.50000000000006, - "y": 520 + "x": 509, + "y": 575 } ], "animated": false, @@ -1112,7 +1112,7 @@ "labelPercentage": 0, "route": [ { - "x": 464.33333333333337, + "x": 464, "y": 796 }, { diff --git a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg index f55323cfa..96c477b53 100644 --- a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg @@ -774,17 +774,17 @@ width="1092" height="1907" viewBox="-88 -88 1092 1907">aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff1122 334455667788 +aabbllmm

nn

+
oocciikkdd

gg

+
hhjj

ee

+
ff1122 334455667788 - + - + abcd abcd + + +abcd abcd + + +abc abc + + +abc abc + + +acfbdhg acfbdhg + + +acfbdhg acfbdhg + + +agdfbhec agdfbhec + + +agdfbhec agdfbhec + + +abcdefghijklmnopq abcdefghijklmnopq + + +abcdefghijklmnopq abcdefghijklmnopq + + +finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot + + +finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot + + +bacde21345abcde bacde21345abcde + + +bacde21345abcde bacde21345abcde + + +size XSsize Ssize Msize Lsize XLsize XXLsize XXXLcustom 8custom 12custom 18custom 21custom 64 custom 10custom 15custom 48 +size XSsize Ssize Msize Lsize XLsize XXLsize XXXLcustom 8custom 12custom 18custom 21custom 64 custom 10custom 15custom 48 diff --git a/e2etests/testdata/stable/font_sizes/elk/board.exp.json b/e2etests/testdata/stable/font_sizes/elk/board.exp.json index 2d5446e61..9658ee501 100644 --- a/e2etests/testdata/stable/font_sizes/elk/board.exp.json +++ b/e2etests/testdata/stable/font_sizes/elk/board.exp.json @@ -573,6 +573,7 @@ "strokeDash": 0, "strokeWidth": 2, "stroke": "#0D32B2", + "fill": "lavender", "label": "custom 48", "fontSize": 48, "fontFamily": "DEFAULT", diff --git a/e2etests/testdata/stable/font_sizes/elk/sketch.exp.svg b/e2etests/testdata/stable/font_sizes/elk/sketch.exp.svg index cb9321042..9c69d48ce 100644 --- a/e2etests/testdata/stable/font_sizes/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/font_sizes/elk/sketch.exp.svg @@ -18,7 +18,7 @@ width="1965" height="793" viewBox="-88 -88 1965 793">size XSsize Ssize Msize Lsize XLsize XXLsize XXXLcustom 8custom 12custom 18custom 21custom 64 custom 10custom 15custom 48 +size XSsize Ssize Msize Lsize XLsize XXLsize XXXLcustom 8custom 12custom 18custom 21custom 64 custom 10custom 15custom 48 diff --git a/e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json b/e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json index 62256201b..414bc5f4a 100644 --- a/e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json +++ b/e2etests/testdata/stable/giant_markdown_test/dagre/board.exp.json @@ -14,8 +14,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, diff --git a/e2etests/testdata/stable/giant_markdown_test/dagre/sketch.exp.svg b/e2etests/testdata/stable/giant_markdown_test/dagre/sketch.exp.svg index 2f32b1bb9..14fc3e202 100644 --- a/e2etests/testdata/stable/giant_markdown_test/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/giant_markdown_test/dagre/sketch.exp.svg @@ -774,7 +774,7 @@ width="3251" height="5500" viewBox="-100 -100 3251 5500">

Markdown: Syntax

+

Markdown: Syntax

  • Overview
      @@ -1031,7 +1031,10 @@ title for the link, surrounded in quotes. For example:

      Code

      Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:

      -
ab

Markdown: Syntax

+

Markdown: Syntax

  • Overview
      @@ -1031,7 +1031,10 @@ title for the link, surrounded in quotes. For example:

      Code

      Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:

      -
ab

Note: This document is itself written using Markdown; you +

Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.


Overview

-
ab

Note: This document is itself written using Markdown; you +

Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.


Overview

-
ab hellohello + + +hellohello + + +ab ab + + +ab ab + + +aabbccddllffwwyynniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 123456 +aabbccddllffwwyynniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 123456 - - + + diff --git a/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg b/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg index d9d031094..e1ae164a1 100644 --- a/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="3244" height="1780" viewBox="-100 -100 3244 1780">abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv + + +abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv + + +mixed togethersugarsolution we get +mixed togethersugarsolution we get mixed togethersugarsolution we get +mixed togethersugarsolution we get
    +
    • Overview
      • Philosophy
      • @@ -786,7 +786,10 @@ width="579" height="752" viewBox="-100 -100 579 752">
          +
          • Overview
            • Philosophy
            • @@ -786,7 +786,10 @@ width="579" height="752" viewBox="-88 -88 579 752">
                +
                • Overview ok this is all measured
                  • Philosophy
                  • @@ -782,7 +782,10 @@ width="445" height="728" viewBox="-100 -100 445 728">
                      +
                      • Overview ok this is all measured
                        • Philosophy
                        • @@ -782,7 +782,10 @@ width="445" height="728" viewBox="-88 -88 445 728">
                            +
                            • Overview
                              • Philosophy
                              • @@ -807,7 +807,10 @@ width="547" height="1164" viewBox="-100 -100 547 1164">
                                  +
                                  • Overview
                                    • Philosophy
                                    • @@ -807,7 +807,10 @@ width="547" height="1164" viewBox="-88 -88 547 1164">

                                      List items may consist of multiple paragraphs. Each subsequent +

                                      List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab:

                                        @@ -805,7 +805,10 @@ sit amet, consectetuer adipiscing elit.

                                        Another item in the same list.

                                    -
                                  ab

                                  List items may consist of multiple paragraphs. Each subsequent +

                                  List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab:

                                    @@ -805,7 +805,10 @@ sit amet, consectetuer adipiscing elit.

                                    Another item in the same list.

                                -
                                ab

                                Markdown: Syntax

                                -
                                ab

                                Markdown: Syntax

                                +
                                ab + + +

                                Markdown: Syntax

                                -
                                ab

                                Markdown: Syntax

                                +
                                ab + + +

                                Every frustum longs to be a cone

                                +

                                Every frustum longs to be a cone

                                • A continuing flow of paper is sufficient to continue the flow of paper
                                • Please remain calm, it's no use both of us being hysterical at the same time
                                • Visits always give pleasure: if not on arrival, then on the departure

                                Festivity Level 1: Your guests are chatting amiably with each other.

                                -
                                xy

                                Every frustum longs to be a cone

                                +

                                Every frustum longs to be a cone

                                • A continuing flow of paper is sufficient to continue the flow of paper
                                • Please remain calm, it's no use both of us being hysterical at the same time
                                • Visits always give pleasure: if not on arrival, then on the departure

                                Festivity Level 1: Your guests are chatting amiably with each other.

                                -
                                xy container

                                they did it in style

                                +

                                a header

                                +

                                a line of text and an

                                +
                                {
                                +	indented: "block",
                                +	of: "json",
                                +}
                                +
                                +

                                walk into a bar.

                                +
                                + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/markdown_stroke_fill/elk/board.exp.json b/e2etests/testdata/stable/markdown_stroke_fill/elk/board.exp.json new file mode 100644 index 000000000..197eecbbb --- /dev/null +++ b/e2etests/testdata/stable/markdown_stroke_fill/elk/board.exp.json @@ -0,0 +1,164 @@ +{ + "name": "", + "shapes": [ + { + "id": "container", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 362, + "height": 407, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#E3E9FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "container", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 117, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "container.md", + "type": "text", + "pos": { + "x": 87, + "y": 87 + }, + "width": 212, + "height": 257, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "darkorange", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# a header\n\na line of text and an\n\n\t{\n\t\tindented: \"block\",\n\t\tof: \"json\",\n\t}\n\nwalk into a bar.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 212, + "labelHeight": 257, + "zIndex": 0, + "level": 2 + }, + { + "id": "no container", + "type": "text", + "pos": { + "x": 134, + "y": 519 + }, + "width": 118, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#CEEDEE", + "stroke": "red", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "they did it in style", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 118, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(container -> no container)[0]", + "src": "container", + "srcArrow": "none", + "srcLabel": "", + "dst": "no container", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 193, + "y": 419 + }, + { + "x": 193, + "y": 519 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/markdown_stroke_fill/elk/sketch.exp.svg b/e2etests/testdata/stable/markdown_stroke_fill/elk/sketch.exp.svg new file mode 100644 index 000000000..d57478e16 --- /dev/null +++ b/e2etests/testdata/stable/markdown_stroke_fill/elk/sketch.exp.svg @@ -0,0 +1,803 @@ + +container

                                they did it in style

                                +

                                a header

                                +

                                a line of text and an

                                +
                                {
                                +	indented: "block",
                                +	of: "json",
                                +}
                                +
                                +

                                walk into a bar.

                                +
                                + + +
                                \ No newline at end of file diff --git a/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json b/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json index 994095495..21a05248c 100644 --- a/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json +++ b/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json @@ -54,8 +54,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, diff --git a/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg b/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg index aa217c671..1e8e6f874 100644 --- a/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg @@ -774,9 +774,12 @@ width="759" height="348" viewBox="-100 -100 759 348">markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                +markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

                                -
                                markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                +markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

                                -
                                markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                +markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

                                -
                                markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                +markdown

                                Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                                sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

                                -
                                {
                                +
                                {
                                 	fenced: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab
                                {
                                +
                                {
                                 	fenced: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                a line of text and an

                                +

                                a line of text and an

                                {
                                 	indented: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                a line of text and an

                                +

                                a line of text and an

                                {
                                 	indented: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                code

                                -
                                ab

                                code

                                +
                                ab + + +

                                code

                                -
                                ab

                                code

                                +
                                ab + + +thisgoesmultiple linesthisgoesmultiple lines + + +thisgoesmultiple linesthisgoesmultiple lines + + +abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvw + + +abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvw + + +abcdefghijklmnopqrstu abcdefghijklmnopqrstu + + +abcdefghijklmnopqrstu abcdefghijklmnopqrstu + + +Foo Baz12hello Foo Baz12hello + + +Foo Baz12hello Foo Baz12hello + + +acdefgbh acdefgbh + + +acdefgbh acdefgbh + + +topabcbottomstartend topabcbottomstartend + + +topabcbottomstartend topabcbottomstartend + + +

                                A paragraph is simply one or more consecutive lines of text, separated +

                                A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.

                                -
                                ab

                                A paragraph is simply one or more consecutive lines of text, separated +

                                A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.

                                -
                                ab

                                Here is an example of AppleScript:

                                +

                                Here is an example of AppleScript:

                                tell application "Foo"
                                     beep
                                 end tell
                                 

                                A code block continues until it reaches a line that is not indented (or the end of the article).

                                -
                                ab

                                Here is an example of AppleScript:

                                +

                                Here is an example of AppleScript:

                                tell application "Foo"
                                     beep
                                 end tell
                                 

                                A code block continues until it reaches a line that is not indented (or the end of the article).

                                -
                                ab ba a note here to remember that padding must consider notes toojustalongnotehereba a note here to remember that padding must consider notes toojustalongnotehere + + +ba a note here to remember that padding must consider notes toojustalongnotehereba a note here to remember that padding must consider notes toojustalongnotehere + + +scoreritemResponseitemessayRubricconceptitemOutcome scoreritemResponseitemessayRubricconceptitemOutcome + + +scoreritemResponseitemessayRubricconceptitemOutcome scoreritemResponseitemessayRubricconceptitemOutcome + + +a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) +a_shapea_sequenceanotherfinallysequencesequencesequencescoreritemResponseitemessayRubricconceptitemOutcomescorerconceptessayRubricitemitemOutcomeitemResponsescoreritemResponseitemessayRubricconceptitemOutcome getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts)getItem()itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) diff --git a/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg b/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg index a953a362a..6f98cb8c8 100644 --- a/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg @@ -50,7 +50,10 @@ width="1162" height="660" viewBox="-100 -100 1162 660">acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc + + +acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/stylish/elk/board.exp.json b/e2etests/testdata/stable/stylish/elk/board.exp.json index 0cd252c22..f2da4deeb 100644 --- a/e2etests/testdata/stable/stylish/elk/board.exp.json +++ b/e2etests/testdata/stable/stylish/elk/board.exp.json @@ -46,7 +46,7 @@ "type": "", "pos": { "x": 12, - "y": 238 + "y": 359 }, "width": 114, "height": 126, @@ -95,7 +95,8 @@ "strokeDash": 5, "strokeWidth": 2, "stroke": "green", - "label": "", + "fill": "lavender", + "label": "in style", "fontSize": 16, "fontFamily": "DEFAULT", "language": "", @@ -103,9 +104,9 @@ "italic": true, "bold": false, "underline": false, - "labelWidth": 0, - "labelHeight": 0, - "labelPosition": "", + "labelWidth": 47, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", "labelPercentage": 0, "route": [ { @@ -114,7 +115,7 @@ }, { "x": 69, - "y": 238 + "y": 359 } ], "animated": false, diff --git a/e2etests/testdata/stable/stylish/elk/sketch.exp.svg b/e2etests/testdata/stable/stylish/elk/sketch.exp.svg index eff681d67..ec024fa78 100644 --- a/e2etests/testdata/stable/stylish/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/stylish/elk/sketch.exp.svg @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg b/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg index ab997d9a1..05e1cc4e7 100644 --- a/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg @@ -20,7 +20,10 @@ width="339" height="326" viewBox="-100 -100 339 326"> -cube -cubeAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTND + + +AKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTND + + +ninety ninesixty fourthirty twosixteeneightninety ninesixty fourthirty twosixteeneight + + +ninety ninesixty fourthirty twosixteeneightninety ninesixty fourthirty twosixteeneight + + +