diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 384cfa3f3..b9feefc85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: steps: - uses: actions/checkout@v3 - run: git submodule update --init - - run: COLOR=1 ./ci/sub/nofixups.sh + - run: COLOR=1 ./ci/sub/bin/nofixups.sh env: GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..d7226ae8f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +d2layouts/d2dagrelayout/dagre.js +d2layouts/d2elklayout/setup.js +d2renderers/d2latex/mathjax.js +d2renderers/d2latex/polyfills.js +d2renderers/d2latex/setup.js +lib/png/generate_png.js diff --git a/Makefile b/Makefile index fdc039a33..8fd431900 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: fmt gen lint build test .PHONY: fmt fmt: - prefix "$@" ./ci/fmt.sh + prefix "$@" ./ci/sub/bin/fmt.sh .PHONY: gen gen: prefix "$@" ./ci/gen.sh diff --git a/README.md b/README.md index 03bd040c1..fd864cbca 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,20 @@ 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) [![release](https://img.shields.io/github/v/release/terrastruct/d2)](https://github.com/terrastruct/d2/releases) [![discord](https://img.shields.io/discord/1039184639652265985?label=discord)](https://discord.gg/NF6X8K4eDq) [![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 @@ -32,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 @@ -140,7 +145,7 @@ one, please see [./d2renderers/d2fonts](./d2renderers/d2fonts). ## Export file types -D2 currently supports SVG exports. More coming soon. +D2 currently supports SVG and PNG exports. More coming soon. ## Language tooling @@ -186,21 +191,37 @@ Open sourced under the Mozilla Public License 2.0. See [./LICENSE.txt](./LICENSE ## Related -### VSCode extension +We are constantly working on new plugins, integrations, extensions. Contributions are +welcome in any official or community plugins. If you have somewhere in your workflow that +you want to use D2, feel free to open a discussion. We have limited bandwidth and usually +choose the most high-demand ones to work on. If you make something cool with D2 yourself, +let us know and we'll be happy to include it here! -[https://github.com/terrastruct/d2-vscode](https://github.com/terrastruct/d2-vscode) +### Official plugins -### Vim extension +- **VSCode extension**: [https://github.com/terrastruct/d2-vscode](https://github.com/terrastruct/d2-vscode) +- **Vim extension**: [https://github.com/terrastruct/d2-vim](https://github.com/terrastruct/d2-vim) +- **Obsidian plugin**: [https://github.com/terrastruct/d2-obsidian](https://github.com/terrastruct/d2-obsidian) +- **Slack app**: [https://d2lang.com/tour/slack](https://d2lang.com/tour/slack) +- **Discord plugin**: [https://d2lang.com/tour/discord](https://d2lang.com/tour/discord) -[https://github.com/terrastruct/d2-vim](https://github.com/terrastruct/d2-vim) +### Community plugins -### Language docs - -[https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs) +- **Tree-sitter grammar**: [https://github.com/pleshevskiy/tree-sitter-d2](https://github.com/pleshevskiy/tree-sitter-d2) +- **Emacs major mode**: [https://github.com/andorsk/d2-mode](https://github.com/andorsk/d2-mode) +- **Goldmark extension**: [https://github.com/FurqanSoftware/goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2) +- **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) +- **Structurizr to D2 exporter**: [https://github.com/goto1134/structurizr-d2-exporter](https://github.com/goto1134/structurizr-d2-exporter) +- **MdBook preprocessor**: [https://github.com/danieleades/mdbook-d2](https://github.com/danieleades/mdbook-d2) +- **D2 org-mode support**: [https://github.com/xcapaldi/ob-d2](https://github.com/xcapaldi/ob-d2) +- **Python D2 diagram builder**: [https://github.com/MrBlenny/py-d2](https://github.com/MrBlenny/py-d2) ### 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/e2ereport.sh b/ci/e2ereport.sh index 691ad8e46..ebf45b912 100755 --- a/ci/e2ereport.sh +++ b/ci/e2ereport.sh @@ -3,6 +3,7 @@ set -eu export REPORT_OUTPUT="out/e2e_report.html" rm -f $REPORT_OUTPUT +export E2E_REPORT=1 FORCE_COLOR=1 DEBUG=1 go run ./e2etests/report/main.go "$@"; diff --git a/ci/fmt.sh b/ci/fmt.sh deleted file mode 100755 index 4fb39da34..000000000 --- a/ci/fmt.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -set -eu -. "$(dirname "$0")/sub/lib.sh" -cd -- "$(dirname "$0")/.." - -if is_changed README.md; then - sh_c tocsubst --skip 1 README.md -fi -if is_changed docs/INSTALL.md; then - sh_c tocsubst --skip 1 docs/INSTALL.md -fi -./ci/sub/fmt/make.sh 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/_build.sh b/ci/release/_build.sh index 3f8b1663d..8df5e2bd1 100755 --- a/ci/release/_build.sh +++ b/ci/release/_build.sh @@ -3,20 +3,25 @@ set -eu cd -- "$(dirname "$0")/../.." . ./ci/sub/lib.sh +sh_c rm -Rf "$HW_BUILD_DIR" sh_c mkdir -p "$HW_BUILD_DIR" -sh_c rsync --recursive --perms --delete \ - --human-readable --copy-links ./ci/release/template/ "$HW_BUILD_DIR/" -VERSION=$VERSION sh_c eval "'$HW_BUILD_DIR/README.md.sh'" \> "'$HW_BUILD_DIR/README.md'" -sh_c rm -f "$HW_BUILD_DIR/README.md.sh" -sh_c find "$HW_BUILD_DIR" -exec touch {} \\\; +sh_c cp ./ci/release/template/LICENSE.txt "$HW_BUILD_DIR" +sh_c cp ./ci/release/template/Makefile "$HW_BUILD_DIR" +sh_c cp -R ./ci/release/template/man "$HW_BUILD_DIR" +sh_c cp -R ./ci/release/template/scripts "$HW_BUILD_DIR" +sh_c VERSION="$VERSION" ./ci/release/template/README.md.sh \> "'$HW_BUILD_DIR/README.md'" ensure_goos ensure_goarch sh_c mkdir -p "$HW_BUILD_DIR/bin" -sh_c CGO_ENABLED=0 go build -trimpath \ +sh_c GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build -trimpath \ -ldflags "'-X oss.terrastruct.com/d2/lib/version.Version=$VERSION'" \ -o "$HW_BUILD_DIR/bin/d2" . +if [ "$GOOS" = windows ]; then + sh_c mv "$HW_BUILD_DIR/bin/d2" "$HW_BUILD_DIR/bin/d2.exe" +fi + ARCHIVE=$PWD/$ARCHIVE cd "$(dirname "$HW_BUILD_DIR")" sh_c tar -czf "$ARCHIVE" "$(basename "$HW_BUILD_DIR")" diff --git a/ci/release/_install.sh b/ci/release/_install.sh index 6428f432b..f0c653312 100755 --- a/ci/release/_install.sh +++ b/ci/release/_install.sh @@ -347,7 +347,7 @@ install_d2_standalone() { install_d2_brew() { header "installing d2 with homebrew" - sh_c brew tap terrastruct/d2 + sh_c brew update sh_c brew install d2 } @@ -390,8 +390,8 @@ install_tala_standalone() { install_tala_brew() { header "installing tala with homebrew" - sh_c brew tap terrastruct/d2 - sh_c brew install tala + sh_c brew update + sh_c brew install terrastruct/tap/tala } uninstall() { @@ -502,11 +502,6 @@ fetch_gh() { sh_c mv "$file.inprogress" "$file" } -brew() { - # Makes brew sane. - HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_AUTO_UPDATE=1 command brew "$@" -} - # The main function does more than provide organization. It provides robustness in that if # the install script was to only partial download into sh, sh will not execute it because # main is not invoked until the very last byte. diff --git a/ci/release/aws/ensure.sh b/ci/release/aws/ensure.sh new file mode 100755 index 000000000..8ee2afffb --- /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 84d6618aa..6d0e821af 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,7 +1,16 @@ #### Features ๐Ÿš€ +- Tooltips can be set on shapes. See [https://d2lang.com/tour/tooltips](https://d2lang.com/tour/interactive). [#548](https://github.com/terrastruct/d2/pull/548) +- Links can be set on shapes. See [https://d2lang.com/tour/tooltips](https://d2lang.com/tour/interactive). [#548](https://github.com/terrastruct/d2/pull/548) +- The `width` and `height` attributes are no longer restricted to images and can be applied to non-container shapes. [#498](https://github.com/terrastruct/d2/pull/498) + #### Improvements ๐Ÿงน +- Watch mode now renders fit to screen. [#560](https://github.com/terrastruct/d2/pull/560) + #### Bugfixes โ›‘๏ธ -- Fixed sequence diagram span size for self-edges [#397](https://github.com/terrastruct/d2/pull/397) \ No newline at end of file +- Restricts where `near` key constant values can be used, with good error messages, instead of erroring (e.g. setting `near: top-center` on a container would cause bad layouts or error). [#538](https://github.com/terrastruct/d2/pull/538) +- Fixes an error during ELK layout when images had empty labels. [#555](https://github.com/terrastruct/d2/pull/555) +- Fixes rendering classes and tables with empty headers. [#498](https://github.com/terrastruct/d2/pull/498) +- Fixes rendering sql tables with no columns. [#553](https://github.com/terrastruct/d2/pull/553) diff --git a/ci/release/changelogs/v0.1.1.md b/ci/release/changelogs/v0.1.1.md new file mode 100644 index 000000000..82704afbc --- /dev/null +++ b/ci/release/changelogs/v0.1.1.md @@ -0,0 +1,7 @@ +#### Improvements ๐Ÿงน + +- The Windows release binary is now suffixed correctly with `.exe` [#388](https://github.com/terrastruct/d2/issues/388) + +#### Bugfixes โ›‘๏ธ + +- Fixed sequence diagram span size for self-edges [#397](https://github.com/terrastruct/d2/pull/397) diff --git a/ci/release/changelogs/v0.1.2.md b/ci/release/changelogs/v0.1.2.md new file mode 100644 index 000000000..efa7b6e56 --- /dev/null +++ b/ci/release/changelogs/v0.1.2.md @@ -0,0 +1,25 @@ +D2 now has an official playground site: [https://play.d2lang.com](https://play.d2lang.com). It loads and runs fast, works on all the major browsers and has been tested on desktop and mobile on a variety of devices. It's the easiest way to get started with D2 and share diagrams. The playground is all open source ([https://github.com/terrastruct/d2-playground](https://github.com/terrastruct/d2-playground)). We'd love to hear your feedback and feature requests. + +Windows users, the install experience just got a whole lot better. Making D2 accessible and easy to use continues to be a priority for us. With this release, we added an MSI installer for Windows, so that installs are just a few clicks. An official Docker image has also been added. + +#### Features ๐Ÿš€ + +- Diagram padding 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 be set with the `style.fill` keyword. [https://github.com/terrastruct/d2/pull/452](https://github.com/terrastruct/d2/pull/452) +- Adds official Docker image. See [./docs/INSTALL.md#docker](./docs/INSTALL.md#docker). [#76](https://github.com/terrastruct/d2/issues/76) +- Adds `.msi` installer for convenient installation on Windows. [#379](https://github.com/terrastruct/d2/issues/379) + +#### Improvements ๐Ÿงน + +- `d2 fmt` preserves leading comment spacing. [#400](https://github.com/terrastruct/d2/issues/400) +- `stroke` and `fill` keywords work for Markdown text. [https://github.com/terrastruct/d2/pull/460](https://github.com/terrastruct/d2/pull/460) +- PNG export resolution increased by 2x to not be blurry exporting on retina displays. [https://github.com/terrastruct/d2/pull/445](https://github.com/terrastruct/d2/pull/445) + +#### Bugfixes โ›‘๏ธ + +- Fixes crash when sequence diagrams has no messages. [https://github.com/terrastruct/d2/pull/427](https://github.com/terrastruct/d2/pull/427) +- Fixes `constraint` keyword setting label. [https://github.com/terrastruct/d2/issues/415](https://github.com/terrastruct/d2/issues/415) +- Fixes serialization affecting binary plugins (TALA). [https://github.com/terrastruct/d2/pull/426](https://github.com/terrastruct/d2/pull/426) +- Fixes 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) +- Fixes 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) +- Fixes 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/changelogs/v0.1.3.md b/ci/release/changelogs/v0.1.3.md new file mode 100644 index 000000000..150fc23d1 --- /dev/null +++ b/ci/release/changelogs/v0.1.3.md @@ -0,0 +1,34 @@ +Many have asked how to get the diagram to look like the one on D2's [cheat sheet](https://d2lang.com/tour/cheat-sheet). With this release, now you can! See [https://d2lang.com/tour/themes](https://d2lang.com/tour/themes) for more. + +![sketch](https://user-images.githubusercontent.com/3120367/209235066-d8ad6b3c-d19b-491d-b014-407f3c47407f.png) + +The Slack app for D2 has now hit production, so if you're looking for the quickest way to express a visual model without interrupting the conversation flow, go to [http://d2lang.com/tour/slack](http://d2lang.com/tour/slack) to install. + +Hope everyone is enjoying the holidays this week! + +#### Features ๐Ÿš€ + +- `sketch` flag renders the diagram to look like it was sketched by hand. [#492](https://github.com/terrastruct/d2/pull/492) +- `near` now takes constants like `top-center`, particularly useful for diagram titles. See [docs](https://d2lang.com/tour/text#near-a-constant) for more. [#525](https://github.com/terrastruct/d2/pull/525) + +#### Improvements ๐Ÿงน + +- Improved label placements for shapes with images and icons to avoid overlapping labels. [#474](https://github.com/terrastruct/d2/pull/474) +- Themes are applied to `sql_table` and `class` shapes. [#521](https://github.com/terrastruct/d2/pull/521) +- `class` shapes use monospaced font. [#521](https://github.com/terrastruct/d2/pull/521) +- Sequence diagram edge group labels have more reasonable padding. [#512](https://github.com/terrastruct/d2/pull/512) +- ELK layout engine preserves order of nodes. [#282](https://github.com/terrastruct/d2/issues/282) +- Non-markdown text (`shape: text` without language block) works with `bold`, `italic`, `underline`, and `font-size`. [#528](https://github.com/terrastruct/d2/pull/528) +- Markdown headings set font-family explicitly, so that external stylesheets with more specific targeting don't override it. [#525](https://github.com/terrastruct/d2/pull/525) + +#### Bugfixes โ›‘๏ธ + +- `d2 fmt` only rewrites if it has changes, instead of always rewriting. [#470](https://github.com/terrastruct/d2/pull/470) +- Text no longer overflows in `sql_table` shapes. [#458](https://github.com/terrastruct/d2/pull/458) +- ELK connection labels are now given the appropriate dimensions. [#483](https://github.com/terrastruct/d2/pull/483) +- Dagre connection lengths make room for longer labels. [#484](https://github.com/terrastruct/d2/pull/484) +- Icons with query parameters are escaped to valid SVG XML. [#438](https://github.com/terrastruct/d2/issues/438) +- Connections at the boundaries no longer get part of its stroke clipped. [#493](https://github.com/terrastruct/d2/pull/493) +- Fixes edge case where `style` being defined in same scope as `sql_table` causes compiler to skip compiling `sql_table`. [#506](https://github.com/terrastruct/d2/issues/506) +- Fixes panic passing a non-string value to `constraint`. [#248](https://github.com/terrastruct/d2/issues/248) +- Fixes edge case where the key `null` was compiling wrongly. [#507](https://github.com/terrastruct/d2/issues/507) 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 2c0cdfb35..d08a71bf6 100755 --- a/ci/release/template/README.md.sh +++ b/ci/release/template/README.md.sh @@ -1,12 +1,35 @@ #!/bin/sh set -eu +cd -- "$(dirname "$0")/../../.." +. ./ci/sub/lib.sh +ensure_os +ensure_arch cat <"$out" + if [ "$code" -eq 0 ]; then + return + fi + cat "$out" >&2 + return "$code" +} + echo_dur() { local dur=$1 local h=$((dur/60/60)) 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/d2chaos/d2chaos_test.go b/d2chaos/d2chaos_test.go index ddbfa9dc2..d5ac6a17d 100644 --- a/d2chaos/d2chaos_test.go +++ b/d2chaos/d2chaos_test.go @@ -120,7 +120,7 @@ func test(t *testing.T, textPath, text string) { ruler, err := textmeasure.NewRuler() assert.Nil(t, err) - err = g.SetDimensions(nil, ruler) + err = g.SetDimensions(nil, ruler, nil) assert.Nil(t, err) err = d2dagrelayout.Layout(ctx, g) @@ -128,7 +128,7 @@ func test(t *testing.T, textPath, text string) { t.Fatal(err) } - _, err = d2exporter.Export(ctx, g, 0) + _, err = d2exporter.Export(ctx, g, 0, nil) if err != nil { t.Fatal(err) } diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 2d2cba269..2e4d75c3b 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -363,6 +363,9 @@ func (c *compiler) applyScalar(attrs *d2graph.Attributes, reserved string, box d } attrs.Direction.Value = scalar.ScalarString() return + case "constraint": + // Compilation for shape-specific keywords happens elsewhere + return } if _, ok := d2graph.StyleKeywords[reserved]; ok { @@ -580,16 +583,25 @@ func (c *compiler) compileShapes(obj *d2graph.Object) { c.compileShapes(obj) } - for _, obj := range obj.ChildrenArray { - switch obj.Attributes.Shape.Value { + for i := 0; i < len(obj.ChildrenArray); i++ { + ch := obj.ChildrenArray[i] + switch ch.Attributes.Shape.Value { case d2target.ShapeClass, d2target.ShapeSQLTable: - flattenContainer(obj.Graph, obj) + flattenContainer(obj.Graph, ch) } - if obj.IDVal == "style" { - obj.Parent.Attributes.Style = obj.Attributes.Style + if ch.IDVal == "style" { + obj.Attributes.Style = ch.Attributes.Style if obj.Graph != nil { - flattenContainer(obj.Graph, obj) - removeObject(obj.Graph, obj) + flattenContainer(obj.Graph, ch) + for i := 0; i < len(obj.Graph.Objects); i++ { + if obj.Graph.Objects[i] == ch { + obj.Graph.Objects = append(obj.Graph.Objects[:i], obj.Graph.Objects[i+1:]...) + break + } + } + delete(obj.Children, ch.ID) + obj.ChildrenArray = append(obj.ChildrenArray[:i], obj.ChildrenArray[i+1:]...) + i-- } } } @@ -666,8 +678,8 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) { typ = "" } d2Col := d2target.SQLColumn{ - Name: col.IDVal, - Type: typ, + Name: d2target.Text{Label: col.IDVal}, + Type: d2target.Text{Label: typ}, } // The only map a sql table field could have is to specify constraint if col.Map != nil { @@ -676,6 +688,10 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) { continue } if n.MapKey.Key.Path[0].Unbox().ScalarString() == "constraint" { + if n.MapKey.Value.StringBox().Unbox() == nil { + c.errorf(n.MapKey.GetRange().Start, n.MapKey.GetRange().End, "constraint value must be a string") + return + } d2Col.Constraint = n.MapKey.Value.StringBox().Unbox().ScalarString() } } @@ -703,23 +719,6 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) { } } -// TODO too similar to flattenContainer, should reconcile in a refactor -func removeObject(g *d2graph.Graph, obj *d2graph.Object) { - for i := 0; i < len(obj.Graph.Objects); i++ { - if obj.Graph.Objects[i] == obj { - obj.Graph.Objects = append(obj.Graph.Objects[:i], obj.Graph.Objects[i+1:]...) - break - } - } - delete(obj.Parent.Children, obj.ID) - for i, child := range obj.Parent.ChildrenArray { - if obj == child { - obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray[:i], obj.Parent.ChildrenArray[i+1:]...) - break - } - } -} - func flattenContainer(g *d2graph.Graph, obj *d2graph.Object) { absID := obj.AbsID() @@ -798,15 +797,18 @@ func (c *compiler) validateKey(obj *d2graph.Object, m *d2ast.Map, mk *d2ast.Key) return } - if reserved == "" && obj.Attributes.Shape.Value == d2target.ShapeImage { - c.errorf(mk.Range.Start, mk.Range.End, "image shapes cannot have children.") - } + switch strings.ToLower(obj.Attributes.Shape.Value) { + case d2target.ShapeImage: + if reserved == "" { + c.errorf(mk.Range.Start, mk.Range.End, "image shapes cannot have children.") + } + case d2target.ShapeCircle, d2target.ShapeSquare: + checkEqual := (reserved == "width" && obj.Attributes.Height != nil) || + (reserved == "height" && obj.Attributes.Width != nil) - if reserved == "width" && obj.Attributes.Shape.Value != d2target.ShapeImage { - c.errorf(mk.Range.Start, mk.Range.End, "width is only applicable to image shapes.") - } - if reserved == "height" && obj.Attributes.Shape.Value != d2target.ShapeImage { - c.errorf(mk.Range.Start, mk.Range.End, "height is only applicable to image shapes.") + if checkEqual && obj.Attributes.Width.Value != obj.Attributes.Height.Value { + c.errorf(mk.Range.Start, mk.Range.End, fmt.Sprintf("width and height must be equal for %s shapes", obj.Attributes.Shape.Value)) + } } in := d2target.IsShape(obj.Attributes.Shape.Value) @@ -831,6 +833,14 @@ func (c *compiler) validateKey(obj *d2graph.Object, m *d2ast.Map, mk *d2ast.Key) return } + switch strings.ToLower(obj.Attributes.Shape.Value) { + case d2target.ShapeSQLTable, d2target.ShapeClass: + default: + if len(obj.Children) > 0 && (reserved == "width" || reserved == "height") { + c.errorf(mk.Range.Start, mk.Range.End, fmt.Sprintf("%s cannot be used on container: %s", reserved, obj.AbsID())) + } + } + if len(mk.Edges) > 0 { return } @@ -851,11 +861,33 @@ func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ast.Map) { func (c *compiler) validateNear(g *d2graph.Graph) { for _, obj := range g.Objects { if obj.Attributes.NearKey != nil { - _, ok := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey)) - if !ok { - c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "near key %#v does not exist. It must be the absolute path to a shape.", d2format.Format(obj.Attributes.NearKey)) + _, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey)) + _, isConst := d2graph.NearConstants[d2graph.Key(obj.Attributes.NearKey)[0]] + if !isKey && !isConst { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.Attributes.NearKey), strings.Join(d2graph.NearConstantsArray, ", ")) continue } + if !isKey && isConst && obj.Parent != g.Root { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "constant near keys can only be set on root level shapes") + continue + } + if !isKey && isConst && len(obj.ChildrenArray) > 0 { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "constant near keys cannot be set on shapes with children") + continue + } + if !isKey && isConst { + is := false + for _, e := range g.Edges { + if e.Src == obj || e.Dst == obj { + is = true + break + } + } + if is { + c.errorf(obj.Attributes.NearKey.GetRange().Start, obj.Attributes.NearKey.GetRange().End, "constant near keys cannot be set on connected shapes") + continue + } + } } } } diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 593c139d9..52a2e3149 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -7,13 +7,13 @@ import ( "testing" tassert "github.com/stretchr/testify/assert" - "oss.terrastruct.com/util-go/assert" - "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" ) func TestCompile(t *testing.T) { @@ -95,8 +95,119 @@ x: { height: 230 } `, - expErr: `d2/testdata/d2compiler/TestCompile/dimensions_on_nonimage.d2:3:2: width is only applicable to image shapes. -d2/testdata/d2compiler/TestCompile/dimensions_on_nonimage.d2:4:2: height is only applicable to image shapes. + assertions: func(t *testing.T, g *d2graph.Graph) { + if len(g.Objects) != 1 { + t.Fatalf("expected 1 objects: %#v", g.Objects) + } + if g.Objects[0].ID != "hey" { + t.Fatalf("expected g.Objects[0].ID to be 'hey': %#v", g.Objects[0]) + } + if g.Objects[0].Attributes.Shape.Value != d2target.ShapeHexagon { + t.Fatalf("expected g.Objects[0].Attributes.Shape.Value to be hexagon: %#v", g.Objects[0].Attributes.Shape.Value) + } + if g.Objects[0].Attributes.Width.Value != "200" { + t.Fatalf("expected g.Objects[0].Attributes.Width.Value to be 200: %#v", g.Objects[0].Attributes.Width.Value) + } + if g.Objects[0].Attributes.Height.Value != "230" { + t.Fatalf("expected g.Objects[0].Attributes.Height.Value to be 230: %#v", g.Objects[0].Attributes.Height.Value) + } + }, + }, + { + name: "equal_dimensions_on_circle", + + text: `hey: "" { + shape: circle + width: 200 + height: 230 +} +`, + expErr: `d2/testdata/d2compiler/TestCompile/equal_dimensions_on_circle.d2:3:2: width and height must be equal for circle shapes +d2/testdata/d2compiler/TestCompile/equal_dimensions_on_circle.d2:4:2: width and height must be equal for circle shapes +`, + }, + { + name: "single_dimension_on_circle", + + text: `hey: "" { + shape: circle + height: 230 +} +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + if len(g.Objects) != 1 { + t.Fatalf("expected 1 objects: %#v", g.Objects) + } + if g.Objects[0].ID != "hey" { + t.Fatalf("expected ID to be 'hey': %#v", g.Objects[0]) + } + if g.Objects[0].Attributes.Shape.Value != d2target.ShapeCircle { + t.Fatalf("expected Attributes.Shape.Value to be circle: %#v", g.Objects[0].Attributes.Shape.Value) + } + if g.Objects[0].Attributes.Width != nil { + t.Fatalf("expected Attributes.Width to be nil: %#v", g.Objects[0].Attributes.Width) + } + if g.Objects[0].Attributes.Height == nil { + t.Fatalf("Attributes.Height is nil") + } + }, + }, + { + name: "no_dimensions_on_containers", + + text: ` +containers: { + circle container: { + shape: circle + width: 512 + + diamond: { + shape: diamond + width: 128 + height: 64 + } + } + diamond container: { + shape: diamond + width: 512 + height: 256 + + circle: { + shape: circle + width: 128 + } + } + oval container: { + shape: oval + width: 512 + height: 256 + + hexagon: { + shape: hexagon + width: 128 + height: 64 + } + } + hexagon container: { + shape: hexagon + width: 512 + height: 256 + + oval: { + shape: oval + width: 128 + height: 64 + } + } +} +`, + expErr: `d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:5:3: width cannot be used on container: containers.circle container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:15:3: width cannot be used on container: containers.diamond container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:16:3: height cannot be used on container: containers.diamond container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:25:3: width cannot be used on container: containers.oval container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:26:3: height cannot be used on container: containers.oval container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:36:3: width cannot be used on container: containers.hexagon container +d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height cannot be used on container: containers.hexagon container `, }, { @@ -1266,6 +1377,50 @@ x -> y: { } }, }, + { + name: "near_constant", + + text: `x.near: top-center +`, + }, + { + name: "near_bad_constant", + + text: `x.near: txop-center +`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: 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_bad_container", + + text: `x: { + near: top-center + y +} +`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:1:1: constant near keys cannot be set on shapes with children +`, + }, + { + name: "near_bad_connected", + + text: `x: { + near: top-center +} +x -> y +`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:1:1: constant near keys cannot be set on connected shapes +`, + }, + { + name: "nested_near_constant", + + text: `x.y.near: top-center +`, + expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes +`, + }, { name: "reserved_icon_near_style", @@ -1312,7 +1467,7 @@ y expErr: `d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:3:9: bad icon url "::????:::%%orange": parse "::????:::%%orange": missing protocol scheme d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:4:18: expected "opacity" to be a number between 0.0 and 1.0 d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:5:18: expected "opacity" to be a number between 0.0 and 1.0 -d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" does not exist. It must be the absolute path to a shape. +d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" 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 `, }, { @@ -1465,8 +1620,8 @@ b`, g.Objects[0].Attributes.Label.Value) if len(g.Objects) != 1 { t.Fatal(g.Objects) } - assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name) - assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name) + assert.String(t, `GetType()`, g.Objects[0].SQLTable.Columns[0].Name.Label) + assert.String(t, `Is()`, g.Objects[0].SQLTable.Columns[1].Name.Label) }, }, { @@ -1490,8 +1645,8 @@ b`, g.Objects[0].Attributes.Label.Value) if len(g.Objects[0].ChildrenArray) != 1 { t.Fatal(g.Objects) } - assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name) - assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name) + assert.String(t, `GetType()`, g.Objects[1].SQLTable.Columns[0].Name.Label) + assert.String(t, `Is()`, g.Objects[1].SQLTable.Columns[1].Name.Label) }, }, { @@ -1618,6 +1773,17 @@ choo: { assert.String(t, "left", g.Objects[0].Attributes.Direction.Value) }, }, + { + name: "constraint_label", + + text: `foo { + label: bar + constraint: BIZ +}`, + assertions: func(t *testing.T, g *d2graph.Graph) { + assert.String(t, "bar", g.Objects[0].Attributes.Label.Value) + }, + }, { name: "invalid_direction", @@ -1639,6 +1805,44 @@ choo: { } }, }, + { + name: "null", + + text: `null +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + tassert.Equal(t, "'null'", g.Objects[0].ID) + tassert.Equal(t, "null", g.Objects[0].IDVal) + }, + }, + { + name: "sql-regression", + + text: `a: { + style: { + fill: lemonchiffon + } + b: { + shape: sql_table + c + } + d +} +`, + assertions: func(t *testing.T, g *d2graph.Graph) { + tassert.Equal(t, 3, len(g.Objects)) + }, + }, + { + name: "sql-panic", + text: `test { + shape: sql_table + test_id: varchar(64) {constraint: [primary_key, foreign_key]} +} +`, + expErr: `d2/testdata/d2compiler/TestCompile/sql-panic.d2:3:27: constraint value must be a string +`, + }, } for _, tc := range testCases { diff --git a/d2exporter/export.go b/d2exporter/export.go index 1f2a15b8d..9706365f0 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -5,15 +5,21 @@ import ( "strconv" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/d2themes/d2themescatalog" + "oss.terrastruct.com/util-go/go2" ) -func Export(ctx context.Context, g *d2graph.Graph, themeID int64) (*d2target.Diagram, error) { +func Export(ctx context.Context, g *d2graph.Graph, themeID int64, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) { theme := d2themescatalog.Find(themeID) diagram := d2target.NewDiagram() + if fontFamily == nil { + fontFamily = go2.Pointer(d2fonts.SourceSansPro) + } + diagram.FontFamily = fontFamily diagram.Shapes = make([]d2target.Shape, len(g.Objects)) for i := range g.Objects { @@ -34,6 +40,11 @@ func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Them if obj.Attributes.Shape.Value == d2target.ShapeText { shape.Color = theme.Colors.Neutrals.N1 } + if obj.Attributes.Shape.Value == d2target.ShapeSQLTable || obj.Attributes.Shape.Value == d2target.ShapeClass { + shape.PrimaryAccentColor = theme.Colors.B2 + shape.SecondaryAccentColor = theme.Colors.AA2 + shape.NeutralAccentColor = theme.Colors.Neutrals.N2 + } } func applyStyles(shape *d2target.Shape, obj *d2graph.Object) { @@ -45,6 +56,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 @@ -68,19 +81,17 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) { if obj.Attributes.Style.FontColor != nil { shape.Color = obj.Attributes.Style.FontColor.Value } - if obj.Attributes.Shape.Value != d2target.ShapeText { - if obj.Attributes.Style.Italic != nil { - shape.Italic, _ = strconv.ParseBool(obj.Attributes.Style.Italic.Value) - } - if obj.Attributes.Style.Bold != nil { - shape.Bold, _ = strconv.ParseBool(obj.Attributes.Style.Bold.Value) - } - if obj.Attributes.Style.Underline != nil { - shape.Underline, _ = strconv.ParseBool(obj.Attributes.Style.Underline.Value) - } - if obj.Attributes.Style.Font != nil { - shape.FontFamily = obj.Attributes.Style.Font.Value - } + if obj.Attributes.Style.Italic != nil { + shape.Italic, _ = strconv.ParseBool(obj.Attributes.Style.Italic.Value) + } + if obj.Attributes.Style.Bold != nil { + shape.Bold, _ = strconv.ParseBool(obj.Attributes.Style.Bold.Value) + } + if obj.Attributes.Style.Underline != nil { + shape.Underline, _ = strconv.ParseBool(obj.Attributes.Style.Underline.Value) + } + if obj.Attributes.Style.Font != nil { + shape.FontFamily = obj.Attributes.Style.Font.Value } } @@ -200,6 +211,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/d2exporter/export_test.go b/d2exporter/export_test.go index 1c08fbe1d..ca813d2b1 100644 --- a/d2exporter/export_test.go +++ b/d2exporter/export_test.go @@ -8,12 +8,15 @@ import ( "cdr.dev/slog" + tassert "github.com/stretchr/testify/assert" + "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/d2themes/d2themescatalog" "oss.terrastruct.com/d2/lib/geo" @@ -80,6 +83,23 @@ y: {shape: square} } }, }, + { + name: "sequence_group_position", + + dsl: `hey { + shape: sequence_diagram + a + b + group: { + a -> b + } +} +`, + assertions: func(t *testing.T, d *d2target.Diagram) { + tassert.Equal(t, "hey.group", d.Shapes[3].ID) + tassert.Equal(t, "INSIDE_TOP_LEFT", d.Shapes[3].LabelPosition) + }, + }, } runa(t, tcs) @@ -216,15 +236,15 @@ func run(t *testing.T, tc testCase) { ruler, err := textmeasure.NewRuler() assert.JSON(t, nil, err) - err = g.SetDimensions(nil, ruler) + err = g.SetDimensions(nil, ruler, nil) assert.JSON(t, nil, err) - err = d2dagrelayout.Layout(ctx, g) + err = d2sequence.Layout(ctx, g, d2dagrelayout.Layout) if err != nil { t.Fatal(err) } - got, err := d2exporter.Export(ctx, g, tc.themeID) + got, err := d2exporter.Export(ctx, g, tc.themeID, nil) if err != nil { t.Fatal(err) } diff --git a/d2format/escape.go b/d2format/escape.go index b6267ea80..9789a9c2d 100644 --- a/d2format/escape.go +++ b/d2format/escape.go @@ -48,7 +48,7 @@ func escapeUnquotedValue(s string, inKey bool) string { } if strings.EqualFold(s, "null") { - return "\\null" + return `'null'` } var b strings.Builder diff --git a/d2format/escape_test.go b/d2format/escape_test.go index c0090c03f..b7e67837c 100644 --- a/d2format/escape_test.go +++ b/d2format/escape_test.go @@ -170,7 +170,7 @@ func TestEscapeUnquoted(t *testing.T) { { name: "null", str: `null`, - exp: `\null`, + exp: `'null'`, }, { name: "empty", diff --git a/d2format/format.go b/d2format/format.go index 754e3302a..a45d55f63 100644 --- a/d2format/format.go +++ b/d2format/format.go @@ -81,7 +81,7 @@ func (p *printer) comment(c *d2ast.Comment) { lines := strings.Split(c.Value, "\n") for i, line := range lines { p.sb.WriteString("#") - if line != "" && !strings.HasPrefix(line, " ") { + if line != "" { p.sb.WriteByte(' ') } p.sb.WriteString(line) diff --git a/d2format/format_test.go b/d2format/format_test.go index 4bfea1519..883229068 100644 --- a/d2format/format_test.go +++ b/d2format/format_test.go @@ -592,6 +592,23 @@ hi # Fraud is the homage that force pays to reason. in: `x: {} `, exp: `x +`, + }, + { + name: "leading_space_comments", + in: `# foo +# foobar +# baz +x -> y +#foo +y +`, + exp: `# foo +# foobar +# baz +x -> y +# foo +y `, }, } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 838ffe6d7..7bd59ec6a 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -3,6 +3,7 @@ package d2graph import ( "errors" "fmt" + "math" "net/url" "strconv" "strings" @@ -21,6 +22,7 @@ import ( ) const INNER_LABEL_PADDING int = 5 +const DEFAULT_SHAPE_PADDING = 100. // TODO: Refactor with a light abstract layer on top of AST implementing scenarios, // variables, imports, substitutions and then a final set of structures representing @@ -392,14 +394,23 @@ func (obj *Object) GetFill(theme *d2themes.Theme) string { return theme.Colors.Neutrals.N5 } + if strings.EqualFold(shape, d2target.ShapeSQLTable) || strings.EqualFold(shape, d2target.ShapeClass) { + return theme.Colors.Neutrals.N1 + } + return theme.Colors.Neutrals.N7 } 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.ShapeText) { return theme.Colors.Neutrals.N1 } + if strings.EqualFold(shape, d2target.ShapeClass) || + strings.EqualFold(shape, d2target.ShapeSQLTable) { + return theme.Colors.Neutrals.N7 + } if dashGapSize != 0.0 { return theme.Colors.B2 } @@ -432,7 +443,14 @@ func (obj *Object) AbsIDArray() []string { } func (obj *Object) Text() *d2target.MText { - isBold := !obj.IsContainer() + isBold := !obj.IsContainer() && obj.Attributes.Shape.Value != "text" + isItalic := false + if obj.Attributes.Style.Bold != nil && obj.Attributes.Style.Bold.Value == "true" { + isBold = true + } + if obj.Attributes.Style.Italic != nil && obj.Attributes.Style.Italic.Value == "true" { + isItalic = true + } fontSize := d2fonts.FONT_SIZE_M if obj.OuterSequenceDiagram() == nil { if obj.IsContainer() { @@ -448,11 +466,14 @@ func (obj *Object) Text() *d2target.MText { if obj.Class != nil || obj.SQLTable != nil { fontSize = d2fonts.FONT_SIZE_XL } + if obj.Class != nil { + isBold = false + } return &d2target.MText{ Text: obj.Attributes.Label.Value, FontSize: fontSize, IsBold: isBold, - IsItalic: false, + IsItalic: isItalic, Language: obj.Attributes.Language, Shape: obj.Attributes.Shape.Value, @@ -649,6 +670,164 @@ func (obj *Object) AppendReferences(ida []string, ref Reference, unresolvedObj * } } +func (obj *Object) GetLabelSize(mtexts []*d2target.MText, ruler *textmeasure.Ruler, fontFamily *d2fonts.FontFamily) (*d2target.TextDimensions, error) { + shapeType := strings.ToLower(obj.Attributes.Shape.Value) + + var dims *d2target.TextDimensions + switch shapeType { + case d2target.ShapeText: + if obj.Attributes.Language == "latex" { + width, height, err := d2latex.Measure(obj.Text().Text) + if err != nil { + return nil, err + } + dims = d2target.NewTextDimensions(width, height) + } else if obj.Attributes.Language != "" { + var err error + dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text(), fontFamily) + if err != nil { + return nil, err + } + } else { + dims = GetTextDimensions(mtexts, ruler, obj.Text(), fontFamily) + } + + case d2target.ShapeClass: + dims = GetTextDimensions(mtexts, ruler, obj.Text(), go2.Pointer(d2fonts.SourceCodePro)) + + default: + dims = GetTextDimensions(mtexts, ruler, obj.Text(), fontFamily) + } + + if shapeType == d2target.ShapeSQLTable && obj.Attributes.Label.Value == "" { + // measure with placeholder text to determine height + placeholder := *obj.Text() + placeholder.Text = "Table" + dims = GetTextDimensions(mtexts, ruler, &placeholder, fontFamily) + } + + if dims == nil { + if shapeType == d2target.ShapeImage { + dims = d2target.NewTextDimensions(0, 0) + } else { + return nil, fmt.Errorf("dimensions for object label %#v not found", obj.Text()) + } + } + + return dims, nil +} + +func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.Ruler, fontFamily *d2fonts.FontFamily, labelDims d2target.TextDimensions) (*d2target.TextDimensions, error) { + dims := d2target.TextDimensions{} + + switch strings.ToLower(obj.Attributes.Shape.Value) { + default: + return d2target.NewTextDimensions(labelDims.Width, labelDims.Height), nil + + case d2target.ShapeImage: + return d2target.NewTextDimensions(128, 128), nil + + case d2target.ShapeClass: + maxWidth := labelDims.Width + + for _, f := range obj.Class.Fields { + fdims := GetTextDimensions(mtexts, ruler, f.Text(), go2.Pointer(d2fonts.SourceCodePro)) + if fdims == nil { + return nil, fmt.Errorf("dimensions for class field %#v not found", f.Text()) + } + lineWidth := fdims.Width + if maxWidth < lineWidth { + maxWidth = lineWidth + } + } + for _, m := range obj.Class.Methods { + mdims := GetTextDimensions(mtexts, ruler, m.Text(), go2.Pointer(d2fonts.SourceCodePro)) + if mdims == nil { + return nil, fmt.Errorf("dimensions for class method %#v not found", m.Text()) + } + lineWidth := mdims.Width + if maxWidth < lineWidth { + maxWidth = lineWidth + } + } + dims.Width = maxWidth + + // All rows should be the same height + var anyRowText *d2target.MText + if len(obj.Class.Fields) > 0 { + anyRowText = obj.Class.Fields[0].Text() + } else if len(obj.Class.Methods) > 0 { + anyRowText = obj.Class.Methods[0].Text() + } + if anyRowText != nil { + // 10px of padding top and bottom so text doesn't look squished + rowHeight := GetTextDimensions(mtexts, ruler, anyRowText, go2.Pointer(d2fonts.SourceCodePro)).Height + 20 + dims.Height = rowHeight * (len(obj.Class.Fields) + len(obj.Class.Methods) + 2) + } else { + dims.Height = labelDims.Height + } + + case d2target.ShapeSQLTable: + maxNameWidth := 0 + maxTypeWidth := 0 + constraintWidth := 0 + + for i := range obj.SQLTable.Columns { + // Note: we want to set dimensions of actual column not the for loop copy of the struct + c := &obj.SQLTable.Columns[i] + ctexts := c.Texts() + + nameDims := GetTextDimensions(mtexts, ruler, ctexts[0], fontFamily) + if nameDims == nil { + return nil, fmt.Errorf("dimensions for sql_table name %#v not found", ctexts[0].Text) + } + c.Name.LabelWidth = nameDims.Width + c.Name.LabelHeight = nameDims.Height + if maxNameWidth < nameDims.Width { + maxNameWidth = nameDims.Width + } + + typeDims := GetTextDimensions(mtexts, ruler, ctexts[1], fontFamily) + if typeDims == nil { + return nil, fmt.Errorf("dimensions for sql_table type %#v not found", ctexts[1].Text) + } + c.Type.LabelWidth = typeDims.Width + c.Type.LabelHeight = typeDims.Height + if maxTypeWidth < typeDims.Width { + maxTypeWidth = typeDims.Width + } + + if c.Constraint != "" { + // covers UNQ constraint with padding + constraintWidth = 60 + } + } + + // The rows get padded a little due to header font being larger than row font + dims.Height = labelDims.Height * (len(obj.SQLTable.Columns) + 1) + headerWidth := d2target.HeaderPadding + labelDims.Width + d2target.HeaderPadding + rowsWidth := d2target.NamePadding + maxNameWidth + d2target.TypePadding + maxTypeWidth + d2target.TypePadding + constraintWidth + dims.Width = go2.Max(headerWidth, rowsWidth) + } + + return &dims, nil +} + +func (obj *Object) GetPadding() (x, y float64) { + switch strings.ToLower(obj.Attributes.Shape.Value) { + case d2target.ShapeImage, + d2target.ShapeSQLTable, + d2target.ShapeText, + d2target.ShapeCode: + return 0., 0. + case d2target.ShapeClass: + // TODO fix class row width measurements (see SQL table) + return 100., 0. + default: + return DEFAULT_SHAPE_PADDING, DEFAULT_SHAPE_PADDING + } +} + type Edge struct { Index int `json:"index"` @@ -822,13 +1001,13 @@ func findMeasured(mtexts []*d2target.MText, t1 *d2target.MText) *d2target.TextDi return nil } -func getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText) (*d2target.TextDimensions, error) { +func getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily) (*d2target.TextDimensions, error) { if dims := findMeasured(mtexts, t); dims != nil { return dims, nil } if ruler != nil { - width, height, err := textmeasure.MeasureMarkdown(t.Text, ruler) + width, height, err := textmeasure.MeasureMarkdown(t.Text, ruler, fontFamily) if err != nil { return nil, err } @@ -838,7 +1017,7 @@ func getMarkdownDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t return nil, fmt.Errorf("text not pre-measured and no ruler provided") } -func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText) *d2target.TextDimensions { +func GetTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2target.MText, fontFamily *d2fonts.FontFamily) *d2target.TextDimensions { if dims := findMeasured(mtexts, t); dims != nil { return dims } @@ -858,7 +1037,10 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2 } else if t.IsItalic { style = d2fonts.FONT_STYLE_ITALIC } - w, h = ruler.Measure(d2fonts.SourceSansPro.Font(t.FontSize, style), t.Text) + if fontFamily == nil { + fontFamily = go2.Pointer(d2fonts.SourceSansPro) + } + w, h = ruler.Measure(fontFamily.Font(t.FontSize, style), t.Text) } return d2target.NewTextDimensions(w, h) } @@ -867,150 +1049,75 @@ func getTextDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, t *d2 } func appendTextDedup(texts []*d2target.MText, t *d2target.MText) []*d2target.MText { - if getTextDimensions(texts, nil, t) == nil { + if GetTextDimensions(texts, nil, t, nil) == nil { return append(texts, t) } return texts } -func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler) error { +func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler, fontFamily *d2fonts.FontFamily) error { for _, obj := range g.Objects { obj.Box = &geo.Box{} - // TODO fix edge cases for unnamed class etc - // Image shapes can set their own widths/heights - if obj.Attributes.Label.Value == "" && obj.Attributes.Shape.Value != d2target.ShapeImage { - obj.Width = 100 - obj.Height = 100 - continue + + var desiredWidth int + var desiredHeight int + if obj.Attributes.Width != nil { + desiredWidth, _ = strconv.Atoi(obj.Attributes.Width.Value) + } + if obj.Attributes.Height != nil { + desiredHeight, _ = strconv.Atoi(obj.Attributes.Height.Value) + } + shapeType := strings.ToLower(obj.Attributes.Shape.Value) + + labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily) + if err != nil { + return err } - var dims *d2target.TextDimensions - var innerLabelPadding = INNER_LABEL_PADDING - if obj.Attributes.Shape.Value == d2target.ShapeText { - if obj.Attributes.Language == "latex" { - width, height, err := d2latex.Measure(obj.Text().Text) - if err != nil { - return err - } - dims = d2target.NewTextDimensions(width, height) - } else { - var err error - dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text()) - if err != nil { - return err - } - } - innerLabelPadding = 0 - } else { - dims = getTextDimensions(mtexts, ruler, obj.Text()) - } - if dims == nil { - if obj.Attributes.Shape.Value == d2target.ShapeImage { - dims = d2target.NewTextDimensions(0, 0) - } else { - return fmt.Errorf("dimensions for object label %#v not found", obj.Text()) - } - } - - switch obj.Attributes.Shape.Value { + switch shapeType { case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode: // no labels default: if obj.Attributes.Label.Value != "" { - obj.LabelWidth = go2.Pointer(dims.Width) - obj.LabelHeight = go2.Pointer(dims.Height) + obj.LabelWidth = go2.Pointer(labelDims.Width) + obj.LabelHeight = go2.Pointer(labelDims.Height) } } - dims.Width += innerLabelPadding - dims.Height += innerLabelPadding - obj.LabelDimensions = *dims - obj.Width = float64(dims.Width) - obj.Height = float64(dims.Height) + if shapeType != d2target.ShapeText && obj.Attributes.Label.Value != "" { + labelDims.Width += INNER_LABEL_PADDING + labelDims.Height += INNER_LABEL_PADDING + } + obj.LabelDimensions = *labelDims - switch strings.ToLower(obj.Attributes.Shape.Value) { - default: - obj.Width += 100 - obj.Height += 100 + defaultDims, err := obj.GetDefaultSize(mtexts, ruler, fontFamily, *labelDims) + if err != nil { + return err + } - case d2target.ShapeImage: - if obj.Attributes.Width != nil { - w, _ := strconv.Atoi(obj.Attributes.Width.Value) - obj.Width = float64(w) - } else { - obj.Width = 128 - } - if obj.Attributes.Height != nil { - h, _ := strconv.Atoi(obj.Attributes.Height.Value) - obj.Height = float64(h) - } else { - obj.Height = 128 - } + obj.Width = float64(go2.Max(defaultDims.Width, desiredWidth)) + obj.Height = float64(go2.Max(defaultDims.Height, desiredHeight)) + paddingX, paddingY := obj.GetPadding() + + switch shapeType { case d2target.ShapeSquare, d2target.ShapeCircle: - sideLength := go2.Max(obj.Width, obj.Height) - obj.Width = sideLength + 100 - obj.Height = sideLength + 100 - - case d2target.ShapeClass: - maxWidth := dims.Width - - for _, f := range obj.Class.Fields { - fdims := getTextDimensions(mtexts, ruler, f.Text()) - if fdims == nil { - return fmt.Errorf("dimensions for class field %#v not found", f.Text()) - } - lineWidth := fdims.Width - if maxWidth < lineWidth { - maxWidth = lineWidth - } - } - for _, m := range obj.Class.Methods { - mdims := getTextDimensions(mtexts, ruler, m.Text()) - if mdims == nil { - return fmt.Errorf("dimensions for class method %#v not found", m.Text()) - } - lineWidth := mdims.Width - if maxWidth < lineWidth { - maxWidth = lineWidth - } + if desiredWidth != 0 || desiredHeight != 0 { + paddingX = 0. + paddingY = 0. } - // All rows should be the same height - var anyRowText *d2target.MText - if len(obj.Class.Fields) > 0 { - anyRowText = obj.Class.Fields[0].Text() - } else if len(obj.Class.Methods) > 0 { - anyRowText = obj.Class.Methods[0].Text() + sideLength := math.Max(obj.Width+paddingX, obj.Height+paddingY) + obj.Width = sideLength + obj.Height = sideLength + + default: + if desiredWidth == 0 { + obj.Width += float64(paddingX) } - if anyRowText != nil { - // 10px of padding top and bottom so text doesn't look squished - rowHeight := getTextDimensions(mtexts, ruler, anyRowText).Height + 20 - obj.Height = float64(rowHeight * (len(obj.Class.Fields) + len(obj.Class.Methods) + 2)) + if desiredHeight == 0 { + obj.Height += float64(paddingY) } - // Leave room for padding - obj.Width = float64(maxWidth + 100) - - case d2target.ShapeSQLTable: - maxWidth := dims.Width - - for _, c := range obj.SQLTable.Columns { - cdims := getTextDimensions(mtexts, ruler, c.Text()) - if cdims == nil { - return fmt.Errorf("dimensions for column %#v not found", c.Text()) - } - lineWidth := cdims.Width - if maxWidth < lineWidth { - maxWidth = lineWidth - } - } - - // The rows get padded a little due to header font being larger than row font - obj.Height = float64(dims.Height * (len(obj.SQLTable.Columns) + 1)) - // Leave room for padding - obj.Width = float64(maxWidth + 100) - - case d2target.ShapeText, d2target.ShapeCode: } } for _, edge := range g.Edges { @@ -1025,7 +1132,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler for _, label := range endpointLabels { t := edge.Text() t.Text = label - dims := getTextDimensions(mtexts, ruler, t) + dims := GetTextDimensions(mtexts, ruler, t, fontFamily) edge.MinWidth += dims.Width // Some padding as it's not totally near the end edge.MinHeight += dims.Height + 5 @@ -1035,7 +1142,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler continue } - dims := getTextDimensions(mtexts, ruler, edge.Text()) + dims := GetTextDimensions(mtexts, ruler, edge.Text(), fontFamily) if dims == nil { return fmt.Errorf("dimensions for edge label %#v not found", edge.Text()) } @@ -1063,7 +1170,9 @@ func (g *Graph) Texts() []*d2target.MText { } } else if obj.SQLTable != nil { for _, column := range obj.SQLTable.Columns { - texts = appendTextDedup(texts, column.Text()) + for _, t := range column.Texts() { + texts = appendTextDedup(texts, t) + } } } } @@ -1149,6 +1258,22 @@ var StyleKeywords = map[string]struct{}{ "filled": {}, } +// TODO maybe autofmt should allow other values, and transform them to conform +// e.g. left-center becomes center-left +var NearConstantsArray = []string{ + "top-left", + "top-center", + "top-right", + + "center-left", + "center-right", + + "bottom-left", + "bottom-center", + "bottom-right", +} +var NearConstants map[string]struct{} + func init() { for k, v := range StyleKeywords { ReservedKeywords[k] = v @@ -1156,4 +1281,8 @@ func init() { for k, v := range ReservedKeywordHolders { ReservedKeywords[k] = v } + NearConstants = make(map[string]struct{}, len(NearConstantsArray)) + for _, k := range NearConstantsArray { + NearConstants[k] = struct{}{} + } } diff --git a/d2graph/serde.go b/d2graph/serde.go index e5a8b1fbc..76e83ae2c 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -2,6 +2,7 @@ package d2graph import ( "encoding/json" + "strings" "oss.terrastruct.com/util-go/go2" ) @@ -48,7 +49,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error { for _, id := range so["ChildrenArray"].([]interface{}) { o := idToObj[id.(string)] childrenArray = append(childrenArray, o) - children[id.(string)] = o + children[strings.ToLower(id.(string))] = o o.Parent = idToObj[so["AbsID"].(string)] } diff --git a/d2graph/serde_test.go b/d2graph/serde_test.go index 2afc65cbb..070400f20 100644 --- a/d2graph/serde_test.go +++ b/d2graph/serde_test.go @@ -14,9 +14,7 @@ func TestSerialization(t *testing.T) { t.Parallel() g, err := d2compiler.Compile("", strings.NewReader("a.a.b -> a.a.c"), nil) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) asserts := func(g *d2graph.Graph) { assert.Equal(t, 4, len(g.Objects)) @@ -41,15 +39,33 @@ func TestSerialization(t *testing.T) { asserts(g) b, err := d2graph.SerializeGraph(g) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) var newG d2graph.Graph err = d2graph.DeserializeGraph(b, &newG) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) asserts(&newG) } + +func TestCasingRegression(t *testing.T) { + t.Parallel() + + script := `UserCreatedTypeField` + + g, err := d2compiler.Compile("", strings.NewReader(script), nil) + assert.Nil(t, err) + + _, ok := g.Root.HasChild([]string{"UserCreatedTypeField"}) + assert.True(t, ok) + + b, err := d2graph.SerializeGraph(g) + assert.Nil(t, err) + + var newG d2graph.Graph + err = d2graph.DeserializeGraph(b, &newG) + assert.Nil(t, err) + + _, ok = newG.Root.HasChild([]string{"UserCreatedTypeField"}) + assert.True(t, ok) +} diff --git a/d2layouts/d2dagrelayout/dagre.js b/d2layouts/d2dagrelayout/dagre.js index c1e1b462d..741ec0b62 100644 --- a/d2layouts/d2dagrelayout/dagre.js +++ b/d2layouts/d2dagrelayout/dagre.js @@ -76,7 +76,7 @@ var lodash;if(typeof require==="function"){try{lodash={cloneDeep:require("lodash /* * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs, * adds appropriate edges to ensure that all cluster nodes are placed between - * these boundries, and ensures that the graph is connected. + * these boundaries, and ensures that the graph is connected. * * In addition we ensure, through the use of the minlen property, that nodes * and subgraph border nodes to not end up on the same rank. @@ -179,7 +179,7 @@ g.graph().nodeRankFactor=nodeSep}function dfs(g,root,nodeSep,weight,height,depth _.forEach(g[relationship](v),function(e){var u=e.v===v?e.w:e.v,edge=result.edge(u,v),weight=!_.isUndefined(edge)?edge.weight:0;result.setEdge(u,v,{weight:g.edge(e).weight+weight})});if(_.has(node,"minRank")){result.setNode(v,{borderLeft:node.borderLeft[rank],borderRight:node.borderRight[rank]})}}});return result}function createRootNode(g){var v;while(g.hasNode(v=_.uniqueId("_root")));return v}},{"../graphlib":7,"../lodash":10}],16:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports=crossCount; /* * A function that takes a layering (an array of layers, each with an array of - * ordererd nodes) and a graph and returns a weighted crossing count. + * ordered nodes) and a graph and returns a weighted crossing count. * * Pre-conditions: * @@ -232,11 +232,11 @@ var cc=0;_.forEach(southEntries.forEach(function(entry){var index=entry.pos+firs * constraint graph this function will resolve any conflicts between the * constraint graph and the barycenters for the entries. If the barycenters for * an entry would violate a constraint in the constraint graph then we coalesce - * the nodes in the conflict into a new node that respects the contraint and + * the nodes in the conflict into a new node that respects the constraint and * aggregates barycenter and weight information. * * This implementation is based on the description in Forster, "A Fast and - * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it + * Simple Heuristic for Constrained Two-Level Crossing Reduction," though it * differs in some specific details. * * Pre-conditions: @@ -482,7 +482,7 @@ rank=0}return label.rank=rank}_.forEach(g.sources(),dfs)} * ({x, y, width, height}) if it were pointing at the rectangle's center. */function intersectRect(rect,point){var x=rect.x;var y=rect.y; // Rectangle intersection algorithm from: -// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes +// https://math.stackexchange.com/questions/108113/find-edge-between-two-boxes var dx=point.x-x;var dy=point.y-y;var w=rect.width/2;var h=rect.height/2;if(!dx&&!dy){throw new Error("Not possible to find intersection inside of the rectangle")}var sx,sy;if(Math.abs(dy)*w>Math.abs(dx)*h){ // Intersection is top or bottom of rect. if(dy<0){h=-h}sx=h*dx/dy;sy=h}else{ @@ -538,7 +538,7 @@ var offset=_.min(_.map(g.nodes(),function(v){return g.node(v).rank}));var layers */ var lib=require("./lib");module.exports={Graph:lib.Graph,json:require("./lib/json"),alg:require("./lib/alg"),version:lib.version}},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){var _=require("../lodash");module.exports=components;function components(g){var visited={};var cmpts=[];var cmpt;function dfs(v){if(_.has(visited,v))return;visited[v]=true;cmpt.push(v);_.each(g.successors(v),dfs);_.each(g.predecessors(v),dfs)}_.each(g.nodes(),function(v){cmpt=[];dfs(v);if(cmpt.length){cmpts.push(cmpt)}});return cmpts}},{"../lodash":49}],33:[function(require,module,exports){var _=require("../lodash");module.exports=dfs; /* - * A helper that preforms a pre- or post-order traversal on the input graph + * A helper that performs a pre- or post-order traversal on the input graph * and returns the nodes in the order they were visited. If the graph is * undirected then this algorithm will navigate using neighbors. If the graph * is directed then this algorithm will navigate using successors. @@ -1114,7 +1114,7 @@ function baseHasIn(object,key){return object!=null&&key in Object(object)}module function baseIsNaN(value){return value!==value}module.exports=baseIsNaN},{}],102:[function(require,module,exports){var isFunction=require("./isFunction"),isMasked=require("./_isMasked"),isObject=require("./isObject"),toSource=require("./_toSource"); /** * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + * [syntax characters](https://262.ecma-international.org/7.0/#sec-patterns). */var reRegExpChar=/[\\^$.*+?()[\]{}|]/g; /** Used to detect host constructors (Safari). */var reIsHostCtor=/^\[object .+?Constructor\]$/; /** Used for built-in method references. */var funcProto=Function.prototype,objectProto=Object.prototype; @@ -1718,7 +1718,7 @@ var freeGlobal=typeof global=="object"&&global&&global.Object===Object&&global;m /** Used to check objects for own properties. */var hasOwnProperty=objectProto.hasOwnProperty; /** * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * [`toStringTag`](https://262.ecma-international.org/7.0/#sec-object.prototype.tostring) * of values. */var nativeObjectToString=objectProto.toString; /** Built-in value references. */var symToStringTag=Symbol?Symbol.toStringTag:undefined; diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index e1d29175e..68d8ec0f6 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -64,22 +64,35 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { } rootAttrs := dagreGraphAttrs{ - ranksep: 100, edgesep: 40, nodesep: 60, } + isHorizontal := false switch g.Root.Attributes.Direction.Value { case "down": rootAttrs.rankdir = "TB" case "right": rootAttrs.rankdir = "LR" + isHorizontal = true case "left": rootAttrs.rankdir = "RL" + isHorizontal = true case "up": rootAttrs.rankdir = "BT" default: rootAttrs.rankdir = "TB" } + + maxLabelSize := 0 + for _, edge := range g.Edges { + size := edge.LabelDimensions.Width + if !isHorizontal { + size = edge.LabelDimensions.Height + } + maxLabelSize = go2.Max(maxLabelSize, size) + } + rootAttrs.ranksep = go2.Max(100, maxLabelSize+40) + configJS := setGraphAttrs(rootAttrs) if _, err := vm.RunString(configJS); err != nil { return err @@ -90,7 +103,14 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { for _, obj := range g.Objects { id := obj.AbsID() idToObj[id] = obj - loadScript += generateAddNodeLine(id, int(obj.Width), int(obj.Height)) + + height := obj.Height + if obj.LabelWidth != nil && obj.LabelHeight != nil { + if obj.Attributes.Shape.Value == d2target.ShapeImage || obj.Attributes.Icon != nil { + height += float64(*obj.LabelHeight) + label.PADDING + } + } + loadScript += generateAddNodeLine(id, int(obj.Width), int(height)) if obj.Parent != g.Root { loadScript += generateAddParentLine(id, obj.Parent.AbsID()) } @@ -151,8 +171,12 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { if obj.LabelWidth != nil && obj.LabelHeight != nil { if len(obj.ChildrenArray) > 0 { obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) - } else if obj.Attributes.Shape.Value == d2target.ShapeImage || obj.Attributes.Icon != nil { - obj.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) + } else if obj.Attributes.Shape.Value == d2target.ShapeImage { + obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter)) + // remove the extra height we added to the node when passing to dagre + obj.Height -= float64(*obj.LabelHeight) + label.PADDING + } else if obj.Attributes.Icon != nil { + obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) } else { obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } @@ -261,7 +285,7 @@ func escapeID(id string) string { // fixes \\ id = strings.ReplaceAll(id, "\\", `\\`) // replaces \n with \\n whenever \n is not preceded by \ (does not replace \\n) - re := regexp.MustCompile(`[^\\](\n)`) + re := regexp.MustCompile(`[^\\]\n`) id = re.ReplaceAllString(id, `\\n`) // avoid an unescaped \r becoming a \n in the layout result id = strings.ReplaceAll(id, "\r", `\r`) diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 2449a9751..ada0ca0e7 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -9,9 +9,10 @@ import ( _ "embed" "encoding/json" "fmt" - "math" + "strings" "github.com/dop251/goja" + "oss.terrastruct.com/util-go/xdefer" "oss.terrastruct.com/util-go/go2" @@ -20,6 +21,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 @@ -40,11 +42,12 @@ type ELKNode struct { } type ELKLabel struct { - Text string `json:"text"` - X float64 `json:"x"` - Y float64 `json:"y"` - Width float64 `json:"width"` - Height float64 `json:"height"` + Text string `json:"text"` + X float64 `json:"x"` + Y float64 `json:"y"` + Width float64 `json:"width"` + Height float64 `json:"height"` + LayoutOptions *ELKLayoutOptions `json:"layoutOptions,omitempty"` } type ELKPoint struct { @@ -75,13 +78,16 @@ type ELKGraph struct { } type ELKLayoutOptions struct { - Algorithm string `json:"elk.algorithm,omitempty"` - HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` - NodeSpacing float64 `json:"spacing.nodeNodeBetweenLayers,omitempty"` - Padding string `json:"elk.padding,omitempty"` - EdgeNodeSpacing float64 `json:"spacing.edgeNodeBetweenLayers,omitempty"` - Direction string `json:"elk.direction"` - SelfLoopSpacing float64 `json:"elk.spacing.nodeSelfLoop"` + Algorithm string `json:"elk.algorithm,omitempty"` + HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` + NodeSpacing float64 `json:"spacing.nodeNodeBetweenLayers,omitempty"` + Padding string `json:"elk.padding,omitempty"` + EdgeNodeSpacing float64 `json:"spacing.edgeNodeBetweenLayers,omitempty"` + Direction string `json:"elk.direction"` + SelfLoopSpacing float64 `json:"elk.spacing.nodeSelfLoop"` + InlineEdgeLabels bool `json:"elk.edgeLabels.inline,omitempty"` + ConsiderModelOrder string `json:"elk.layered.considerModelOrder.strategy,omitempty"` + ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"` } func Layout(ctx context.Context, g *d2graph.Graph) (err error) { @@ -104,11 +110,12 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { elkGraph := &ELKGraph{ ID: "root", LayoutOptions: &ELKLayoutOptions{ - Algorithm: "layered", - HierarchyHandling: "INCLUDE_CHILDREN", - NodeSpacing: 100.0, - EdgeNodeSpacing: 50.0, - SelfLoopSpacing: 50.0, + Algorithm: "layered", + HierarchyHandling: "INCLUDE_CHILDREN", + NodeSpacing: 100.0, + EdgeNodeSpacing: 50.0, + SelfLoopSpacing: 50.0, + ConsiderModelOrder: "NODES_AND_EDGES", }, } switch g.Root.Attributes.Direction.Value { @@ -139,15 +146,23 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { } walk(g.Root, nil, func(obj, parent *d2graph.Object) { + height := obj.Height + if obj.LabelWidth != nil && obj.LabelHeight != nil { + if obj.Attributes.Shape.Value == d2target.ShapeImage || obj.Attributes.Icon != nil { + height += float64(*obj.LabelHeight) + label.PADDING + } + } + n := &ELKNode{ ID: obj.AbsID(), Width: obj.Width, - Height: obj.Height, + Height: height, } if len(obj.ChildrenArray) > 0 { n.LayoutOptions = &ELKLayoutOptions{ - Padding: "[top=75,left=75,bottom=75,right=75]", + Padding: "[top=75,left=75,bottom=75,right=75]", + ForceNodeModelOrder: true, } } @@ -178,6 +193,9 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { Text: edge.Attributes.Label.Value, Width: float64(edge.LabelDimensions.Width), Height: float64(edge.LabelDimensions.Height), + LayoutOptions: &ELKLayoutOptions{ + InlineEdgeLabels: true, + }, }) } elkGraph.Edges = append(elkGraph.Edges, e) @@ -240,15 +258,18 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { parentX = parent.TopLeft.X parentY = parent.TopLeft.Y } - obj.TopLeft = geo.NewPoint(math.Round(parentX+n.X), math.Round(parentY+n.Y)) + obj.TopLeft = geo.NewPoint(parentX+n.X, parentY+n.Y) obj.Width = n.Width obj.Height = n.Height if obj.LabelWidth != nil && obj.LabelHeight != nil { if len(obj.ChildrenArray) > 0 { obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) - } else if obj.Attributes.Shape.Value == d2target.ShapeImage || obj.Attributes.Icon != nil { - obj.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) + } else if obj.Attributes.Shape.Value == d2target.ShapeImage { + obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter)) + obj.Height -= float64(*obj.LabelHeight) + label.PADDING + } else if obj.Attributes.Icon != nil { + obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) } else { obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } @@ -288,6 +309,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/d2near/layout.go b/d2layouts/d2near/layout.go new file mode 100644 index 000000000..99e8a10e1 --- /dev/null +++ b/d2layouts/d2near/layout.go @@ -0,0 +1,146 @@ +// d2near applies near keywords when they're constants +// Intended to be run as the last stage of layout after the diagram has already undergone layout +package d2near + +import ( + "context" + "math" + "strings" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/util-go/go2" +) + +const pad = 20 + +// Layout finds the shapes which are assigned constant near keywords and places them. +func Layout(ctx context.Context, g *d2graph.Graph, constantNears []*d2graph.Object) error { + if len(constantNears) == 0 { + return nil + } + + // Imagine the graph has two long texts, one at top center and one at top left. + // Top left should go left enough to not collide with center. + // So place the center ones first, then the later ones will consider them for bounding box + for _, processCenters := range []bool{true, false} { + for _, obj := range constantNears { + if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") { + obj.TopLeft = geo.NewPoint(place(obj)) + } + } + for _, obj := range constantNears { + if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") { + // The z-index for constant nears does not matter, as it will not collide + g.Objects = append(g.Objects, obj) + obj.Parent.Children[obj.ID] = obj + obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray, obj) + } + } + } + + // These shapes skipped core layout, which means they also skipped label placements + for _, obj := range constantNears { + if obj.Attributes.Shape.Value == d2target.ShapeImage { + obj.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter)) + } else if obj.Attributes.Icon != nil { + obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) + } else { + obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) + } + } + + return nil +} + +// place returns the position of obj, taking into consideration its near value and the diagram +func place(obj *d2graph.Object) (float64, float64) { + tl, br := boundingBox(obj.Graph) + w := br.X - tl.X + h := br.Y - tl.Y + switch d2graph.Key(obj.Attributes.NearKey)[0] { + case "top-left": + return tl.X - obj.Width - pad, tl.Y - obj.Height - pad + case "top-center": + return tl.X + w/2 - obj.Width/2, tl.Y - obj.Height - pad + case "top-right": + return br.X + pad, tl.Y - obj.Height - pad + case "center-left": + return tl.X - obj.Width - pad, tl.Y + h/2 - obj.Height/2 + case "center-right": + return br.X + pad, tl.Y + h/2 - obj.Height/2 + case "bottom-left": + return tl.X - obj.Width - pad, br.Y + pad + case "bottom-center": + return br.X - w/2 - obj.Width/2, br.Y + pad + case "bottom-right": + return br.X + pad, br.Y + pad + } + return 0, 0 +} + +// WithoutConstantNears plucks out the graph objects which have "near" set to a constant value +// This is to be called before layout engines so they don't take part in regular positioning +func WithoutConstantNears(ctx context.Context, g *d2graph.Graph) (nears []*d2graph.Object) { + for i := 0; i < len(g.Objects); i++ { + obj := g.Objects[i] + if obj.Attributes.NearKey == nil { + continue + } + _, isKey := g.Root.HasChild(d2graph.Key(obj.Attributes.NearKey)) + if isKey { + continue + } + _, isConst := d2graph.NearConstants[d2graph.Key(obj.Attributes.NearKey)[0]] + if isConst { + nears = append(nears, obj) + g.Objects = append(g.Objects[:i], g.Objects[i+1:]...) + i-- + delete(obj.Parent.Children, obj.ID) + for i := 0; i < len(obj.Parent.ChildrenArray); i++ { + if obj.Parent.ChildrenArray[i] == obj { + obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray[:i], obj.Parent.ChildrenArray[i+1:]...) + break + } + } + } + } + return nears +} + +// boundingBox gets the center of the graph as defined by shapes +// The bounds taking into consideration only shapes gives more of a feeling of true center +// It differs from d2target.BoundingBox which needs to include every visible thing +func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) { + if len(g.Objects) == 0 { + return geo.NewPoint(0, 0), geo.NewPoint(0, 0) + } + x1 := math.Inf(1) + y1 := math.Inf(1) + x2 := math.Inf(-1) + y2 := math.Inf(-1) + + for _, obj := range g.Objects { + if obj.Attributes.NearKey != nil { + // Top left should not be MORE top than top-center + // But it should go more left if top-center label extends beyond bounds of diagram + switch d2graph.Key(obj.Attributes.NearKey)[0] { + case "top-center", "bottom-center": + x1 = math.Min(x1, obj.TopLeft.X) + x2 = math.Max(x2, obj.TopLeft.X+obj.Width) + case "center-left", "center-right": + y1 = math.Min(y1, obj.TopLeft.Y) + y2 = math.Max(y2, obj.TopLeft.Y+obj.Height) + } + } else { + x1 = math.Min(x1, obj.TopLeft.X) + y1 = math.Min(y1, obj.TopLeft.Y) + x2 = math.Max(x2, obj.TopLeft.X+obj.Width) + y2 = math.Max(y2, obj.TopLeft.Y+obj.Height) + } + } + + return geo.NewPoint(x1, y1), geo.NewPoint(x2, y2) +} diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 7940e13b1..49ae00b43 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -5,24 +5,14 @@ import ( "sort" "strings" - "oss.terrastruct.com/util-go/go2" - "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/util-go/go2" ) -// Layout runs the sequence diagram layout engine on objects of shape sequence_diagram -// -// 1. Traverse graph from root, skip objects with shape not `sequence_diagram` -// 2. Construct a sequence diagram from all descendant objects and edges -// 3. Remove those objects and edges from the main graph -// 4. Run layout on sequence diagrams -// 5. Set the resulting dimensions to the main graph shape -// 6. Run core layouts (still without sequence diagram innards) -// 7. Put back sequence diagram innards in correct location -func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Context, g *d2graph.Graph) error) error { +func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string]*sequenceDiagram, map[string]int, map[string]int, error) { objectsToRemove := make(map[*d2graph.Object]struct{}) edgesToRemove := make(map[*d2graph.Edge]struct{}) sequenceDiagrams := make(map[string]*sequenceDiagram) @@ -42,7 +32,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte sd, err := layoutSequenceDiagram(g, obj) if err != nil { - return err + return nil, nil, nil, err } obj.Children = make(map[string]*d2graph.Object) obj.ChildrenArray = nil @@ -70,8 +60,27 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte layoutEdges, edgeOrder := getLayoutEdges(g, edgesToRemove) g.Edges = layoutEdges layoutObjects, objectOrder := getLayoutObjects(g, objectsToRemove) + // TODO this isn't a proper deletion because the objects still appear as children of the object g.Objects = layoutObjects + return sequenceDiagrams, objectOrder, edgeOrder, nil +} + +// Layout runs the sequence diagram layout engine on objects of shape sequence_diagram +// +// 1. Traverse graph from root, skip objects with shape not `sequence_diagram` +// 2. Construct a sequence diagram from all descendant objects and edges +// 3. Remove those objects and edges from the main graph +// 4. Run layout on sequence diagrams +// 5. Set the resulting dimensions to the main graph shape +// 6. Run core layouts (still without sequence diagram innards) +// 7. Put back sequence diagram innards in correct location +func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Context, g *d2graph.Graph) error) error { + sequenceDiagrams, objectOrder, edgeOrder, err := WithoutSequenceDiagrams(ctx, g) + if err != nil { + return err + } + if g.Root.IsSequenceDiagram() { // the sequence diagram is the only layout engine if the whole diagram is // shape: sequence_diagram @@ -89,7 +98,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 4eeac153a..1058d6037 100644 --- a/d2layouts/d2sequence/sequence_diagram.go +++ b/d2layouts/d2sequence/sequence_diagram.go @@ -74,6 +74,7 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se // Groups may have more nested groups for len(queue) > 0 { curr := queue[0] + curr.LabelPosition = go2.Pointer(string(label.InsideTopLeft)) groups = append(groups, curr) queue = queue[1:] queue = append(queue, curr.ChildrenArray...) @@ -299,14 +300,19 @@ func (sd *sequenceDiagram) placeActors() { // โ”‚ // โ”‚ func (sd *sequenceDiagram) addLifelineEdges() { - lastRoute := sd.messages[len(sd.messages)-1].Route endY := 0. - for _, p := range lastRoute { - endY = math.Max(endY, p.Y) + if len(sd.messages) > 0 { + lastRoute := sd.messages[len(sd.messages)-1].Route + for _, p := range lastRoute { + endY = math.Max(endY, p.Y) + } } for _, note := range sd.notes { endY = math.Max(endY, note.TopLeft.Y+note.Height) } + for _, actor := range sd.actors { + endY = math.Max(endY, actor.TopLeft.Y+actor.Height) + } endY += sd.yStep for _, actor := range sd.actors { @@ -461,8 +467,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/d2lib/d2.go b/d2lib/d2.go index 6ab141d95..936124b9f 100644 --- a/d2lib/d2.go +++ b/d2lib/d2.go @@ -9,7 +9,9 @@ import ( "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2layouts/d2near" "oss.terrastruct.com/d2/d2layouts/d2sequence" + "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/textmeasure" ) @@ -20,7 +22,13 @@ type CompileOptions struct { Ruler *textmeasure.Ruler Layout func(context.Context, *d2graph.Graph) error - ThemeID int64 + // FontFamily controls the font family used for all texts that are not the following: + // - code + // - latex + // - pre-measured (web setting) + // TODO maybe some will want to configure code font too, but that's much lower priority + FontFamily *d2fonts.FontFamily + ThemeID int64 } func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target.Diagram, *d2graph.Graph, error) { @@ -36,19 +44,30 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target } if len(g.Objects) > 0 { - err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler) + err = g.SetDimensions(opts.MeasuredTexts, opts.Ruler, opts.FontFamily) if err != nil { return nil, nil, err } - if layout, err := getLayout(opts); err != nil { + coreLayout, err := getLayout(opts) + if err != nil { return nil, nil, err - } else if err := d2sequence.Layout(ctx, g, layout); err != nil { + } + + constantNears := d2near.WithoutConstantNears(ctx, g) + + err = d2sequence.Layout(ctx, g, coreLayout) + if err != nil { + return nil, nil, err + } + + err = d2near.Layout(ctx, g, constantNears) + if err != nil { return nil, nil, err } } - diagram, err := d2exporter.Export(ctx, g, opts.ThemeID) + diagram, err := d2exporter.Export(ctx, g, opts.ThemeID, opts.FontFamily) return diagram, g, err } diff --git a/d2plugin/serve.go b/d2plugin/serve.go index 4f83aa8a8..b13680746 100644 --- a/d2plugin/serve.go +++ b/d2plugin/serve.go @@ -8,9 +8,9 @@ import ( "io" "github.com/spf13/pflag" - "oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/util-go/xmain" ) // Serve returns a xmain.RunFunc that will invoke the plugin p as necessary to service the diff --git a/d2renderers/d2fonts/d2fonts.go b/d2renderers/d2fonts/d2fonts.go index a2b57829e..fe50ffc2a 100644 --- a/d2renderers/d2fonts/d2fonts.go +++ b/d2renderers/d2fonts/d2fonts.go @@ -1,6 +1,7 @@ // d2fonts holds fonts for renderings // TODO write a script to do this as part of CI +// Currently using an online converter: https://dopiaza.org/tools/datauri/index.php package d2fonts import ( @@ -8,7 +9,7 @@ import ( "strings" ) -type FontFamily int +type FontFamily string type FontStyle string type Font struct { @@ -38,8 +39,9 @@ const ( FONT_STYLE_BOLD FontStyle = "bold" FONT_STYLE_ITALIC FontStyle = "italic" - SourceSansPro FontFamily = iota - SourceCodePro FontFamily = iota + SourceSansPro FontFamily = "SourceSansPro" + SourceCodePro FontFamily = "SourceCodePro" + HandDrawn FontFamily = "HandDrawn" ) var FontSizes = []int{ @@ -61,6 +63,7 @@ var FontStyles = []FontStyle{ var FontFamilies = []FontFamily{ SourceSansPro, SourceCodePro, + HandDrawn, } //go:embed encoded/SourceSansPro-Regular.txt @@ -75,6 +78,12 @@ var sourceSansProItalicBase64 string //go:embed encoded/SourceCodePro-Regular.txt var sourceCodeProRegularBase64 string +//go:embed encoded/ArchitectsDaughter-Regular.txt +var architectsDaughterRegularBase64 string + +//go:embed encoded/FuzzyBubbles-Bold.txt +var fuzzyBubblesBoldBase64 string + //go:embed ttf/* var fontFacesFS embed.FS @@ -99,6 +108,19 @@ func init() { Family: SourceCodePro, Style: FONT_STYLE_REGULAR, }: sourceCodeProRegularBase64, + { + Family: HandDrawn, + Style: FONT_STYLE_REGULAR, + }: architectsDaughterRegularBase64, + { + Family: HandDrawn, + Style: FONT_STYLE_ITALIC, + // This font has no italic, so just reuse regular + }: architectsDaughterRegularBase64, + { + Family: HandDrawn, + Style: FONT_STYLE_BOLD, + }: fuzzyBubblesBoldBase64, } for k, v := range FontEncodings { @@ -138,4 +160,24 @@ func init() { Family: SourceSansPro, Style: FONT_STYLE_ITALIC, }] = b + b, err = fontFacesFS.ReadFile("ttf/ArchitectsDaughter-Regular.ttf") + if err != nil { + panic(err) + } + FontFaces[Font{ + Family: HandDrawn, + Style: FONT_STYLE_REGULAR, + }] = b + FontFaces[Font{ + Family: HandDrawn, + Style: FONT_STYLE_ITALIC, + }] = b + b, err = fontFacesFS.ReadFile("ttf/FuzzyBubbles-Bold.ttf") + if err != nil { + panic(err) + } + FontFaces[Font{ + Family: HandDrawn, + Style: FONT_STYLE_BOLD, + }] = b } diff --git a/d2renderers/d2fonts/encoded/ArchitectsDaughter-Regular.txt b/d2renderers/d2fonts/encoded/ArchitectsDaughter-Regular.txt new file mode 100644 index 000000000..c8b93eb56 --- /dev/null +++ b/d2renderers/d2fonts/encoded/ArchitectsDaughter-Regular.txt @@ -0,0 +1 @@ +data:application/font-woff;base64,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AfsAEwFpADQAlQA0AeoAGAHHABoAigAXAogAFQIo/+oCQP//AiP/9gCDABwCAgAKAXwAQgKGACYA/wAKAs4AVAJmAEkChgAmAT4AHgDhABkCGQASAYAACQFQAAsAsAAZAfYAIQGEABIAkAAPAREAKwBzABoBFgAXAp4AMgJ7AA0CmAAXA3MACQHw/+MCkAAVApAAGgKQABoCkAAaApAAGgKQABoDggAeAtUAFgJeAB4CXgAeAl4AHgJeAB4AmwAIAJsADACb/8cAm//RAroACgKCABkCrgAWAq4AFgKuABYCrgAWAq4AFgHiAEwCrgAWAj8AFAI/ABQCPwAUAj8AFAIjABwCBgAhAtgASQIJABICCQASAgkAEgIJABICCQASAgkAEgOyABICiAAWAioAFAIqABQCKgAUAioAFACbABwAmwAhAJv/vACb/9oCUgAzAgAAIgJAADQCQAA0AkAANAJAADQCQAA0AWAAOgJAADQCCAANAggADQIIAA0CCAANAjIAFgITABgCMgAWApAAGgIJABICkAAhAgkAEgKQABoCCQASAtUAFgKIABYC1QAWAogAFgLVABYCiAAWAtUAFgKIABYCmAAmAl4AHgIqABQCXgAeAioAFAJeAB4CKgAUAl4AHgIqABQCXgAeAioAFALFABYCEAAXAsUAFgIQABcCxQAWAhAAFwLFABYCEAAXAmwAFQH7ABcCbAAVAfv/8QCb/5MAm/+EAJv/vACb/98Am//VAJv/9ACbABoAmwA2Ah4ACQCs/4QCbAArAnUAHgLFADcAoQAQAsUANwChAAgCxQA3AsUANwExABYCxQAJAKH/wQKCABkCAAAiAoIAGQIAACICggAZAgAAIgIAACICrgAWAkAANAKuABYCQAA0Aq4AFgJAADQEPAAWA6cAGALaAC4CNQAbAtoALgI1ABsC2gAuAjUAGwKSAA8CAgAWApIADwICABYCkgAPAgIAFgKSAA8CAgAWAn4AAAGeABACfgAAAn4AAAGe//YCPwAUAggADQI/ABQCCAANAj8AFAIIAA0CPwAUAggADQI/ABQCCAANAj8AFAIIAA0DwwAOAzwALgIjABwCMgAWAiMAHAMjABwB+wATAyMAHAH7ABMDIwAcAfsAEwJgAA4DggAdA7IAEgKuABYCQAA0ApIADwICABYBOgASAToAHwD8AB8AkAAiAOEAHwD8ACYBxwAaARcALAPDAA4DPAAuA8MADgM8AC4DwwAOAzwALgIjABwCMgAWAT4AHgGlAB4ApgAbAKYAGwCmACMA8gA2APIANgEfAFUBmgAXAZoAFwDFACEBsQAWAecAVAHjADIB0gAVAy4AHQTXAAABPgAeAr0ADgLOAA4BmgB7AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABXwAAAAEAAgADAAQABQAGAAcACAAJAAoACwAMAA0ADgAPABAAEQASABMAFAAVABYAFwAYABkAGgAbABwAHQAeAB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIAUwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAGEAowCEAIUAvQCWAOgAhgCOAIsAnQCpAKQAigDaAIMAkwDyAPMAjQCXAIgAwwDeAPEAngCqAPUA9AD2AKIArQDJAMcArgBiAGMAkABkAMsAZQDIAMoAzwDMAM0AzgDpAGYA0wDQANEArwBnAPAAkQDWANQA1QBoAOsA7QCJAGoAaQBrAG0AbABuAKAAbwBxAHAAcgBzAHUAdAB2AHcA6gB4AHoAeQB7AH0AfAC4AKEAfwB+AIAAgQDsAO4AugECAQMBBAEFAQYBBwD9AP4BCAEJAQoBCwD/AQABDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAD4APkBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAPoA1wEnASgBKQEqASsBLAEtAS4BLwEwATEA4gDjATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4AsACxAT8BQAFBAUIBQwFEAUUBRgFHAUgA+wD8AOQA5QFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQC7AV4BXwFgAWEA5gDnAKYBYgFjAWQBZQFmAWcA2ADhANsA3ADdAOAA2QDfAWgBaQFqAWsBbAFtAW4BbwCyALMAtgC3AMQAtAC1AMUAggDCAIcAqwC+AL8AvAFwAIwA7wDAAMEBcQdBbWFjcm9uB2FtYWNyb24GQWJyZXZlBmFicmV2ZQdBb2dvbmVrB2FvZ29uZWsLQ2NpcmN1bWZsZXgLY2NpcmN1bWZsZXgKQ2RvdGFjY2VudApjZG90YWNjZW50BkRjYXJvbgdFbWFjcm9uB2VtYWNyb24GRWJyZXZlBmVicmV2ZQpFZG90YWNjZW50CmVkb3RhY2NlbnQHRW9nb25lawdlb2dvbmVrBkVjYXJvbgZlY2Fyb24LR2NpcmN1bWZsZXgLZ2NpcmN1bWZsZXgKR2RvdGFjY2VudApnZG90YWNjZW50DEdjb21tYWFjY2VudAxnY29tbWFhY2NlbnQLSGNpcmN1bWZsZXgLaGNpcmN1bWZsZXgESGJhcgRoYmFyBkl0aWxkZQZpdGlsZGUHaW1hY3JvbgZJYnJldmUHSW9nb25lawdpb2dvbmVrC0pjaXJjdW1mbGV4C2pjaXJjdW1mbGV4DEtjb21tYWFjY2VudAxrY29tbWFhY2NlbnQGTGFjdXRlBmxhY3V0ZQxMY29tbWFhY2NlbnQMbGNvbW1hYWNjZW50BkxjYXJvbgRMZG90BGxkb3QGTmFjdXRlBm5hY3V0ZQxOY29tbWFhY2NlbnQMbmNvbW1hYWNjZW50Bk5jYXJvbgZuY2Fyb24LbmFwb3N0cm9waGUHT21hY3JvbgdvbWFjcm9uBk9icmV2ZQZvYnJldmUNT2h1bmdhcnVtbGF1dA1vaHVuZ2FydW1sYXV0BlJhY3V0ZQZyYWN1dGUMUmNvbW1hYWNjZW50DHJjb21tYWFjY2VudAZSY2Fyb24GcmNhcm9uBlNhY3V0ZQZzYWN1dGULU2NpcmN1bWZsZXgLc2NpcmN1bWZsZXgMVGNvbW1hYWNjZW50DHRjb21tYWFjY2VudAZUY2Fyb24EVGJhcgR0YmFyBlV0aWxkZQZ1dGlsZGUHVW1hY3Jvbgd1bWFjcm9uBlVicmV2ZQZ1YnJldmUFVXJpbmcFdXJpbmcNVWh1bmdhcnVtbGF1dA11aHVuZ2FydW1sYXV0B1VvZ29uZWsHdW9nb25lawtXY2lyY3VtZmxleAt3Y2lyY3VtZmxleAtZY2lyY3VtZmxleAt5Y2lyY3VtZmxleAZaYWN1dGUGemFjdXRlClpkb3RhY2NlbnQKemRvdGFjY2VudAdBRWFjdXRlB2FlYWN1dGULT3NsYXNoYWN1dGULb3NsYXNoYWN1dGUMU2NvbW1hYWNjZW50DHNjb21tYWFjY2VudAZXZ3JhdmUGd2dyYXZlBldhY3V0ZQZ3YWN1dGUJV2RpZXJlc2lzCXdkaWVyZXNpcwZZZ3JhdmUGeWdyYXZlBEV1cm8LY29tbWFhY2NlbnQAAAAADACWAAMAAQQJAAAAdCQGAAMAAQQJAAEAJiPgAAMAAQQJAAIADiPSAAMAAQQJAAMASiOIAAMAAQQJAAQANiNSAAMAAQQJAAUAJCMuAAMAAQQJAAYANCL6AAMAAQQJAAgAICLaAAMAAQQJAAkAICLaAAMAAQQJAAwANCKmAAMAAQQJAA0icAA2AAMAAQQJAA4ANgAAACAAaAB0AHQAcAA6AC8ALwBzAGMAcgBpAHAAdABzAC4AcwBpAGwALgBvAHIAZwAvAE8ARgBMAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABjACkAIAAyADAAMQAwACwAIABLAGkAbQBiAGUAcgBsAHkAIABHAGUAcwB3AGUAaQBuACAAKABrAGkAbQBiAGUAcgBsAHkAZwBlAHMAdwBlAGkAbgAuAGMAbwBtACkADQAKAA0ACgBUAGgAaQBzACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAGkAcwAgAGwAaQBjAGUAbgBzAGUAZAAgAHUAbgBkAGUAcgAgAHQAaABlACAAUwBJAEwAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUAbgBzAGUALAAgAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAuACAAIABUAGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGkAcwAgAGMAbwBwAGkAZQBkACAAYgBlAGwAbwB3ACwAIABhAG4AZAAgAGkAcwAgAGEAbABzAG8AIABhAHYAYQBpAGwAYQBiAGwAZQAgAHcAaQB0AGgAIABhACAARgBBAFEAIABhAHQAOgAgACAAaAB0AHQAcAA6AC8ALwBzAGMAcgBpAHAAdABzAC4AcwBpAGwALgBvAHIAZwAvAE8ARgBMAA0ACgANAAoADQAKAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQANAAoAUwBJAEwAIABPAFAARQBOACAARgBPAE4AVAAgAEwASQBDAEUATgBTAEUAIABWAGUAcgBzAGkAbwBuACAAMQAuADEAIAAtACAAMgA2ACAARgBlAGIAcgB1AGEAcgB5ACAAMgAwADAANwANAAoALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAA0ACgANAAoAUABSAEUAQQBNAEIATABFAA0ACgBUAGgAZQAgAGcAbwBhAGwAcwAgAG8AZgAgAHQAaABlACAATwBwAGUAbgAgAEYAbwBuAHQAIABMAGkAYwBlAG4AcwBlACAAKABPAEYATAApACAAYQByAGUAIAB0AG8AIABzAHQAaQBtAHUAbABhAHQAZQAgAHcAbwByAGwAZAB3AGkAZABlACAAZABlAHYAZQBsAG8AcABtAGUAbgB0ACAAbwBmACAAYwBvAGwAbABhAGIAbwByAGEAdABpAHYAZQAgAGYAbwBuAHQAIABwAHIAbwBqAGUAYwB0AHMALAAgAHQAbwAgAHMAdQBwAHAAbwByAHQAIAB0AGgAZQAgAGYAbwBuAHQAIABjAHIAZQBhAHQAaQBvAG4AIABlAGYAZgBvAHIAdABzACAAbwBmACAAYQBjAGEAZABlAG0AaQBjACAAYQBuAGQAIABsAGkAbgBnAHUAaQBzAHQAaQBjACAAYwBvAG0AbQB1AG4AaQB0AGkAZQBzACwAIABhAG4AZAAgAHQAbwAgAHAAcgBvAHYAaQBkAGUAIABhACAAZgByAGUAZQAgAGEAbgBkACAAbwBwAGUAbgAgAGYAcgBhAG0AZQB3AG8AcgBrACAAaQBuACAAdwBoAGkAYwBoACAAZgBvAG4AdABzACAAbQBhAHkAIABiAGUAIABzAGgAYQByAGUAZAAgAGEAbgBkACAAaQBtAHAAcgBvAHYAZQBkACAAaQBuACAAcABhAHIAdABuAGUAcgBzAGgAaQBwAA0ACgB3AGkAdABoACAAbwB0AGgAZQByAHMALgANAAoADQAKAFQAaABlACAATwBGAEwAIABhAGwAbABvAHcAcwAgAHQAaABlACAAbABpAGMAZQBuAHMAZQBkACAAZgBvAG4AdABzACAAdABvACAAYgBlACAAdQBzAGUAZAAsACAAcwB0AHUAZABpAGUAZAAsACAAbQBvAGQAaQBmAGkAZQBkACAAYQBuAGQAIAByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAZgByAGUAZQBsAHkAIABhAHMAIABsAG8AbgBnACAAYQBzACAAdABoAGUAeQAgAGEAcgBlACAAbgBvAHQAIABzAG8AbABkACAAYgB5ACAAdABoAGUAbQBzAGUAbAB2AGUAcwAuACAAVABoAGUAIABmAG8AbgB0AHMALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAYQBuAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdwBvAHIAawBzACwAIABjAGEAbgAgAGIAZQAgAGIAdQBuAGQAbABlAGQALAAgAGUAbQBiAGUAZABkAGUAZAAsACAAcgBlAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAGEAbgBkAC8AbwByACAAcwBvAGwAZAAgAHcAaQB0AGgAIABhAG4AeQAgAHMAbwBmAHQAdwBhAHIAZQAgAHAAcgBvAHYAaQBkAGUAZAAgAHQAaABhAHQAIABhAG4AeQAgAHIAZQBzAGUAcgB2AGUAZAAgAG4AYQBtAGUAcwAgAGEAcgBlACAAbgBvAHQAIAB1AHMAZQBkACAAYgB5ACAAZABlAHIAaQB2AGEAdABpAHYAZQAgAHcAbwByAGsAcwAuACAAVABoAGUAIABmAG8AbgB0AHMAIABhAG4AZAAgAGQAZQByAGkAdgBhAHQAaQB2AGUAcwAsACAAaABvAHcAZQB2AGUAcgAsACAAYwBhAG4AbgBvAHQAIABiAGUAIAByAGUAbABlAGEAcwBlAGQAIAB1AG4AZABlAHIAIABhAG4AeQAgAG8AdABoAGUAcgAgAHQAeQBwAGUAIABvAGYAIABsAGkAYwBlAG4AcwBlAC4AIABUAGgAZQAgAHIAZQBxAHUAaQByAGUAbQBlAG4AdAAgAGYAbwByACAAZgBvAG4AdABzACAAdABvACAAcgBlAG0AYQBpAG4AIAB1AG4AZABlAHIAIAB0AGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGQAbwBlAHMAIABuAG8AdAAgAGEAcABwAGwAeQAgAHQAbwAgAGEAbgB5ACAAZABvAGMAdQBtAGUAbgB0ACAAYwByAGUAYQB0AGUAZAAgAHUAcwBpAG4AZwAgAHQAaABlACAAZgBvAG4AdABzACAAbwByACAAdABoAGUAaQByACAAZABlAHIAaQB2AGEAdABpAHYAZQBzAC4ADQAKAA0ACgBEAEUARgBJAE4ASQBUAEkATwBOAFMADQAKACIARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAiACAAcgBlAGYAZQByAHMAIAB0AG8AIAB0AGgAZQAgAHMAZQB0ACAAbwBmACAAZgBpAGwAZQBzACAAcgBlAGwAZQBhAHMAZQBkACAAYgB5ACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApACAAdQBuAGQAZQByACAAdABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABhAG4AZAAgAGMAbABlAGEAcgBsAHkAIABtAGEAcgBrAGUAZAAgAGEAcwAgAHMAdQBjAGgALgAgAFQAaABpAHMAIABtAGEAeQAgAGkAbgBjAGwAdQBkAGUAIABzAG8AdQByAGMAZQAgAGYAaQBsAGUAcwAsACAAYgB1AGkAbABkACAAcwBjAHIAaQBwAHQAcwAgAGEAbgBkACAAZABvAGMAdQBtAGUAbgB0AGEAdABpAG8AbgAuAA0ACgANAAoAIgBSAGUAcwBlAHIAdgBlAGQAIABGAG8AbgB0ACAATgBhAG0AZQAiACAAcgBlAGYAZQByAHMAIAB0AG8AIABhAG4AeQAgAG4AYQBtAGUAcwAgAHMAcABlAGMAaQBmAGkAZQBkACAAYQBzACAAcwB1AGMAaAAgAGEAZgB0AGUAcgAgAHQAaABlACAAYwBvAHAAeQByAGkAZwBoAHQAIABzAHQAYQB0AGUAbQBlAG4AdAAoAHMAKQAuAA0ACgANAAoAIgBPAHIAaQBnAGkAbgBhAGwAIABWAGUAcgBzAGkAbwBuACIAIAByAGUAZgBlAHIAcwAgAHQAbwAgAHQAaABlACAAYwBvAGwAbABlAGMAdABpAG8AbgAgAG8AZgAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUAIABjAG8AbQBwAG8AbgBlAG4AdABzACAAYQBzACAAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYgB5ACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApAC4ADQAKAA0ACgAiAE0AbwBkAGkAZgBpAGUAZAAgAFYAZQByAHMAaQBvAG4AIgAgAHIAZQBmAGUAcgBzACAAdABvACAAYQBuAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAbQBhAGQAZQAgAGIAeQAgAGEAZABkAGkAbgBnACAAdABvACwAIABkAGUAbABlAHQAaQBuAGcALAAgAG8AcgAgAHMAdQBiAHMAdABpAHQAdQB0AGkAbgBnACAALQAtACAAaQBuACAAcABhAHIAdAAgAG8AcgAgAGkAbgAgAHcAaABvAGwAZQAgAC0ALQAgAGEAbgB5ACAAbwBmACAAdABoAGUAIABjAG8AbQBwAG8AbgBlAG4AdABzACAAbwBmACAAdABoAGUAIABPAHIAaQBnAGkAbgBhAGwAIABWAGUAcgBzAGkAbwBuACwAIABiAHkAIABjAGgAYQBuAGcAaQBuAGcAIABmAG8AcgBtAGEAdABzACAAbwByACAAYgB5ACAAcABvAHIAdABpAG4AZwAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAHQAbwAgAGEAIABuAGUAdwAgAGUAbgB2AGkAcgBvAG4AbQBlAG4AdAAuAA0ACgANAAoAIgBBAHUAdABoAG8AcgAiACAAcgBlAGYAZQByAHMAIAB0AG8AIABhAG4AeQAgAGQAZQBzAGkAZwBuAGUAcgAsACAAZQBuAGcAaQBuAGUAZQByACwAIABwAHIAbwBnAHIAYQBtAG0AZQByACwAIAB0AGUAYwBoAG4AaQBjAGEAbAAgAHcAcgBpAHQAZQByACAAbwByACAAbwB0AGgAZQByACAAcABlAHIAcwBvAG4AIAB3AGgAbwAgAGMAbwBuAHQAcgBpAGIAdQB0AGUAZAAgAHQAbwAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAuAA0ACgANAAoAUABFAFIATQBJAFMAUwBJAE8ATgAgACYAIABDAE8ATgBEAEkAVABJAE8ATgBTAA0ACgBQAGUAcgBtAGkAcwBzAGkAbwBuACAAaQBzACAAaABlAHIAZQBiAHkAIABnAHIAYQBuAHQAZQBkACwAIABmAHIAZQBlACAAbwBmACAAYwBoAGEAcgBnAGUALAAgAHQAbwAgAGEAbgB5ACAAcABlAHIAcwBvAG4AIABvAGIAdABhAGkAbgBpAG4AZwAgAGEAIABjAG8AcAB5ACAAbwBmACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACwAIAB0AG8AIAB1AHMAZQAsACAAcwB0AHUAZAB5ACwAIABjAG8AcAB5ACwAIABtAGUAcgBnAGUALAAgAGUAbQBiAGUAZAAsACAAbQBvAGQAaQBmAHkALAAgAHIAZQBkAGkAcwB0AHIAaQBiAHUAdABlACwAIABhAG4AZAAgAHMAZQBsAGwAIABtAG8AZABpAGYAaQBlAGQAIABhAG4AZAAgAHUAbgBtAG8AZABpAGYAaQBlAGQAIABjAG8AcABpAGUAcwAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAsACAAcwB1AGIAagBlAGMAdAAgAHQAbwAgAHQAaABlACAAZgBvAGwAbABvAHcAaQBuAGcAIABjAG8AbgBkAGkAdABpAG8AbgBzADoADQAKAA0ACgAxACkAIABOAGUAaQB0AGgAZQByACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbgBvAHIAIABhAG4AeQAgAG8AZgAgAGkAdABzACAAaQBuAGQAaQB2AGkAZAB1AGEAbAAgAGMAbwBtAHAAbwBuAGUAbgB0AHMALAAgAGkAbgAgAE8AcgBpAGcAaQBuAGEAbAAgAG8AcgAgAE0AbwBkAGkAZgBpAGUAZAAgAFYAZQByAHMAaQBvAG4AcwAsACAAbQBhAHkAIABiAGUAIABzAG8AbABkACAAYgB5ACAAaQB0AHMAZQBsAGYALgANAAoADQAKADIAKQAgAE8AcgBpAGcAaQBuAGEAbAAgAG8AcgAgAE0AbwBkAGkAZgBpAGUAZAAgAFYAZQByAHMAaQBvAG4AcwAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAG0AYQB5ACAAYgBlACAAYgB1AG4AZABsAGUAZAAsACAAcgBlAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAGEAbgBkAC8AbwByACAAcwBvAGwAZAAgAHcAaQB0AGgAIABhAG4AeQAgAHMAbwBmAHQAdwBhAHIAZQAsACAAcAByAG8AdgBpAGQAZQBkACAAdABoAGEAdAAgAGUAYQBjAGgAIABjAG8AcAB5ACAAYwBvAG4AdABhAGkAbgBzACAAdABoAGUAIABhAGIAbwB2AGUAIABjAG8AcAB5AHIAaQBnAGgAdAAgAG4AbwB0AGkAYwBlACAAYQBuAGQAIAB0AGgAaQBzACAAbABpAGMAZQBuAHMAZQAuACAAVABoAGUAcwBlACAAYwBhAG4AIABiAGUAIABpAG4AYwBsAHUAZABlAGQAIABlAGkAdABoAGUAcgAgAGEAcwAgAHMAdABhAG4AZAAtAGEAbABvAG4AZQAgAHQAZQB4AHQAIABmAGkAbABlAHMALAAgAGgAdQBtAGEAbgAtAHIAZQBhAGQAYQBiAGwAZQAgAGgAZQBhAGQAZQByAHMAIABvAHIAIABpAG4AIAB0AGgAZQAgAGEAcABwAHIAbwBwAHIAaQBhAHQAZQAgAG0AYQBjAGgAaQBuAGUALQByAGUAYQBkAGEAYgBsAGUAIABtAGUAdABhAGQAYQB0AGEAIABmAGkAZQBsAGQAcwAgAHcAaQB0AGgAaQBuACAAdABlAHgAdAAgAG8AcgAgAGIAaQBuAGEAcgB5ACAAZgBpAGwAZQBzACAAYQBzACAAbABvAG4AZwAgAGEAcwAgAHQAaABvAHMAZQAgAGYAaQBlAGwAZABzACAAYwBhAG4AIABiAGUAIABlAGEAcwBpAGwAeQAgAHYAaQBlAHcAZQBkACAAYgB5ACAAdABoAGUAIAB1AHMAZQByAC4ADQAKAA0ACgAzACkAIABOAG8AIABNAG8AZABpAGYAaQBlAGQAIABWAGUAcgBzAGkAbwBuACAAbwBmACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbQBhAHkAIAB1AHMAZQAgAHQAaABlACAAUgBlAHMAZQByAHYAZQBkACAARgBvAG4AdAAgAE4AYQBtAGUAKABzACkAIAB1AG4AbABlAHMAcwAgAGUAeABwAGwAaQBjAGkAdAAgAHcAcgBpAHQAdABlAG4AIABwAGUAcgBtAGkAcwBzAGkAbwBuACAAaQBzACAAZwByAGEAbgB0AGUAZAAgAGIAeQAgAHQAaABlACAAYwBvAHIAcgBlAHMAcABvAG4AZABpAG4AZwAgAEMAbwBwAHkAcgBpAGcAaAB0ACAASABvAGwAZABlAHIALgAgAFQAaABpAHMAIAByAGUAcwB0AHIAaQBjAHQAaQBvAG4AIABvAG4AbAB5ACAAYQBwAHAAbABpAGUAcwAgAHQAbwAgAHQAaABlACAAcAByAGkAbQBhAHIAeQAgAGYAbwBuAHQAIABuAGEAbQBlACAAYQBzAA0ACgBwAHIAZQBzAGUAbgB0AGUAZAAgAHQAbwAgAHQAaABlACAAdQBzAGUAcgBzAC4ADQAKAA0ACgA0ACkAIABUAGgAZQAgAG4AYQBtAGUAKABzACkAIABvAGYAIAB0AGgAZQAgAEMAbwBwAHkAcgBpAGcAaAB0ACAASABvAGwAZABlAHIAKABzACkAIABvAHIAIAB0AGgAZQAgAEEAdQB0AGgAbwByACgAcwApACAAbwBmACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAcwBoAGEAbABsACAAbgBvAHQAIABiAGUAIAB1AHMAZQBkACAAdABvACAAcAByAG8AbQBvAHQAZQAsACAAZQBuAGQAbwByAHMAZQAgAG8AcgAgAGEAZAB2AGUAcgB0AGkAcwBlACAAYQBuAHkAIABNAG8AZABpAGYAaQBlAGQAIABWAGUAcgBzAGkAbwBuACwAIABlAHgAYwBlAHAAdAAgAHQAbwAgAGEAYwBrAG4AbwB3AGwAZQBkAGcAZQAgAHQAaABlACAAYwBvAG4AdAByAGkAYgB1AHQAaQBvAG4AKABzACkAIABvAGYAIAB0AGgAZQAgAEMAbwBwAHkAcgBpAGcAaAB0ACAASABvAGwAZABlAHIAKABzACkAIABhAG4AZAAgAHQAaABlACAAQQB1AHQAaABvAHIAKABzACkAIABvAHIAIAB3AGkAdABoACAAdABoAGUAaQByACAAZQB4AHAAbABpAGMAaQB0ACAAdwByAGkAdAB0AGUAbgANAAoAcABlAHIAbQBpAHMAcwBpAG8AbgAuAA0ACgANAAoANQApACAAVABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACwAIABtAG8AZABpAGYAaQBlAGQAIABvAHIAIAB1AG4AbQBvAGQAaQBmAGkAZQBkACwAIABpAG4AIABwAGEAcgB0ACAAbwByACAAaQBuACAAdwBoAG8AbABlACwAIABtAHUAcwB0ACAAYgBlACAAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAZQBuAHQAaQByAGUAbAB5ACAAdQBuAGQAZQByACAAdABoAGkAcwAgAGwAaQBjAGUAbgBzAGUALAAgAGEAbgBkACAAbQB1AHMAdAAgAG4AbwB0ACAAYgBlACAAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAdQBuAGQAZQByACAAYQBuAHkAIABvAHQAaABlAHIAIABsAGkAYwBlAG4AcwBlAC4AIABUAGgAZQAgAHIAZQBxAHUAaQByAGUAbQBlAG4AdAAgAGYAbwByACAAZgBvAG4AdABzACAAdABvACAAcgBlAG0AYQBpAG4AIAB1AG4AZABlAHIAIAB0AGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGQAbwBlAHMAIABuAG8AdAAgAGEAcABwAGwAeQAgAHQAbwAgAGEAbgB5ACAAZABvAGMAdQBtAGUAbgB0ACAAYwByAGUAYQB0AGUAZAAgAHUAcwBpAG4AZwAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAuAA0ACgANAAoAVABFAFIATQBJAE4AQQBUAEkATwBOAA0ACgBUAGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGIAZQBjAG8AbQBlAHMAIABuAHUAbABsACAAYQBuAGQAIAB2AG8AaQBkACAAaQBmACAAYQBuAHkAIABvAGYAIAB0AGgAZQAgAGEAYgBvAHYAZQAgAGMAbwBuAGQAaQB0AGkAbwBuAHMAIABhAHIAZQAgAG4AbwB0ACAAbQBlAHQALgANAAoADQAKAEQASQBTAEMATABBAEkATQBFAFIADQAKAFQASABFACAARgBPAE4AVAAgAFMATwBGAFQAVwBBAFIARQAgAEkAUwAgAFAAUgBPAFYASQBEAEUARAAgACIAQQBTACAASQBTACIALAAgAFcASQBUAEgATwBVAFQAIABXAEEAUgBSAEEATgBUAFkAIABPAEYAIABBAE4AWQAgAEsASQBOAEQALAAgAEUAWABQAFIARQBTAFMAIABPAFIAIABJAE0AUABMAEkARQBEACwAIABJAE4AQwBMAFUARABJAE4ARwAgAEIAVQBUACAATgBPAFQAIABMAEkATQBJAFQARQBEACAAVABPACAAQQBOAFkAIABXAEEAUgBSAEEATgBUAEkARQBTACAATwBGACAATQBFAFIAQwBIAEEATgBUAEEAQgBJAEwASQBUAFkALAAgAEYASQBUAE4ARQBTAFMAIABGAE8AUgAgAEEAIABQAEEAUgBUAEkAQwBVAEwAQQBSACAAUABVAFIAUABPAFMARQAgAEEATgBEACAATgBPAE4ASQBOAEYAUgBJAE4ARwBFAE0ARQBOAFQAIABPAEYAIABDAE8AUABZAFIASQBHAEgAVAAsACAAUABBAFQARQBOAFQALAAgAFQAUgBBAEQARQBNAEEAUgBLACwAIABPAFIAIABPAFQASABFAFIAIABSAEkARwBIAFQALgAgAEkATgAgAE4ATwAgAEUAVgBFAE4AVAAgAFMASABBAEwATAAgAFQASABFAA0ACgBDAE8AUABZAFIASQBHAEgAVAAgAEgATwBMAEQARQBSACAAQgBFACAATABJAEEAQgBMAEUAIABGAE8AUgAgAEEATgBZACAAQwBMAEEASQBNACwAIABEAEEATQBBAEcARQBTACAATwBSACAATwBUAEgARQBSACAATABJAEEAQgBJAEwASQBUAFkALAAgAEkATgBDAEwAVQBEAEkATgBHACAAQQBOAFkAIABHAEUATgBFAFIAQQBMACwAIABTAFAARQBDAEkAQQBMACwAIABJAE4ARABJAFIARQBDAFQALAAgAEkATgBDAEkARABFAE4AVABBAEwALAAgAE8AUgAgAEMATwBOAFMARQBRAFUARQBOAFQASQBBAEwAIABEAEEATQBBAEcARQBTACwAIABXAEgARQBUAEgARQBSACAASQBOACAAQQBOACAAQQBDAFQASQBPAE4AIABPAEYAIABDAE8ATgBUAFIAQQBDAFQALAAgAFQATwBSAFQAIABPAFIAIABPAFQASABFAFIAVwBJAFMARQAsACAAQQBSAEkAUwBJAE4ARwAgAEYAUgBPAE0ALAAgAE8AVQBUACAATwBGACAAVABIAEUAIABVAFMARQAgAE8AUgAgAEkATgBBAEIASQBMAEkAVABZACAAVABPACAAVQBTAEUAIABUAEgARQAg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NjIeAjI+AjcyHgEUBgcGIyInLgF1DQkGKgsXAzggNgYbAw88IQgUHSQmKxkVGRcGDQEPESw+LhwUHAHWu/YfCQgBv4NRCA0wSg0DFSYiDhomGBUPFiQSLx8SFwAAAv+8AAYAyQJjAAgAFgAAExYXBiMiJicRJyI1ND4CNzIXFhQGB3UNCQYqCxcDSDIXOVcwKgsBCRIB1rv2HwkIAb9AGQ4KCA8FFwQJDwb////f//8AoALSEiYALAAAEAcBPP/AAJT////V/00AiQJGEiYALAAAEAYBP68GAAD////0/1UAqAKBEiYATAAAEAYBP84OAAD//wAa//8AeQLEEiYALAAAEAcBPQAAAJwAAQA2AAYAiwHWAAgAABMWFwYjIiYnEXUNCQYqCxcDAda79h8JCAG///8ACQAEAgYDjBImAC0AABAHAToAfADdAAL/hP6RANkCrwAiADoAABcUIyIuAScmJzQ2NR4BMzI3NjU0JicmNDcmNzYzBhQeARcWAiY0Njc2NxYXFhcWFAcGJy4CJwYHBgejWAkbNRtBEg4hTCscEg8NBg4CAxkTFwkBCwcRzRcVEyo+MxEcJAYFChklDRgfPRUIBsinAQcJFioFHwEZLCokJFWhUKV5KBgMCT5ITZRKoQJjCiYkFCwdIQ4ZOAwVBhAPGwwaHiokDA4A//8AK/8FAmEC0BImAC4AABAGAV5yAAAA//8AHv8FAmQC2xImAE4AABAGAV52AAAA//8ANwAOArQDWBImAC8AABAHAHQBBwDo//8AEP//AIYDPBImAE8AABAHAHT/9wDM//8AN/8FArQCrhImAC8AABAHAV4AngAA//8ACP8FAJMCqhImAE8AABAGAV6NAAAA//8ANwAOArQCrhImAC8AABAHAU0BAAAA//8ANwAOArQCrhImAC8AABAHAHcBKAAA//8AFv//AQcCqhAmAE8AABAHAHcAoQAA//8ACQAOArQCrhImAC8AABAGAUrrHAAA////wf//AM4CqhImAE8AABAGAUqjAAAA//8AGf/4AmADZRImADEAABAHAHQA8AD1//8AIv/6AeMCcBImAFEAABAHAHQAqwAA//8AGf8FAmACzBImADEAABAGAV59AAAA//8AIv8FAeMB8xImAFEAABAGAV47AAAA//8AGf/4AmADcRImADEAABAHATsAqQEz//8AIv/6AeMCgBImAFEAABAGATtoQgAA//8AIv/6AeMDDRImAFEAABAHAV7/sANL//8AFgAOAo4C2RImADIAABAHAG8AqAB2//8ANP//AjACYxImAFIAABAGAG9xAAAA//8AFgAOAo4C9RImADIAABAHATwAzAC3//8ANP//AjACPhImAFIAABAHATwAnAAA//8AFgAOAo4DFRImADIAABAHAUEAvgCD//8ANP//AjACkhImAFIAABAHAUEAkQAAAAIAFv/yBDQCewAwAEAAAAEyFxUGBwYHBhYXFhUyPgEzFQUXMjc2MxQOAScGBTUGBwYiJicmNTQ+AjMyFzcWMwQGFBYXFjI3Nj8BJicmIgYDV1UkHGS5PxECAQQ9tmAy/oIFdV+vVxoTCn7+iS1NHWdyKlgxVXFBdEkBG1L+FicdHTzmMA4HARtXH1RUAm4NMgQBAg5OPBAnNBcLNjCkCRIUIgoHDh9YORMIJydTlEV6WzZtYwilW2lgIkheHB7EdSMMLAADABj/6gOWAd4AMQBEAEsAADc0NzY3MjMyFhc2NzY6ARcWFxYUBw4CBwYHBhQXFjMyNzY3MhYVBgcGIyImJwYjIiYkPgE3NTQnJiMOAQcGBxQXFjI2ASYjIgcGFRhmUXIGBzJCDjxtISYVIEwlDwIhVVkqXR4NAhlnOTtITQoPJGJMTEdgGDylX2EBaB0DAhwPHidPI0wXIRlzRQG6FzUmHDmpdkg5DC4pYh4JCBEtEx4JGBEHAwcuEysNWxQaIw8LPiUdLyRwWBA2NgxBPxwPBxAWMoU+FREJAUgkCxYj//8ALv/0AqsDLhImADUAABAHAHQA0AC+//8AG//1AiICcBImAFUAABAHAHQAxgAA//8ALv8FAqsChxImADUAABAHAV4AqQAA//8AG/8FAiIB4hImAFUAABAGAV5WAAAA//8ALv/0AqsDKRImADUAABAHATsAjQDr//8AG//1AiICeRImAFUAABAHATsAggA7//8AD///AnAC2xImADYAABAHAHQA9QBr//8AFv/jAd8CcBImAFYAABAHAHQArQAA//8AD///AnADPRImADYAABAHAToAmgCO//8AFv/jAd8CrxImAFYAABAGATpdAAAA//8AD/8XAnACYBImADYAABAHAHgAxgAA//8AFv8aAd8ByhImAFYAABAHAHgAtgAD//8AD///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 diff --git a/d2renderers/d2fonts/encoded/FuzzyBubbles-Bold.txt b/d2renderers/d2fonts/encoded/FuzzyBubbles-Bold.txt new file mode 100644 index 000000000..22c7da96d --- /dev/null +++ b/d2renderers/d2fonts/encoded/FuzzyBubbles-Bold.txt @@ -0,0 +1 @@ +data:application/font-woff;base64,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AiYCEQG3ATsB/wI3AjEA4gG8AfMB+AEdAeoB7QGCAekB6AIwAgMAGQAIABAAIAAXAB4ABgAlADsALgAxADgAVwBPAFIAVABBAHAAgQBzAHYAjwB9Ab8AjQCxAKcAqgCsAMcApQF6AOoA1ADcAPkA5gDzAOgBGQFYAUsBTgFVAZsBkgGVAZcBYwHIAdwBzAHOAfAB1QFAAe4CPwI5AjsCPQJXAi4CWQAcAO0ACQDVAB0A7wAjARMAJgEaACcBGwAkARgAKgE5ACsBOgA+AV0ALwFMADkBVgBAAWEAMAFNAEcBdwBFAXUASQF5AEgBeABNAYcATAGGAFwBoQBaAZ4AUAGTAFsBoABVAZoAXgGjAGABpgGnAGMBqgBlAawAZAGrAGYBrQBoAbUAbAHBAG4BxABtAcMAPwFgAIsB5gB0Ac0AiQHkAHIB2QCUAg0AlgIPAJUCDgCaAhoAngIeAJwCHACbAhsAowIsAKICKwChAioAvgJNALsCSQCoAjoAvQJMALkCRwC8AksAwgJRAMgCWADJANACYQDSAmMA0QJiAIMB3gCzAkEAKQAsAUkAYgBnAbMAawBvAcYADwDbAFEBlAB1AHUAqQCpAK8ArwCtAK0ArgCuAEYBdgCMAesAHwD0AAcA6QCOAe8AFgDlABsA7AA3AVQAPQFbAFMBlgBZAZ0AfAHUAIoB5QCXAhAAmAIWAKsCPAC6AkgAnwIfAKQCLQB+AdYAkAHxAH8B1wDNAl4CAAFIASABFAEBAUQCEwHaAjIBjAF8AOMBIQIzAbkBAwFFAT4BiAIUAY0BFQENAS8BRwE9AS4BHgHbAMQCUwDBAlAAwwJSABgA5wAaAOsAEQDdABMA3wAUAOAAFQDhABIA3gAKANYADADYAA0A2QAOANoACwDXADoBVwA8AVkAQgFkADIBTwA0AVEANQFSADYBUwAzAVAAWAGcAFYBmQCAAdgAggHdAHcBzwB5AdEAegHSAHsB0wB4AdAAhAHfAIYB4QCHAeIAiAHjAIUB4ACwAj4AsgJAALQCQgC2AkQAtwJFALgCRgC1AkMAywJcAMoCWgDMAl0AzgJfAV8BXgIGAgcCBQE1ATYBEAG+AiABcwGxAcIB/AFDAWUBqAH9AYACGAGyAbsCFwGwAX8BrgISAWwBbgABAAAACgC6AU4AAkRGTFQAmGxhdG4ADgB2AAhBWkUg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As4AIwPBACMDwQAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwLOACMCzgAjAs4AIwK1ADICpgAyAqYAMgKmADICpgAyAqYAMgKmADIC+gAyBSkAMgL6ADIC+gAyBI8AMgI+ADICPgAyAj4AMgI+ADICPgAyAj4AMgI+ADICPgAyAj4AMgI+ADICPv/4Aj4AMgI+ADICPgAyAj4AMgI+ADICPgAyAj4AMgK/ADYCPgAyAvoAMgI+ADICJgAmApcAMgKXADIClwAyApcAMgKXADIClwAyAuYAiwLwADIC8AAyAvAAMgGCADIBggAyAYIALQGCACsBggAhAYL/qwGCADIBggAyAYIAMgGCADIBggAyAYIAKQGCACEBggAyAYIAIgJmACgCZgAoAmsAOwJrADsCDQA7BHMAOwINACoCDQA7Ag0AOwINADsDdQA7Ag3/5AMsADYCvwA2BSUANgK/ADYCvwA2Ar8ANgQnADYCvwA2AroAMgP6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ADICugAyAroAMgK6ACUCugAlAroAMgK6ADIChAAyAvIANQKXADYClwA2ApcANgKXADYClwAlApcANgJIAC0CSAAtAkgALQJIAC0CnABGAkgALQJIAC0CpgAoAqYAKAKmACgCpgAoAqYAKAKEADICsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7ArAAOwKwADsCsAA7Ao0ALQN7ADsDewA7A3sAOwN7ADsDewA7AqEANgJNAC0CTQAtAk0ALQJNAC0CTQAtAk0ALQJNAC0CTQAtAk0ALQJ1ADICdQAyAnUAMgJ1ADICYQAyAmEAMgJhADICYQAyAmEAMgJhADICYQAyAmEAMgJhADICYQAyAmEAMgJhADICYQAyAmEAMgJhADIAzwAAAAAAAAAA/+0CYQAyAmEAMgJhADIDzwAyA88AMgJhADICYQAyAmEAMgJhADIC0wAtAmEAMgCWAAAB5QAyAasANwJhADICYQAyAdsALQKhAGUB+QAoA7QARAJhADICegA7Aw8AOQGCAIwBggAiAYIAigGCADABggCQAVYAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//sAAP/7AAAAFAAAAAABggCMARoAMgEaADICQwA2AkMANgFNAAAAAAAAAAAAFQAA//YCQwA2AkMANgJDADYCQwA2AqYAMgEcAEIAAABCAlcAIwFNAAAAAAAAAAD/9gAAAAAAAP/2AAAAAAAA//YAAAAAAAD/9gAAAAAAAP/2ANUANgKmADIBJAAUAAAAEAAA//0AAP/9AvIAHgMTADIB4wAqAokAHgGOACgBjgAoAAAAAAAA/+wCiQAeAokAHgG0ADYBLP/6AAAAAAAA//oAAP/6AckAVQJSAB4CSAAtAokAHgBr//oAAP/6AAD/+gAA//oBOgAABH8AHgIqADICKgAyAioAMgIqADICKgAyAioAMgIqADICKgAyAioAMgIqADICKgAqAioAMgIqADICKgAyAioAMgIqADICoQAyAioAMgLBAJICKgAyA8sAVQKCAFUCogA7AioAMgHTAFUCogAyAioAMgKBAAYBxADHAZQAnQG9ACMDegAjBIAAIwR/ACMCkgAjAk0AMgLCACMBvQAjAlIAIwGCACQC4gAlAgP/8gJIAC0CSAAtAkgALQJIAC0CSAAtAkgALQLmAIsA0AAAAAAAAAAA/+wB2wCDAikAbgKaADQCrgBkAsgAZQHJAGQByQBlAmsAOwJr//gCa//CAAD/+gAA//oAAP/qAAD/6gEOAAIAAAACAAAAAAHJAFUByQBVAQYAUQEGADkBBgAFAQYAAwEG//kBBv+NAQYAEwEGAFEBBgA7AQYAUQEGADgBBgBGAQYACwEG//kDDgA7AQYAFgEG//oBaP+IAWj/iAFo/4gCXAA7AlwAOwJcADsCa//8AQUAXAEFAFMBBQBcAQUADQEFAFwByQCIAdsAWQIpAIwCgAAoAjEAAAJtAFwDPAAoAQX/8gNAADsBJQAAAAAAAAAAAAAAAP/2AqIAUAKN/8YByQBVAUcAAALiAIQCogA7AqIAOwK/AAoCogA7AqIAOwJXAC0ECgA7AdMAKQKiADsDGAAoBEYAKAKmADICpgAyAqYAMgKmADICpgAyAqYAMgKmADICpgAyAqYAMgKmAC8CpgAyAqYAMgKmADICpgAyBBoAMgDzAAAAAAAAAqYAMgKmADICpgAyAqYAMgKmADICpgAyAqYAMgKmADICpgAyAqYAMgKmADIBvgAtA2AALQNOAC0BUQA8AqYAMgHvADICHgAyAqYAJQKmACUCpgAyAqYAMgJrADsCWAAjAeUAKAHWAC0DIgAyASQAkgE0AGUAP//0AHP/9AP5ADIChAALAoQALAIhAB4CIQAeAIYAAAJcAB4ByQAuAdsAKgHMAC0BJP+JAYMAPAGDABQA4AA8AOAAFAEkABQA5AAeAZUAOwGVADsBlQAuAZX/9wGV/7gDEwAyAckAkADgAAAAAAAAAAAAAAGVADYChAAUAtgAKAH3ACQB9wAkAfcAJAH3ACQCKgA2AfcAJAH3ACQB2AAAAfcAJAEkABQCLwAtAnUAMgLiACUBgwBVAoAAKAAAAAAB5QAjAeUAIwHlACMB5QAjAeUAIwJrADsCUgAtA5kALQFrACgBdAAAAAAAAAAA//YDdwAVAkMANgFtACYCjQA7Ao0AOwKNADsCjQA7Ao0AOwKNADsCjQA7Ao0AOwKNADsCjQA7Ao0AOwKNADsCjQA7Ao0AOwKNADsCjQA7Ao0AOwKNADsDQAAAAo0AOwKNADsCjQA7AhQAMQM2ADsDNgA7AzYAOwM2ADsDNgA7A4EAGQI+ADICQwAyAkMAMgJDADICQwAyAkMAMgJNAC0CQwAyAkMAMgJDADICQwAyAfYAKQH2ACkB9gApAfYAKQL/ADawACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwjISMhLbADLCBkswMUFQBCQ7ATQyBgYEKxAhRDQrElA0OwAkNUeCCwDCOwAkNDYWSwBFB4sgICAkNgQrAhZRwhsAJDQ7IOFQFCHCCwAkMjQrITARNDYEIjsABQWGVZshYBAkNgQi2wBCywAyuwFUNYIyEjIbAWQ0MjsABQWGVZGyBkILDAULAEJlqyKAENQ0VjRbAGRVghsAMlWVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBDUNF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AU4BTwFQAVEBUgFTAGcBVAFVAVYA0wFXAVgBWQFaAVsBXAFdAV4BXwFgAWEAkQFiAK8BYwAzADQANQFkAWUBZgFnAWgANgFpAOQA+wFqAWsBbAA3AW0BbgFvAXAA7QA4ANQBcQFyANUBcwBoAXQBdQF2AXcA1gF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGEADkAOgGFAYYBhwGIADsAPADrAYkAuwGKAYsBjAGNAY4APQGPAOYBkABEAGkBkQGSAZMBlAGVAZYBlwBrAZgBmQGaAZsBnACNAZ0BngGfAGwBoACgAaEAagGiAaMBpAAJAaUBpgGnAKcAbgGoAEEAYQANACMAbQBFAD8AXwBeAGAAPgBAANsBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQDoAIcBtgBGAP4A4QG3AbgBuQEAAG8BugG7AbwA3gG9AIQA2AG+Ab8BwAHBAcIBwwHEAcUBxgHHAB0ByAAPAckBygHLAcwAiwC9AEcAggDCAc0BzgHPAQEAgwCOAdAB0QHSALgB0wAHAdQA3AHVAdYB1wHYAdkASABwAdoB2wByAdwB3QHeAd8B4AHhAHMB4gHjAHEB5AAbAeUAqwHmALMAsgHnAegAIADqAekB6gAEAKMASQHrAewB7QDAABgAwQCmABcB7gC8APcASgD5Ae8B8AHxAfIAiQBDAfMB9AAhAJUB9QCpAKoAvgC/AEsB9gH3AfgB+QH6AfsA3wH8Af0AEAH+AEwAdAH/AgAAdgIBAHcCAgIDANcAdQIEAgUCBgCSAgcCCABNAgkCCgBOAgsCDAINAE8CDgIPAhACEQISAB8AlAITAhQCFQCkAOMAUADaAhYCFwIYAhkCGgDvAhsA8ABRAhwCHQIeAh8AHAIgAI8AeAAGAiEAUgB5AiIAewIjAiQCJQImAicCKAB8AikCKgIrALEA4AIsAHoCLQIuAi8CMAIxAjICMwI0AjUCNgAUAPQA9QI3AjgAnQCeAKECOQB9AjoAUwCIAAsADAAIABEAwwI7AjwAxgI9Aj4ADgCTAj8AVAAiAKIABQDFALQAtQC2ALcAxAAKAFUCQAJBAkICQwCKAkQA3QJFAkYCRwJIAkkAVgJKAOUA/AJLAkwCTQJOAIYAHgAaABkAEgJPAIUCUABXAlECUgJTAlQA7gAWAPYCVQDZAlYCVwCMABUCWABYAH4CWQCAAloAgQJbAH8CXAJdAl4CXwJgAmECYgJjAmQCZQBCAmYCZwJoAFkAWgJpAmoCawJsAm0AWwBcAOwCbgC6Am8AlgJwAnECcgJzAF0CdADnAnUAEwJDUgROVUxMB3VuaTAwQTAHQUVhY3V0ZQZBYnJldmUHdW5pMUVBRQd1bmkxRUI2B3VuaTFFQjAHdW5pMUVCMgd1bmkxRUI0B3VuaTAxQ0QHdW5pMUVBNAd1bmkxRUFDB3VuaTFFQTYHdW5pMUVBOAd1bmkxRUFBB3VuaTAyMDAHdW5pMUVBMAd1bmkxRUEyB3VuaTAyMDIHQW1hY3JvbgdBb2dvbmVrCkFyaW5nYWN1dGULQ2NpcmN1bWZsZXgKQ2RvdGFjY2VudAd1bmkwMUM0BkRjYXJvbgZEY3JvYXQHdW5pMDFDNQZFYnJldmUGRWNhcm9uB3VuaTFFQkUHdW5pMUVDNgd1bmkxRUMwB3VuaTFFQzIHdW5pMUVDNAd1bmkwMjA0CkVkb3RhY2NlbnQHdW5pMUVCOAd1bmkxRUJBB3VuaTAyMDYHRW1hY3JvbgNFbmcHRW9nb25lawd1bmkxRUJDBkdjYXJvbgtHY2lyY3VtZmxleAd1bmkwMTIyCkdkb3RhY2NlbnQHdW5pMUU5RQRIYmFyC0hjaXJjdW1mbGV4BklicmV2ZQd1bmkwMUNGB3VuaTAyMDgHdW5pMUVDQQd1bmkxRUM4B3VuaTAyMEEHSW1hY3JvbgdJb2dvbmVrBkl0aWxkZQtKY2lyY3VtZmxleAd1bmkwMTM2B3VuaTAxQzcGTGFjdXRlBkxjYXJvbgd1bmkwMTNCBExkb3QHdW5pMDFDOAd1bmkwMUNBBk5hY3V0ZQZOY2Fyb24HdW5pMDE0NQd1bmkwMUNCBk9icmV2ZQd1bmkwMUQxB3VuaTFFRDAHdW5pMUVEOAd1bmkxRUQyB3VuaTFFRDQHdW5pMUVENgd1bmkwMjBDB3VuaTAyMkEHdW5pMDIzMAd1bmkxRUNDB3VuaTFFQ0UFT2hvcm4HdW5pMUVEQQd1bmkxRUUyB3VuaTFFREMHdW5pMUVERQd1bmkxRUUwDU9odW5nYXJ1bWxhdXQHdW5pMDIwRQdPbWFjcm9uB3VuaTAxRUELT3NsYXNoYWN1dGUHdW5pMDIyQwZSYWN1dGUGUmNhcm9uB3VuaTAxNTYHdW5pMDIxMAd1bmkwMjEyBlNhY3V0ZQd1bmkwMThGC1NjaXJjdW1mbGV4B3VuaTAyMTgEVGJhcgZUY2Fyb24HdW5pMDE2Mgd1bmkwMjFBBlVicmV2ZQd1bmkwMUQzB3VuaTAyMTQHdW5pMDFEOQd1bmkwMURCB3VuaTAxRDUHdW5pMUVFNAd1bmkxRUU2BVVob3JuB3VuaTFFRTgHdW5pMUVGMAd1bmkxRUVBB3VuaTFFRUMHdW5pMUVFRQ1VaHVuZ2FydW1sYXV0B3VuaTAyMTYHVW1hY3JvbgdVb2dvbmVrBVVyaW5nBlV0aWxkZQZXYWN1dGULV2NpcmN1bWZsZXgJV2RpZXJlc2lzBldncmF2ZQtZY2lyY3VtZmxleAd1bmkxRUY0BllncmF2ZQd1bmkxRUY2B3VuaTAyMzIHdW5pMUVGOAZaYWN1dGUKWmRvdGFjY2VudAZhYnJldmUHdW5pMUVBRgd1bmkxRUI3B3VuaTFFQjEHdW5pMUVCMwd1bmkxRUI1B3VuaTAxQ0UHdW5pMUVBNQd1bmkxRUFEB3VuaTFFQTcHdW5pMUVBOQd1bmkxRUFCCWFjdXRlY29tYg5hY3V0ZWNvbWIuY2FzZQd1bmkwMjAxB3VuaTFFQTEHYWVhY3V0ZQd1bmkxRUEzB3VuaTAyMDMHYW1hY3Jvbgdhb2dvbmVrB3VuaTAyQkMHdW5pRjhGRgphcmluZ2FjdXRlB3VuaTAzMkUHdW5pMDMwNgx1bmkwMzA2LmNhc2ULdW5pMDMwNjAzMDEQdW5pMDMwNjAzMDEuY2FzZQt1bmkwMzA2MDMwMBB1bmkwMzA2MDMwMC5jYXNlC3VuaTAzMDYwMzA5EHVuaTAzMDYwMzA5LmNhc2ULdW5pMDMwNjAzMDMQdW5pMDMwNjAzMDMuY2FzZQd1bmkwMzExDHVuaTAzMTEuY2FzZQd1bmkyMjE5B3VuaTAzMEMLdW5pMDMwQy5hbHQMdW5pMDMwQy5jYXNlC2NjaXJjdW1mbGV4CmNkb3RhY2NlbnQHdW5pMjBCNQd1bmkwMzI3B3VuaTAzMDIMdW5pMDMwMi5jYXNlC3VuaTAzMDIwMzAxEHVuaTAzMDIwMzAxLmNhc2ULdW5pMDMwMjAzMDAQdW5pMDMwMjAzMDAuY2FzZQt1bmkwMzAyMDMwORB1bmkwMzAyMDMwOS5jYXNlC3VuaTAzMDIwMzAzEHVuaTAzMDIwMzAzLmNhc2UNY29sb25tb25ldGFyeQd1bmkwMzI2B3VuaTAzMTIMdW5pMDMxMi5jYXNlB3VuaTIwNTIHdW5pMDMwRgx1bmkwMzBGLmNhc2UGZGNhcm9uB3VuaTAzMjQHdW5pMDMwOAx1bmkwMzA4LmNhc2UHdW5pMjIxNQRkb25nB3VuaTAzMDcMdW5pMDMwNy5jYXNlDGRvdGJlbG93Y29tYgd1bmkwMkJBB3VuaTAxQzYGZWJyZXZlBmVjYXJvbgd1bmkxRUJGB3VuaTFFQzcHdW5pMUVDMQd1bmkxRUMzB3VuaTFFQzUHdW5pMDIwNQplZG90YWNjZW50B3VuaTFFQjkHdW5pMUVCQgd1bmkwMjA3B2VtYWNyb24DZW5nB2VvZ29uZWsHdW5pMUVCRARFdXJvA2ZfZgVmX2ZfaQVmX2ZfbAd1bmkyMDc0BmdjYXJvbgtnY2lyY3VtZmxleAd1bmkwMTIzCmdkb3RhY2NlbnQJZ3JhdmVjb21iDmdyYXZlY29tYi5jYXNlB3VuaTIwQjIEaGJhcgtoY2lyY3VtZmxleA1ob29rYWJvdmVjb21iEmhvb2thYm92ZWNvbWIuY2FzZQd1bmkwMzFCDHVuaTAzMUIuY2FzZQd1bmkwMzBCDHVuaTAzMEIuY2FzZQd1bmkyMDEwBmlicmV2ZQd1bmkwMUQwB3VuaTAyMDkJaS5sb2NsVFJLB3VuaTFFQ0IHdW5pMUVDOQd1bmkwMjBCB2ltYWNyb24HaW9nb25lawZpdGlsZGULamNpcmN1bWZsZXgHdW5pMDIzNwd1bmkwMTM3DGtncmVlbmxhbmRpYwd1bmkyMEFEBmxhY3V0ZQZsY2Fyb24HdW5pMDEzQwRsZG90B3VuaTI3RTgEbGlyYQd1bmkyMEJBB3VuaTAxQzkHdW5pMDMzMQd1bmkwMzA0DHVuaTAzMDQuY2FzZQd1bmkyMEJDB3VuaTAwQjUGbWludXRlBm5hY3V0ZQd1bmkyMEE2Bm5jYXJvbgd1bmkwMTQ2B3VuaTAxQ0MHdW5pMjExNgZvYnJldmUHdW5pMUVEMQd1bmkxRUQ5B3VuaTFFRDMHdW5pMUVENQd1bmkxRUQ3B3VuaTAyMEQHdW5pMDIyQgd1bmkwMjMxB3VuaTFFQ0QHdW5pMDMyOAd1bmkxRUNGBW9ob3JuB3VuaTFFREIHdW5pMUVFMwd1bmkxRUREB3VuaTFFREYHdW5pMUVFMQ1vaHVuZ2FydW1sYXV0B3VuaTAyMEYHb21hY3Jvbgd1bmkwMEI5B3VuaTAxRUILb3NsYXNoYWN1dGUHdW5pMDIyRBZwZXJpb2RjZW50ZXJlZC5sb2NsQ0FUG3BlcmlvZGNlbnRlcmVkLmxvY2xDQVQuY2FzZQZwZXNldGEHdW5pMjBCMQd1bmkwMkI5BnJhY3V0ZQZyY2Fyb24HdW5pMDE1Nwd1bmkwMjExB3VuaTI3RTkHdW5pMDMwQQx1bmkwMzBBLmNhc2UHdW5pMDIxMwd1bmkyMEJEB3VuaTIwQjkGc2FjdXRlB3VuaTAyNTkLc2NpcmN1bWZsZXgHdW5pMDIxOQZzZWNvbmQHdW5pMDBBRAd1bmkwMzM1BHRiYXIGdGNhcm9uB3VuaTAxNjMHdW5pMDIxQgd1bmkwMEIzCXRpbGRlY29tYg50aWxkZWNvbWIuY2FzZQd1bmkwMEIyBnVicmV2ZQd1bmkwMjE1B3VuaTFFRTUHdW5pMUVFNwV1aG9ybgd1bmkxRUU5B3VuaTFFRjEHdW5pMUVFQgd1bmkxRUVEB3VuaTFFRUYNdWh1bmdhcnVtbGF1dAd1bmkwMjE3B3VtYWNyb24HdW9nb25lawV1cmluZwZ1dGlsZGUGd2FjdXRlC3djaXJjdW1mbGV4CXdkaWVyZXNpcwZ3Z3JhdmUHdW5pMjBBOQt5Y2lyY3VtZmxleAd1bmkxRUY1BnlncmF2ZQd1bmkxRUY3B3VuaTAyMzMHdW5pMUVGOQZ6YWN1dGUKemRvdGFjY2VudAABAAAACgAmAEYAAkRGTFQADmxhdG4ADgAEAAAAAP//AAIAAAABAAJtYXJrABpta21rAA4AAAAEAAEAAgADAAQAAAABAAAABQNwAv4AuACEAAwABgAQAAEACgADAAEAXgAoAAEAMgAMAAoAFgAWABYAFgAWAVgAFgFSABYBTAABANoCCAACAAEBIQEqAAAABgAAACYAAAAmAAAAIAAAACAAAAAaAAAAGgABAKcBNAAB/7gBjgABAEsBPwABAAYBfAF9AYgBiQIzAjQABgAQAAEACgACAAEAIgAiAAEAGAAMAAIABgAGAAEA4wKHAAIAAB2GAAAdhgABAAIBigGLAAYAEAAB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AAATgBPCAAATvAAAE4YTpAAAE7wAABN6E8IAABO8AAATdBPCAAATvAAAE24TwgAAE7wAABNoE8IAABO8AAATYhPCAAATvAAAE8gTpAAAE7wAABNcE1YAAAAAAAATUBNWAAAAAAAAE0oTwgAAE7wAABNEE8IAABO8AAATPhPCAAATvAAAEzgTwgAAE7wAABPIE8IAABO8AAATMhPCAAATvAAAEywTwgAAE7wAABMmE8IAABO8AAATIBMaAAAAAAAAExQTGgAAAAAAABMOExoAAAAAAAATIBMIAAAAAAAAEwITGgAAAAAAABL8ExoAAAAAAAAXmheUAAAAAAAAF5oXlAAAAAAAABL2EvAS6gAAEuQS9hLwEuoAABLkEvYS8BLqAAAS5BLeFRgAAAAAAAAS9hLYEuoAABLkAAAAABLqAAAAABLSEswAABLGAAASwBLMAAASxgAAEroSzAAAEsYAABK0EswAABLGAAASrhLMAAASxgAAEqgSzAAAEsYAABKuEqIAABLGAAASnBLMAAASxgAAEpYSzAAAEsYAABKQEswAABLGAAASihLMAAASxgAAFUgSzAAAEsYAABKEEswAABLGAAAS0hKiAAASxgAAEn4SzAAAEsYAABJ4EswAABLGAAASchLMAAASxgAAEmwSzAAAEsYAABLSEswAABLGAAASZhLMAAASxgAAEmASWgAAAAAAABJUEloAAAAAAAASThJaAAAAAAAAEkgSWgAAAAAAABJCEloAAAAAAAASPBJaAAAAAAAAEjYSMAAAAAAAABIqEiQAAAAAEh4SKhIkAAAAABIeEhgSJAAAAAASHgAAEhIAAAAAAAASDBIGAAASAAAAEfoSBgAAEgAAABH0EgYAABIAAAAR7hIGAAASAAAAEegSBgAAEgAAABHiEgYAABIAAAAR3BIGAAASAAAAAAAR1gAAAAAAABHQEgYAABIAAAARyhIGAAASAAAAEcQSBgAAEgAAABG+EgYAABIAAAARuBIGAAASAAAAEdwSBgAAEgAAABGyEgYAABIAAAARrBGmAAAAAAAAEaARpgAAAAAAABGaEZQAAAAAAAARmhGOAAAAAAAAEYgSEhGCAAARfBF2EhIRggAAEXwRiBISEYIAABF8EYgRcBGCAAARfBGIEhIRggAAEXwRiBISEYIAABF8EYgSEhGCAAARfBFqEWQRXhFY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DwEQD1kADw8QXxUBEA8QTxtAcwQBAwIDhQUBAgcChQAKBwgHCgiAABEIBggRBoAABgsIBgt+AAsMCAsMfg4BDA0IDA1+AA0SCA0SfgAUEw8TFA+AAAEPAA8BAIAAABAPABB+AAcJAQgRBwhpABIWARMUEhNpAA8BEA9ZAA8PEF8VARAPEE9ZWUEzBGIEYgAAAAAEfwR+BGIEdARiBHMEbARrA1UDUwAAA08AAANPAuoC6ALRAtACzwLOArgCtQKTApICQgJBAjECLgIkAiICFQIRATIBKgDgAN8A3QDbAM0AywDIAMcAHQAbABcABgAYKzc1NjU1IjU3NSY3NCMHIzUjNSYmJzUjNScnIycmNTUnNTY2NzQ3NzU0Jyc1JyY1NTM1IzUmIycjFQcjNTM3NTMUMzUXFTM0MzMWMzU3NTM1MjcmNTQ3JyMiNTUiNSM1MzU3NzU0IyI1IxUjNRcwMjc3NTM1MzUzNSM1JiYjNSM1IzQjIic1JyM1NDc2NTUiNScmNTU0NzY3NTc2NzU0PwI1MxQzMzcyNzc1Mzc2NTc3NjMXMzczNTQ3NjU3NzM1NDMVMjc2MzUzNzY2MzMXFhYXFjMyNjcyFzA3NzMyFxczFxUWHwIVFhYXFBYVFxQXFjEXFAcjFSIVFRcWFhUVIxUUIzIVIxUGFRQfAhUGFRUnFTMVMxUGFSMGFRUjFCMGFRUGNRUjBiMjBhUnIyczJyM3Iic1NDcnIjUwNzI1NTM0JzQzMzU0NDc2Mxc1NzUzNSY1NSM1IzQnJyM1IzUjNSM1IzUiNQYHIzQnJzI1BzUnIzUjIicnIxUiJyYjIgcGIyMVIxUUIyInJzUjNCcjNSM1IzUjNDcyNSY1Iyc1IgYVIxQjIjUVIyc1IwYVBgYHFSI1FSIHBgcVIxUjBgYVFDMyFQcGIwc1IjUjByI1BwciNTQjIgcHFDMVFzQzMxQXFhUUIyMVFhUVFxQzNjMyNTU3NyM0MxUzFRcVMxcXIhUXFRQHByIVIxUjFTcVMzY2MzMyFhcXMxYXFhcUBiMnBxQjIwciFQcVIzUjJzUjJyM0IzAnIyI1NSMnNSM1NCcnIwYjJyMGIwYVFBcWFTMWMzIVFCMiJgcjFQYVFBcWFRcXFBcXMxYzMzI3MxcWMzI2NzY2MzM3NTM2NzQ3NxQzNzMXFhUVFzM3FTMVMxUzFTMXMzI3NjMVMxUzFQcjFDMHIyIHIxUnIyIHBiMVFBcXMxQjIwYjIwcmIycjNSMVMAYjIhUVFxYVMxcVFCMjIiYjFQcjFQciFQc0IxUjFQYHFxUjFDMWFRQzMzYzFxUGFRUwFxYxMxcXMjc0MxcVMzAXMjc2MzMVMxcXMxcXMjUXFhUUFzM3NDMiNTQzFhUHIzIVIgYjMhUHIzAHIxUHFQcjFScjBwYVIxUGFQcGIxUzFSMGIwYHFDMHIxUjFSMVEzAnJiMjNSM0Myc2NTQnJzY1NSY1MjU1Mzc3NSczNzc2MzU3NTM1NxczFzMXFjMyNzYzFBcXMjcVMxUzMhUVBxQzFAcGFRQzMhUUJgcVBxUGFSMVJTUWNzIVBwcmJyY1NjUjNDcjNSM1MxUzFTMVFDMyFTMXFScUIxUHNCcmJyM1IzQnJjUzNTMXFhUzFTMUFxYVFTcmNSc1MhcXMxUjIgU0JiMHIiciNSMiFTUjNSM1IzUnNCMjJzMyNTQzMxczNjMzFjM1MzQ3NSM0Jyc1JzU3MhUWFxYzFTMwFhcUFxc3FxcVFxcWFhcWFhUzFxUUBwcGIyM0JyI1IzUjFCMjFQYHFxcjFScGFRUjIhUzByYjBwciJyI1NTMXMzUzFxQXFDMGBiMHJicmNSMHIyI1Iyc1IjU3NTczFDMzNzMyNSYzMxUzNzM3FxUzFTMVFhUHBxUjFCMnFCMVJyOtAQEBAgELCAYCAgYDAQUGEQUDDAIBAQQEAgUCAQICAQMCAgIDBQEDAgICAQECAgICBAEBAQIBAQECAgMBAgIMCgIBAQEFAwMDAQEFAwMBAQIFBQEBAQYDAwQBAgICAgMNBAEBAgMCBAQDAQgFAwEBAQEBAwICAgECAgQFBx8JAg8HBQQCBAEDAgMIAgMCBg8HBwQMFBICBAgLAQgDFwQDAgMCAQMHBQEFAQEEAgEBAQEBAQMEAwQCAQECAQEBAwMBAQEBAQMDAwIFAQECAgQCAgEBAQIBAgICAgICAQQBAgMCAgEDBwEEAQECCQMDAQMCAQIBAgMBAQMDBQcHAwgBAwEJAQMCAQUBAwgVAgICDQIGBQECAgQGAgIFBBUCBQEBAQIBBQEEAQMEBwICBAECAQYCAQIBAgEBBwgCAgIDAgEGAwEEAgIDAQEDAgQBAQwEEQgQCA0GBAQDBgcBBQIBAgsdBAEDDQQBBAEKAQICAQMBDgIDCQMBAQYDAgMCAQQEAwQDAwQCAgIBAQMCAwMGAQEDAgEDAwYEAgYEAwUBAggDAwIIAQECAgIBAw4JBgMEAwMUAwQBBgECAgEBAQcFAwQDBAMBAgUDBAICBQEBARgCAQIFDAEBAgICEQIECA0LDAICAgECBgQCAwQEAgIBCQEEAgECAgELAQEBAgIGBgMBAQMCBQICGgECAQEEAxACAQEDBQIBAgYFCAEBAwMDAQECAQICAQIBCAEHAQECAgQBAQMEBAJoBQUBEwEBAQMCBQEBAQIBAQEBBAQCBAEJBwQKBAUBAQIBBAIDAgIBAQQCAgIBAgMCARQFBQYE/vYCAQEDFAECAggFAgUBBgMBAgECAQIEAQMEAQEDAQECBAQBAwEDAggCAQMBAgUCAwEFBQECBAIBAwMJBQUJAQUBAwICAwEFAQQFAgMPBAQBAwIEAQQEAwECAgIBAgEDAQUBAgMCAgUXAQMBAgYIAQIDCQMBAgMCAgQBAgICAQEBBAJPAgEBAgIDFgEBAQQNBx4CBAUCAQECCQIDAQQFBAYBBQIBAgYKAgYCBAEBBgEGCwICBAICCgMBAgICAgYCAwMBAwwDBQIDAgQGBgMFBAkaAwYCBQUIGgQGEwsFAQQOBgcBAgIBCgQEAgEBBAICBgICBAQBBAUBAQEBAgkDDAIBAwMDBgEBAgECBBECAgIECAMCAgITAwEBAgICFQwLEAUEBgMVAwIBAgECBQwCAQECBQIDAQIGBQMBAQMBAwQEAgEDAwEGBgQFAgkCAQIBAwECAgIEBAoJAgECAwMJAwMBBw4NCAICBAcFAgwDDgkFDwgaCgEBBAICBQEGAQMBAQEBLQYCBAQFBQoDAQUCBgMFAQQBAgIBBAIBAQIBAQIEAgMCAVQCBgIEAQQCBBYCAggCAQIBAQMECAQFAwEEAQMBAQEDAgQGAQIBAQECAQMBAQICAQMCBAICAgUEAQMDBQQBAwIEAgIBAgEDAQIDAwIBAgIHAwEBAQIFAwEEBAICAgIEAwEBBgEBAgECAQ8BAgQEAgMDBAMFAQQLAQcHCAMCAgIBAgICFQEDAgIBAQIGBgkEAQcBBwsDAQIBAQIDAQECAQMCBAIBAgYDAQcBBwICAQIEBAYBAgECAgQCAgQKBgEDBAEBAgMBAQEBAQICBAMDAQEBBAEEDgICAgEEAQMCAQMB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 diff --git a/d2renderers/d2fonts/ttf/ArchitectsDaughter-Regular.ttf b/d2renderers/d2fonts/ttf/ArchitectsDaughter-Regular.ttf new file mode 100644 index 000000000..e87c52642 Binary files /dev/null and b/d2renderers/d2fonts/ttf/ArchitectsDaughter-Regular.ttf differ diff --git a/d2renderers/d2fonts/ttf/FuzzyBubbles-Bold.ttf b/d2renderers/d2fonts/ttf/FuzzyBubbles-Bold.ttf new file mode 100644 index 000000000..ff162c988 Binary files /dev/null and b/d2renderers/d2fonts/ttf/FuzzyBubbles-Bold.ttf differ diff --git a/d2renderers/d2latex/latex.go b/d2renderers/d2latex/latex.go index 74fdb0759..a822b2c7b 100644 --- a/d2renderers/d2latex/latex.go +++ b/d2renderers/d2latex/latex.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/dop251/goja" + "oss.terrastruct.com/util-go/xdefer" ) diff --git a/d2renderers/d2sketch/fillpattern.svg b/d2renderers/d2sketch/fillpattern.svg new file mode 100644 index 000000000..0bcc3f228 --- /dev/null +++ b/d2renderers/d2sketch/fillpattern.svg @@ -0,0 +1 @@ + diff --git a/d2renderers/d2sketch/rough.js b/d2renderers/d2sketch/rough.js new file mode 100644 index 000000000..04eabe71a --- /dev/null +++ b/d2renderers/d2sketch/rough.js @@ -0,0 +1,1673 @@ +/*eslint-disable */ +// This is a slightly modified version of rough.js for D2 +// +// rough.js is from https://github.com/rough-stuff/rough. +// Attribution for this file is as follows: +// +// MIT License +// +// Copyright (c) 2019 Preet Shihn +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +const t = "http://www.w3.org/2000/svg"; +function e(t, e, s) { + if (t && t.length) { + const [n, i] = e, + a = (Math.PI / 180) * s, + o = Math.cos(a), + h = Math.sin(a); + t.forEach((t) => { + const [e, s] = t; + (t[0] = (e - n) * o - (s - i) * h + n), (t[1] = (e - n) * h + (s - i) * o + i); + }); + } +} +function s(t) { + const e = t[0], + s = t[1]; + return Math.sqrt(Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); +} +function n(t, e) { + return t.type === e; +} +const i = { + A: 7, + a: 7, + C: 6, + c: 6, + H: 1, + h: 1, + L: 2, + l: 2, + M: 2, + m: 2, + Q: 4, + q: 4, + S: 4, + s: 4, + T: 4, + t: 2, + V: 1, + v: 1, + Z: 0, + z: 0, +}; +class a { + constructor(t) { + (this.COMMAND = 0), + (this.NUMBER = 1), + (this.EOD = 2), + (this.segments = []), + this.parseData(t), + this.processPoints(); + } + tokenize(t) { + const e = new Array(); + + for (; "" !== t; ) { + const a = t.match(/^([ \t\r\n,]+)/); + const b = t.match(/^([aAcChHlLmMqQsStTvVzZ])/); + const c = t.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/); + if (a) t = t.substr(a[0].length); + else if (b) + (e[e.length] = { type: this.COMMAND, text: b[0] }), (t = t.substr(b[0].length)); + else { + if (!c) return []; + (e[e.length] = { type: this.NUMBER, text: `${parseFloat(c[0])}` }), + (t = t.substr(c[0].length)); + } + } + return (e[e.length] = { type: this.EOD, text: "" }), e; + } + parseData(t) { + const e = this.tokenize(t); + let s = 0, + a = e[s], + o = "BOD"; + for (this.segments = new Array(); !n(a, this.EOD); ) { + let h; + const r = new Array(); + if ("BOD" === o) { + if ("M" !== a.text && "m" !== a.text) return void this.parseData("M0,0" + t); + s++, (h = i[a.text]), (o = a.text); + } else n(a, this.NUMBER) ? (h = i[o]) : (s++, (h = i[a.text]), (o = a.text)); + if (s + h < e.length) { + for (let t = s; t < s + h; t++) { + const s = e[t]; + if (!n(s, this.NUMBER)) + return void console.error("Param not a number: " + o + "," + s.text); + r[r.length] = +s.text; + } + if ("number" != typeof i[o]) return void console.error("Bad segment: " + o); + { + const t = { key: o, data: r }; + this.segments.push(t), + (s += h), + (a = e[s]), + "M" === o && (o = "L"), + "m" === o && (o = "l"); + } + } else console.error("Path data ended short"); + } + } + get closed() { + if (void 0 === this._closed) { + this._closed = !1; + for (const t of this.segments) "z" === t.key.toLowerCase() && (this._closed = !0); + } + return this._closed; + } + processPoints() { + let t = null, + e = [0, 0]; + for (let s = 0; s < this.segments.length; s++) { + const n = this.segments[s]; + switch (n.key) { + case "M": + case "L": + case "T": + n.point = [n.data[0], n.data[1]]; + break; + case "m": + case "l": + case "t": + n.point = [n.data[0] + e[0], n.data[1] + e[1]]; + break; + case "H": + n.point = [n.data[0], e[1]]; + break; + case "h": + n.point = [n.data[0] + e[0], e[1]]; + break; + case "V": + n.point = [e[0], n.data[0]]; + break; + case "v": + n.point = [e[0], n.data[0] + e[1]]; + break; + case "z": + case "Z": + t && (n.point = [t[0], t[1]]); + break; + case "C": + n.point = [n.data[4], n.data[5]]; + break; + case "c": + n.point = [n.data[4] + e[0], n.data[5] + e[1]]; + break; + case "S": + n.point = [n.data[2], n.data[3]]; + break; + case "s": + n.point = [n.data[2] + e[0], n.data[3] + e[1]]; + break; + case "Q": + n.point = [n.data[2], n.data[3]]; + break; + case "q": + n.point = [n.data[2] + e[0], n.data[3] + e[1]]; + break; + case "A": + n.point = [n.data[5], n.data[6]]; + break; + case "a": + n.point = [n.data[5] + e[0], n.data[6] + e[1]]; + } + ("m" !== n.key && "M" !== n.key) || (t = null), + n.point && ((e = n.point), t || (t = n.point)), + ("z" !== n.key && "Z" !== n.key) || (t = null); + } + } +} +class o { + constructor(t) { + (this._position = [0, 0]), + (this._first = null), + (this.bezierReflectionPoint = null), + (this.quadReflectionPoint = null), + (this.parsed = new a(t)); + } + get segments() { + return this.parsed.segments; + } + get closed() { + return this.parsed.closed; + } + get linearPoints() { + if (!this._linearPoints) { + const t = []; + let e = []; + for (const s of this.parsed.segments) { + const n = s.key.toLowerCase(); + (("m" !== n && "z" !== n) || (e.length && (t.push(e), (e = [])), "z" !== n)) && + s.point && + e.push(s.point); + } + e.length && (t.push(e), (e = [])), (this._linearPoints = t); + } + return this._linearPoints; + } + get first() { + return this._first; + } + set first(t) { + this._first = t; + } + setPosition(t, e) { + (this._position = [t, e]), this._first || (this._first = [t, e]); + } + get position() { + return this._position; + } + get x() { + return this._position[0]; + } + get y() { + return this._position[1]; + } +} +class h { + constructor(t, e, s, n, i, a) { + if ( + ((this._segIndex = 0), + (this._numSegs = 0), + (this._rx = 0), + (this._ry = 0), + (this._sinPhi = 0), + (this._cosPhi = 0), + (this._C = [0, 0]), + (this._theta = 0), + (this._delta = 0), + (this._T = 0), + (this._from = t), + t[0] === e[0] && t[1] === e[1]) + ) + return; + const o = Math.PI / 180; + (this._rx = Math.abs(s[0])), + (this._ry = Math.abs(s[1])), + (this._sinPhi = Math.sin(n * o)), + (this._cosPhi = Math.cos(n * o)); + const h = (this._cosPhi * (t[0] - e[0])) / 2 + (this._sinPhi * (t[1] - e[1])) / 2, + r = (-this._sinPhi * (t[0] - e[0])) / 2 + (this._cosPhi * (t[1] - e[1])) / 2; + let c = 0; + const l = + this._rx * this._rx * this._ry * this._ry - + this._rx * this._rx * r * r - + this._ry * this._ry * h * h; + if (l < 0) { + const t = Math.sqrt(1 - l / (this._rx * this._rx * this._ry * this._ry)); + (this._rx = this._rx * t), (this._ry = this._ry * t), (c = 0); + } else + c = + (i === a ? -1 : 1) * + Math.sqrt(l / (this._rx * this._rx * r * r + this._ry * this._ry * h * h)); + const u = (c * this._rx * r) / this._ry, + p = (-c * this._ry * h) / this._rx; + (this._C = [0, 0]), + (this._C[0] = this._cosPhi * u - this._sinPhi * p + (t[0] + e[0]) / 2), + (this._C[1] = this._sinPhi * u + this._cosPhi * p + (t[1] + e[1]) / 2), + (this._theta = this.calculateVectorAngle( + 1, + 0, + (h - u) / this._rx, + (r - p) / this._ry + )); + let d = this.calculateVectorAngle( + (h - u) / this._rx, + (r - p) / this._ry, + (-h - u) / this._rx, + (-r - p) / this._ry + ); + !a && d > 0 ? (d -= 2 * Math.PI) : a && d < 0 && (d += 2 * Math.PI), + (this._numSegs = Math.ceil(Math.abs(d / (Math.PI / 2)))), + (this._delta = d / this._numSegs), + (this._T = + ((8 / 3) * Math.sin(this._delta / 4) * Math.sin(this._delta / 4)) / + Math.sin(this._delta / 2)); + } + getNextSegment() { + if (this._segIndex === this._numSegs) return null; + const t = Math.cos(this._theta), + e = Math.sin(this._theta), + s = this._theta + this._delta, + n = Math.cos(s), + i = Math.sin(s), + a = [ + this._cosPhi * this._rx * n - this._sinPhi * this._ry * i + this._C[0], + this._sinPhi * this._rx * n + this._cosPhi * this._ry * i + this._C[1], + ], + o = [ + this._from[0] + + this._T * (-this._cosPhi * this._rx * e - this._sinPhi * this._ry * t), + this._from[1] + + this._T * (-this._sinPhi * this._rx * e + this._cosPhi * this._ry * t), + ], + h = [ + a[0] + this._T * (this._cosPhi * this._rx * i + this._sinPhi * this._ry * n), + a[1] + this._T * (this._sinPhi * this._rx * i - this._cosPhi * this._ry * n), + ]; + return ( + (this._theta = s), + (this._from = [a[0], a[1]]), + this._segIndex++, + { cp1: o, cp2: h, to: a } + ); + } + calculateVectorAngle(t, e, s, n) { + const i = Math.atan2(e, t), + a = Math.atan2(n, s); + return a >= i ? a - i : 2 * Math.PI - (i - a); + } +} +class r { + constructor(t, e) { + (this.sets = t), (this.closed = e); + } + fit(t) { + const e = []; + for (const s of this.sets) { + const n = s.length; + let i = Math.floor(t * n); + if (i < 5) { + if (n <= 5) continue; + i = 5; + } + e.push(this.reduce(s, i)); + } + let s = ""; + for (const t of e) { + for (let e = 0; e < t.length; e++) { + const n = t[e]; + s += 0 === e ? "M" + n[0] + "," + n[1] : "L" + n[0] + "," + n[1]; + } + this.closed && (s += "z "); + } + return s; + } + reduce(t, e) { + if (t.length <= e) return t; + const n = t.slice(0); + for (; n.length > e; ) { + let t = -1, + e = -1; + for (let i = 1; i < n.length - 1; i++) { + const a = s([n[i - 1], n[i]]), + o = s([n[i], n[i + 1]]), + h = s([n[i - 1], n[i + 1]]), + r = (a + o + h) / 2, + c = Math.sqrt(r * (r - a) * (r - o) * (r - h)); + (t < 0 || c < t) && ((t = c), (e = i)); + } + if (!(e > 0)) break; + n.splice(e, 1); + } + return n; + } +} +function c(t, s) { + const n = [0, 0], + i = Math.round(s.hachureAngle + 90); + i && e(t, n, i); + const a = (function (t, e) { + const s = [...t]; + s[0].join(",") !== s[s.length - 1].join(",") && s.push([s[0][0], s[0][1]]); + const n = []; + if (s && s.length > 2) { + let t = e.hachureGap; + t < 0 && (t = 4 * e.strokeWidth), (t = Math.max(t, 0.1)); + const i = []; + for (let t = 0; t < s.length - 1; t++) { + const e = s[t], + n = s[t + 1]; + if (e[1] !== n[1]) { + const t = Math.min(e[1], n[1]); + i.push({ + ymin: t, + ymax: Math.max(e[1], n[1]), + x: t === e[1] ? e[0] : n[0], + islope: (n[0] - e[0]) / (n[1] - e[1]), + }); + } + } + if ( + (i.sort((t, e) => + t.ymin < e.ymin + ? -1 + : t.ymin > e.ymin + ? 1 + : t.x < e.x + ? -1 + : t.x > e.x + ? 1 + : t.ymax === e.ymax + ? 0 + : (t.ymax - e.ymax) / Math.abs(t.ymax - e.ymax) + ), + !i.length) + ) + return n; + let a = [], + o = i[0].ymin; + for (; a.length || i.length; ) { + if (i.length) { + let t = -1; + for (let e = 0; e < i.length && !(i[e].ymin > o); e++) t = e; + i.splice(0, t + 1).forEach((t) => { + a.push({ s: o, edge: t }); + }); + } + if ( + ((a = a.filter((t) => !(t.edge.ymax <= o))), + a.sort((t, e) => + t.edge.x === e.edge.x + ? 0 + : (t.edge.x - e.edge.x) / Math.abs(t.edge.x - e.edge.x) + ), + a.length > 1) + ) + for (let t = 0; t < a.length; t += 2) { + const e = t + 1; + if (e >= a.length) break; + const s = a[t].edge, + i = a[e].edge; + n.push([ + [Math.round(s.x), o], + [Math.round(i.x), o], + ]); + } + (o += t), + a.forEach((e) => { + e.edge.x = e.edge.x + t * e.edge.islope; + }); + } + } + return n; + })(t, s); + return ( + i && + (e(t, n, -i), + (function (t, s, n) { + const i = []; + t.forEach((t) => i.push(...t)), e(i, s, n); + })(a, n, -i)), + a + ); +} +class l { + constructor(t) { + this.helper = t; + } + fillPolygon(t, e) { + return this._fillPolygon(t, e); + } + _fillPolygon(t, e, s = !1) { + const n = c(t, e); + return { type: "fillSketch", ops: this.renderLines(n, e, s) }; + } + renderLines(t, e, s) { + let n = [], + i = null; + for (const a of t) + (n = n.concat(this.helper.doubleLineOps(a[0][0], a[0][1], a[1][0], a[1][1], e))), + s && + i && + (n = n.concat(this.helper.doubleLineOps(i[0], i[1], a[0][0], a[0][1], e))), + (i = a[1]); + return n; + } +} +class u extends l { + fillPolygon(t, e) { + return this._fillPolygon(t, e, !0); + } +} +class p extends l { + fillPolygon(t, e) { + const s = this._fillPolygon(t, e), + n = Object.assign({}, e, { hachureAngle: e.hachureAngle + 90 }), + i = this._fillPolygon(t, n); + return (s.ops = s.ops.concat(i.ops)), s; + } +} +class d { + constructor(t) { + this.helper = t; + } + fillPolygon(t, e) { + const s = c( + t, + (e = Object.assign({}, e, { + curveStepCount: 4, + hachureAngle: 0, + roughness: 1, + })) + ); + return this.dotsOnLines(s, e); + } + dotsOnLines(t, e) { + let n = [], + i = e.hachureGap; + i < 0 && (i = 4 * e.strokeWidth), (i = Math.max(i, 0.1)); + let a = e.fillWeight; + a < 0 && (a = e.strokeWidth / 2); + for (const o of t) { + const t = s(o) / i, + h = Math.ceil(t) - 1, + r = Math.atan((o[1][1] - o[0][1]) / (o[1][0] - o[0][0])); + for (let t = 0; t < h; t++) { + const s = i * (t + 1), + h = s * Math.sin(r), + c = s * Math.cos(r), + l = [o[0][0] - c, o[0][1] + h], + u = this.helper.randOffsetWithRange(l[0] - i / 4, l[0] + i / 4, e), + p = this.helper.randOffsetWithRange(l[1] - i / 4, l[1] + i / 4, e), + d = this.helper.ellipse(u, p, a, a, e); + n = n.concat(d.ops); + } + } + return { type: "fillSketch", ops: n }; + } +} +class f { + constructor(t) { + this.helper = t; + } + fillPolygon(t, e) { + const s = c(t, e); + return { type: "fillSketch", ops: this.dashedLine(s, e) }; + } + dashedLine(t, e) { + const n = + e.dashOffset < 0 + ? e.hachureGap < 0 + ? 4 * e.strokeWidth + : e.hachureGap + : e.dashOffset, + i = + e.dashGap < 0 ? (e.hachureGap < 0 ? 4 * e.strokeWidth : e.hachureGap) : e.dashGap; + let a = []; + return ( + t.forEach((t) => { + const o = s(t), + h = Math.floor(o / (n + i)), + r = (o + i - h * (n + i)) / 2; + let c = t[0], + l = t[1]; + c[0] > l[0] && ((c = t[1]), (l = t[0])); + const u = Math.atan((l[1] - c[1]) / (l[0] - c[0])); + for (let t = 0; t < h; t++) { + const s = t * (n + i), + o = s + n, + h = [ + c[0] + s * Math.cos(u) + r * Math.cos(u), + c[1] + s * Math.sin(u) + r * Math.sin(u), + ], + l = [ + c[0] + o * Math.cos(u) + r * Math.cos(u), + c[1] + o * Math.sin(u) + r * Math.sin(u), + ]; + a = a.concat(this.helper.doubleLineOps(h[0], h[1], l[0], l[1], e)); + } + }), + a + ); + } +} +class g { + constructor(t) { + this.helper = t; + } + fillPolygon(t, e) { + const s = e.hachureGap < 0 ? 4 * e.strokeWidth : e.hachureGap, + n = e.zigzagOffset < 0 ? s : e.zigzagOffset, + i = c(t, (e = Object.assign({}, e, { hachureGap: s + n }))); + return { type: "fillSketch", ops: this.zigzagLines(i, n, e) }; + } + zigzagLines(t, e, n) { + let i = []; + return ( + t.forEach((t) => { + const a = s(t), + o = Math.round(a / (2 * e)); + let h = t[0], + r = t[1]; + h[0] > r[0] && ((h = t[1]), (r = t[0])); + const c = Math.atan((r[1] - h[1]) / (r[0] - h[0])); + for (let t = 0; t < o; t++) { + const s = 2 * t * e, + a = 2 * (t + 1) * e, + o = Math.sqrt(2 * Math.pow(e, 2)), + r = [h[0] + s * Math.cos(c), h[1] + s * Math.sin(c)], + l = [h[0] + a * Math.cos(c), h[1] + a * Math.sin(c)], + u = [ + r[0] + o * Math.cos(c + Math.PI / 4), + r[1] + o * Math.sin(c + Math.PI / 4), + ]; + (i = i.concat(this.helper.doubleLineOps(r[0], r[1], u[0], u[1], n))), + (i = i.concat(this.helper.doubleLineOps(u[0], u[1], l[0], l[1], n))); + } + }), + i + ); + } +} +const y = {}; +class _ { + constructor(t) { + this.seed = t; + } + next() { + return this.seed + ? ((Math.pow(2, 31) - 1) & (this.seed = Math.imul(48271, this.seed))) / + Math.pow(2, 31) + : Math.random(); + } +} +const M = { + randOffset: function (t, e) { + return z(t, e); + }, + randOffsetWithRange: function (t, e, s) { + return C(t, e, s); + }, + ellipse: function (t, e, s, n, i) { + const a = P(s, n, i); + return w(t, e, i, a).opset; + }, + doubleLineOps: function (t, e, s, n, i) { + return A(t, e, s, n, i); + }, +}; +function x(t, e, s, n, i) { + return { type: "path", ops: A(t, e, s, n, i) }; +} +function m(t, e, s) { + const n = (t || []).length; + if (n > 2) { + let i = []; + for (let e = 0; e < n - 1; e++) + i = i.concat(A(t[e][0], t[e][1], t[e + 1][0], t[e + 1][1], s)); + return ( + e && (i = i.concat(A(t[n - 1][0], t[n - 1][1], t[0][0], t[0][1], s))), + { type: "path", ops: i } + ); + } + return 2 === n ? x(t[0][0], t[0][1], t[1][0], t[1][1], s) : { type: "path", ops: [] }; +} +function k(t, e, s, n, i) { + return (function (t, e) { + return m(t, !0, e); + })( + [ + [t, e], + [t + s, e], + [t + s, e + n], + [t, e + n], + ], + i + ); +} +function b(t, e) { + const s = W(t, 1 * (1 + 0.2 * e.roughness), e), + n = W(t, 1.5 * (1 + 0.22 * e.roughness), e); + return { type: "path", ops: s.concat(n) }; +} +function P(t, e, s) { + const n = Math.sqrt( + 2 * Math.PI * Math.sqrt((Math.pow(t / 2, 2) + Math.pow(e / 2, 2)) / 2) + ), + i = Math.max(s.curveStepCount, (s.curveStepCount / Math.sqrt(200)) * n), + a = (2 * Math.PI) / i; + let o = Math.abs(t / 2), + h = Math.abs(e / 2); + const r = 1 - s.curveFitting; + return (o += z(o * r, s)), (h += z(h * r, s)), { increment: a, rx: o, ry: h }; +} +function w(t, e, s, n) { + const [i, a] = D( + n.increment, + t, + e, + n.rx, + n.ry, + 1, + n.increment * C(0.1, C(0.4, 1, s), s), + s + ), + [o] = D(n.increment, t, e, n.rx, n.ry, 1.5, 0, s), + h = R(i, null, s), + r = R(o, null, s); + return { estimatedPoints: a, opset: { type: "path", ops: h.concat(r) } }; +} +function v(t, e, s, n, i, a, o, h, r) { + const c = t, + l = e; + let u = Math.abs(s / 2), + p = Math.abs(n / 2); + (u += z(0.01 * u, r)), (p += z(0.01 * p, r)); + let d = i, + f = a; + for (; d < 0; ) (d += 2 * Math.PI), (f += 2 * Math.PI); + f - d > 2 * Math.PI && ((d = 0), (f = 2 * Math.PI)); + const g = (2 * Math.PI) / r.curveStepCount, + y = Math.min(g / 2, (f - d) / 2), + _ = I(y, c, l, u, p, d, f, 1, r), + M = I(y, c, l, u, p, d, f, 1.5, r); + let x = _.concat(M); + return ( + o && + (h + ? ((x = x.concat(A(c, l, c + u * Math.cos(d), l + p * Math.sin(d), r))), + (x = x.concat(A(c, l, c + u * Math.cos(f), l + p * Math.sin(f), r)))) + : (x.push({ op: "lineTo", data: [c, l] }), + x.push({ + op: "lineTo", + data: [c + u * Math.cos(d), l + p * Math.sin(d)], + }))), + { type: "path", ops: x } + ); +} +function S(t, e) { + const s = []; + if (t.length) { + const n = e.maxRandomnessOffset || 0, + i = t.length; + if (i > 2) { + s.push({ op: "move", data: [t[0][0] + z(n, e), t[0][1] + z(n, e)] }); + for (let a = 1; a < i; a++) + s.push({ op: "lineTo", data: [t[a][0] + z(n, e), t[a][1] + z(n, e)] }); + } + } + return { type: "fillPath", ops: s }; +} +function O(t, e) { + return (function (t, e) { + let s = t.fillStyle || "hachure"; + if (!y[s]) + switch (s) { + case "zigzag": + y[s] || (y[s] = new u(e)); + break; + case "cross-hatch": + y[s] || (y[s] = new p(e)); + break; + case "dots": + y[s] || (y[s] = new d(e)); + break; + case "dashed": + y[s] || (y[s] = new f(e)); + break; + case "zigzag-line": + y[s] || (y[s] = new g(e)); + break; + case "hachure": + default: + (s = "hachure"), y[s] || (y[s] = new l(e)); + } + return y[s]; + })(e, M).fillPolygon(t, e); +} +function T(t) { + return t.randomizer || (t.randomizer = new _(t.seed || 0)), t.randomizer.next(); +} +function C(t, e, s) { + return s.roughness * s.roughnessGain * (T(s) * (e - t) + t); +} +function z(t, e) { + return C(-t, t, e); +} +function A(t, e, s, n, i) { + const a = E(t, e, s, n, i, !0, !1), + o = E(t, e, s, n, i, !0, !0); + return a.concat(o); +} +function E(t, e, s, n, i, a, o) { + const h = Math.pow(t - s, 2) + Math.pow(e - n, 2), + r = Math.sqrt(h); + i.roughnessGain = r < 200 ? 1 : r > 500 ? 0.4 : -0.0016668 * r + 1.233334; + let c = i.maxRandomnessOffset || 0; + c * c * 100 > h && (c = r / 10); + const l = c / 2, + u = 0.2 + 0.2 * T(i); + let p = (i.bowing * i.maxRandomnessOffset * (n - e)) / 200, + d = (i.bowing * i.maxRandomnessOffset * (t - s)) / 200; + (p = z(p, i)), (d = z(d, i)); + const f = [], + g = () => z(l, i), + y = () => z(c, i); + return ( + a && + (o + ? f.push({ op: "move", data: [t + g(), e + g()] }) + : f.push({ op: "move", data: [t + z(c, i), e + z(c, i)] })), + o + ? f.push({ + op: "bcurveTo", + data: [ + p + t + (s - t) * u + g(), + d + e + (n - e) * u + g(), + p + t + 2 * (s - t) * u + g(), + d + e + 2 * (n - e) * u + g(), + s + g(), + n + g(), + ], + }) + : f.push({ + op: "bcurveTo", + data: [ + p + t + (s - t) * u + y(), + d + e + (n - e) * u + y(), + p + t + 2 * (s - t) * u + y(), + d + e + 2 * (n - e) * u + y(), + s + y(), + n + y(), + ], + }), + f + ); +} +function W(t, e, s) { + const n = []; + n.push([t[0][0] + z(e, s), t[0][1] + z(e, s)]), + n.push([t[0][0] + z(e, s), t[0][1] + z(e, s)]); + for (let i = 1; i < t.length; i++) + n.push([t[i][0] + z(e, s), t[i][1] + z(e, s)]), + i === t.length - 1 && n.push([t[i][0] + z(e, s), t[i][1] + z(e, s)]); + return R(n, null, s); +} +function R(t, e, s) { + const n = t.length; + let i = []; + if (n > 3) { + const a = [], + o = 1 - s.curveTightness; + i.push({ op: "move", data: [t[1][0], t[1][1]] }); + for (let e = 1; e + 2 < n; e++) { + const s = t[e]; + (a[0] = [s[0], s[1]]), + (a[1] = [ + s[0] + (o * t[e + 1][0] - o * t[e - 1][0]) / 6, + s[1] + (o * t[e + 1][1] - o * t[e - 1][1]) / 6, + ]), + (a[2] = [ + t[e + 1][0] + (o * t[e][0] - o * t[e + 2][0]) / 6, + t[e + 1][1] + (o * t[e][1] - o * t[e + 2][1]) / 6, + ]), + (a[3] = [t[e + 1][0], t[e + 1][1]]), + i.push({ + op: "bcurveTo", + data: [a[1][0], a[1][1], a[2][0], a[2][1], a[3][0], a[3][1]], + }); + } + if (e && 2 === e.length) { + const t = s.maxRandomnessOffset; + i.push({ op: "lineTo", data: [e[0] + z(t, s), e[1] + z(t, s)] }); + } + } else + 3 === n + ? (i.push({ op: "move", data: [t[1][0], t[1][1]] }), + i.push({ + op: "bcurveTo", + data: [t[1][0], t[1][1], t[2][0], t[2][1], t[2][0], t[2][1]], + })) + : 2 === n && (i = i.concat(A(t[0][0], t[0][1], t[1][0], t[1][1], s))); + return i; +} +function D(t, e, s, n, i, a, o, h) { + const r = [], + c = [], + l = z(0.5, h) - Math.PI / 2; + c.push([ + z(a, h) + e + 0.9 * n * Math.cos(l - t), + z(a, h) + s + 0.9 * i * Math.sin(l - t), + ]); + for (let o = l; o < 2 * Math.PI + l - 0.01; o += t) { + const t = [z(a, h) + e + n * Math.cos(o), z(a, h) + s + i * Math.sin(o)]; + r.push(t), c.push(t); + } + return ( + c.push([ + z(a, h) + e + n * Math.cos(l + 2 * Math.PI + 0.5 * o), + z(a, h) + s + i * Math.sin(l + 2 * Math.PI + 0.5 * o), + ]), + c.push([ + z(a, h) + e + 0.98 * n * Math.cos(l + o), + z(a, h) + s + 0.98 * i * Math.sin(l + o), + ]), + c.push([ + z(a, h) + e + 0.9 * n * Math.cos(l + 0.5 * o), + z(a, h) + s + 0.9 * i * Math.sin(l + 0.5 * o), + ]), + [c, r] + ); +} +function I(t, e, s, n, i, a, o, h, r) { + const c = a + z(0.1, r), + l = []; + l.push([ + z(h, r) + e + 0.9 * n * Math.cos(c - t), + z(h, r) + s + 0.9 * i * Math.sin(c - t), + ]); + for (let a = c; a <= o; a += t) + l.push([z(h, r) + e + n * Math.cos(a), z(h, r) + s + i * Math.sin(a)]); + return ( + l.push([e + n * Math.cos(o), s + i * Math.sin(o)]), + l.push([e + n * Math.cos(o), s + i * Math.sin(o)]), + R(l, null, r) + ); +} +function q(t, e, s, n, i, a, o, h) { + const r = [], + c = [h.maxRandomnessOffset || 1, (h.maxRandomnessOffset || 1) + 0.5]; + let l = [0, 0]; + for (let u = 0; u < 2; u++) + 0 === u + ? r.push({ op: "move", data: [o.x, o.y] }) + : r.push({ op: "move", data: [o.x + z(c[0], h), o.y + z(c[0], h)] }), + (l = [i + z(c[u], h), a + z(c[u], h)]), + r.push({ + op: "bcurveTo", + data: [ + t + z(c[u], h), + e + z(c[u], h), + s + z(c[u], h), + n + z(c[u], h), + l[0], + l[1], + ], + }); + return o.setPosition(l[0], l[1]), r; +} +function $(t, e, s, n) { + let i = []; + switch (e.key) { + case "M": + case "m": { + const s = "m" === e.key; + if (e.data.length >= 2) { + let a = +e.data[0], + o = +e.data[1]; + s && ((a += t.x), (o += t.y)); + const h = 1 * (n.maxRandomnessOffset || 0); + (a += z(h, n)), + (o += z(h, n)), + t.setPosition(a, o), + i.push({ op: "move", data: [a, o] }); + } + break; + } + case "L": + case "l": { + const s = "l" === e.key; + if (e.data.length >= 2) { + let a = +e.data[0], + o = +e.data[1]; + s && ((a += t.x), (o += t.y)), + (i = i.concat(A(t.x, t.y, a, o, n))), + t.setPosition(a, o); + } + break; + } + case "H": + case "h": { + const s = "h" === e.key; + if (e.data.length) { + let a = +e.data[0]; + s && (a += t.x), (i = i.concat(A(t.x, t.y, a, t.y, n))), t.setPosition(a, t.y); + } + break; + } + case "V": + case "v": { + const s = "v" === e.key; + if (e.data.length) { + let a = +e.data[0]; + s && (a += t.y), (i = i.concat(A(t.x, t.y, t.x, a, n))), t.setPosition(t.x, a); + } + break; + } + case "Z": + case "z": + t.first && + ((i = i.concat(A(t.x, t.y, t.first[0], t.first[1], n))), + t.setPosition(t.first[0], t.first[1]), + (t.first = null)); + break; + case "C": + case "c": { + const s = "c" === e.key; + if (e.data.length >= 6) { + let a = +e.data[0], + o = +e.data[1], + h = +e.data[2], + r = +e.data[3], + c = +e.data[4], + l = +e.data[5]; + s && ((a += t.x), (h += t.x), (c += t.x), (o += t.y), (r += t.y), (l += t.y)); + const u = q(a, o, h, r, c, l, t, n); + (i = i.concat(u)), (t.bezierReflectionPoint = [c + (c - h), l + (l - r)]); + } + break; + } + case "S": + case "s": { + const a = "s" === e.key; + if (e.data.length >= 4) { + let o = +e.data[0], + h = +e.data[1], + r = +e.data[2], + c = +e.data[3]; + a && ((o += t.x), (r += t.x), (h += t.y), (c += t.y)); + let l = o, + u = h; + const p = s ? s.key : ""; + let d = null; + ("c" !== p && "C" !== p && "s" !== p && "S" !== p) || + (d = t.bezierReflectionPoint), + d && ((l = d[0]), (u = d[1])); + const f = q(l, u, o, h, r, c, t, n); + (i = i.concat(f)), (t.bezierReflectionPoint = [r + (r - o), c + (c - h)]); + } + break; + } + case "Q": + case "q": { + const s = "q" === e.key; + if (e.data.length >= 4) { + let a = +e.data[0], + o = +e.data[1], + h = +e.data[2], + r = +e.data[3]; + s && ((a += t.x), (h += t.x), (o += t.y), (r += t.y)); + const c = 1 * (1 + 0.2 * n.roughness), + l = 1.5 * (1 + 0.22 * n.roughness); + i.push({ op: "move", data: [t.x + z(c, n), t.y + z(c, n)] }); + let u = [h + z(c, n), r + z(c, n)]; + i.push({ + op: "qcurveTo", + data: [a + z(c, n), o + z(c, n), u[0], u[1]], + }), + i.push({ op: "move", data: [t.x + z(l, n), t.y + z(l, n)] }), + (u = [h + z(l, n), r + z(l, n)]), + i.push({ + op: "qcurveTo", + data: [a + z(l, n), o + z(l, n), u[0], u[1]], + }), + t.setPosition(u[0], u[1]), + (t.quadReflectionPoint = [h + (h - a), r + (r - o)]); + } + break; + } + case "T": + case "t": { + const a = "t" === e.key; + if (e.data.length >= 2) { + let o = +e.data[0], + h = +e.data[1]; + a && ((o += t.x), (h += t.y)); + let r = o, + c = h; + const l = s ? s.key : ""; + let u = null; + ("q" !== l && "Q" !== l && "t" !== l && "T" !== l) || (u = t.quadReflectionPoint), + u && ((r = u[0]), (c = u[1])); + const p = 1 * (1 + 0.2 * n.roughness), + d = 1.5 * (1 + 0.22 * n.roughness); + i.push({ op: "move", data: [t.x + z(p, n), t.y + z(p, n)] }); + let f = [o + z(p, n), h + z(p, n)]; + i.push({ + op: "qcurveTo", + data: [r + z(p, n), c + z(p, n), f[0], f[1]], + }), + i.push({ op: "move", data: [t.x + z(d, n), t.y + z(d, n)] }), + (f = [o + z(d, n), h + z(d, n)]), + i.push({ + op: "qcurveTo", + data: [r + z(d, n), c + z(d, n), f[0], f[1]], + }), + t.setPosition(f[0], f[1]), + (t.quadReflectionPoint = [o + (o - r), h + (h - c)]); + } + break; + } + case "A": + case "a": { + const s = "a" === e.key; + if (e.data.length >= 7) { + const a = +e.data[0], + o = +e.data[1], + r = +e.data[2], + c = +e.data[3], + l = +e.data[4]; + let u = +e.data[5], + p = +e.data[6]; + if ((s && ((u += t.x), (p += t.y)), u === t.x && p === t.y)) break; + if (0 === a || 0 === o) (i = i.concat(A(t.x, t.y, u, p, n))), t.setPosition(u, p); + else + for (let e = 0; e < 1; e++) { + const e = new h([t.x, t.y], [u, p], [a, o], r, !!c, !!l); + let s = e.getNextSegment(); + for (; s; ) { + const a = q(s.cp1[0], s.cp1[1], s.cp2[0], s.cp2[1], s.to[0], s.to[1], t, n); + (i = i.concat(a)), (s = e.getNextSegment()); + } + } + } + break; + } + } + return i; +} +const N = "undefined" != typeof self, + L = "none"; +class B { + constructor(t, e) { + (this.defaultOptions = { + maxRandomnessOffset: 2, + roughness: 1, + bowing: 1, + stroke: "#000", + strokeWidth: 1, + curveTightness: 0, + curveFitting: 0.95, + curveStepCount: 9, + fillStyle: "hachure", + fillWeight: -1, + hachureAngle: -41, + hachureGap: -1, + dashOffset: -1, + dashGap: -1, + zigzagOffset: -1, + seed: 0, + roughnessGain: 1, + }), + (this.config = t || {}), + (this.surface = e), + this.config.options && (this.defaultOptions = this._options(this.config.options)); + } + static newSeed() { + return Math.floor(Math.random() * Math.pow(2, 31)); + } + _options(t) { + return t ? Object.assign({}, this.defaultOptions, t) : this.defaultOptions; + } + _drawable(t, e, s) { + return { shape: t, sets: e || [], options: s || this.defaultOptions }; + } + line(t, e, s, n, i) { + const a = this._options(i); + return this._drawable("line", [x(t, e, s, n, a)], a); + } + rectangle(t, e, s, n, i) { + const a = this._options(i), + o = [], + h = k(t, e, s, n, a); + if (a.fill) { + const i = [ + [t, e], + [t + s, e], + [t + s, e + n], + [t, e + n], + ]; + "solid" === a.fillStyle ? o.push(S(i, a)) : o.push(O(i, a)); + } + return a.stroke !== L && o.push(h), this._drawable("rectangle", o, a); + } + ellipse(t, e, s, n, i) { + const a = this._options(i), + o = [], + h = P(s, n, a), + r = w(t, e, a, h); + if (a.fill) + if ("solid" === a.fillStyle) { + const s = w(t, e, a, h).opset; + (s.type = "fillPath"), o.push(s); + } else o.push(O(r.estimatedPoints, a)); + return a.stroke !== L && o.push(r.opset), this._drawable("ellipse", o, a); + } + circle(t, e, s, n) { + const i = this.ellipse(t, e, s, s, n); + return (i.shape = "circle"), i; + } + linearPath(t, e) { + const s = this._options(e); + return this._drawable("linearPath", [m(t, !1, s)], s); + } + arc(t, e, s, n, i, a, o = !1, h) { + const r = this._options(h), + c = [], + l = v(t, e, s, n, i, a, o, !0, r); + if (o && r.fill) + if ("solid" === r.fillStyle) { + const o = v(t, e, s, n, i, a, !0, !1, r); + (o.type = "fillPath"), c.push(o); + } else + c.push( + (function (t, e, s, n, i, a, o) { + const h = t, + r = e; + let c = Math.abs(s / 2), + l = Math.abs(n / 2); + (c += z(0.01 * c, o)), (l += z(0.01 * l, o)); + let u = i, + p = a; + for (; u < 0; ) (u += 2 * Math.PI), (p += 2 * Math.PI); + p - u > 2 * Math.PI && ((u = 0), (p = 2 * Math.PI)); + const d = (p - u) / o.curveStepCount, + f = []; + for (let t = u; t <= p; t += d) + f.push([h + c * Math.cos(t), r + l * Math.sin(t)]); + return ( + f.push([h + c * Math.cos(p), r + l * Math.sin(p)]), f.push([h, r]), O(f, o) + ); + })(t, e, s, n, i, a, r) + ); + return r.stroke !== L && c.push(l), this._drawable("arc", c, r); + } + curve(t, e) { + const s = this._options(e); + return this._drawable("curve", [b(t, s)], s); + } + polygon(t, e) { + const s = this._options(e), + n = [], + i = m(t, !0, s); + return ( + s.fill && ("solid" === s.fillStyle ? n.push(S(t, s)) : n.push(O(t, s))), + s.stroke !== L && n.push(i), + this._drawable("polygon", n, s) + ); + } + path(t, e) { + const s = this._options(e), + n = []; + if (!t) return this._drawable("path", n, s); + const i = (function (t, e) { + t = (t || "").replace(/\n/g, " ").replace(/(-\s)/g, "-").replace("/(ss)/g", " "); + let s = new o(t); + if (e.simplification) { + const t = new r(s.linearPoints, s.closed).fit(e.simplification); + s = new o(t); + } + let n = []; + const i = s.segments || []; + for (let t = 0; t < i.length; t++) { + const a = $(s, i[t], t > 0 ? i[t - 1] : null, e); + a && a.length && (n = n.concat(a)); + } + return { type: "path", ops: n }; + })(t, s); + if (s.fill) + if ("solid" === s.fillStyle) { + const e = { type: "path2Dfill", path: t, ops: [] }; + n.push(e); + } else { + const e = this.computePathSize(t), + i = O( + [ + [0, 0], + [e[0], 0], + [e[0], e[1]], + [0, e[1]], + ], + s + ); + (i.type = "path2Dpattern"), (i.size = e), (i.path = t), n.push(i); + } + return s.stroke !== L && n.push(i), this._drawable("path", n, s); + } + computePathSize(e) { + let s = [0, 0]; + if (N && self.document) + try { + const n = self.document.createElementNS(t, "svg"); + n.setAttribute("width", "0"), n.setAttribute("height", "0"); + const i = self.document.createElementNS(t, "path"); + i.setAttribute("d", e), n.appendChild(i), self.document.body.appendChild(n); + const a = i.getBBox(); + a && ((s[0] = a.width || 0), (s[1] = a.height || 0)), + self.document.body.removeChild(n); + } catch (t) {} + const n = this.getCanvasSize(); + return s[0] * s[1] || (s = n), s; + } + getCanvasSize() { + const t = (t) => + t && "object" == typeof t && t.baseVal && t.baseVal.value + ? t.baseVal.value + : t || 100; + return this.surface ? [t(this.surface.width), t(this.surface.height)] : [100, 100]; + } + opsToPath(t) { + let e = ""; + for (const s of t.ops) { + const t = s.data; + switch (s.op) { + case "move": + e += `M${t[0]} ${t[1]} `; + break; + case "bcurveTo": + e += `C${t[0]} ${t[1]}, ${t[2]} ${t[3]}, ${t[4]} ${t[5]} `; + break; + case "qcurveTo": + e += `Q${t[0]} ${t[1]}, ${t[2]} ${t[3]} `; + break; + case "lineTo": + e += `L${t[0]} ${t[1]} `; + } + } + return e.trim(); + } + toPaths(t) { + const e = t.sets || [], + s = t.options || this.defaultOptions, + n = []; + for (const t of e) { + let e = null; + switch (t.type) { + case "path": + e = { + d: this.opsToPath(t), + stroke: s.stroke, + strokeWidth: s.strokeWidth, + fill: L, + }; + break; + case "fillPath": + e = { + d: this.opsToPath(t), + stroke: L, + strokeWidth: 0, + fill: s.fill || L, + }; + break; + case "fillSketch": + e = this.fillSketch(t, s); + break; + case "path2Dfill": + e = { d: t.path || "", stroke: L, strokeWidth: 0, fill: s.fill || L }; + break; + case "path2Dpattern": { + const n = t.size, + i = { + x: 0, + y: 0, + width: 1, + height: 1, + viewBox: `0 0 ${Math.round(n[0])} ${Math.round(n[1])}`, + patternUnits: "objectBoundingBox", + path: this.fillSketch(t, s), + }; + e = { d: t.path, stroke: L, strokeWidth: 0, pattern: i }; + break; + } + } + e && n.push(e); + } + return n; + } + fillSketch(t, e) { + let s = e.fillWeight; + return ( + s < 0 && (s = e.strokeWidth / 2), + { d: this.opsToPath(t), stroke: e.fill || L, strokeWidth: s, fill: L } + ); + } +} +const G = "undefined" != typeof document; +class V { + constructor(t, e) { + (this.canvas = t), + (this.ctx = this.canvas.getContext("2d")), + (this.gen = new B(e, this.canvas)); + } + draw(t) { + const e = t.sets || [], + s = t.options || this.getDefaultOptions(), + n = this.ctx; + for (const t of e) + switch (t.type) { + case "path": + n.save(), + (n.strokeStyle = "none" === s.stroke ? "transparent" : s.stroke), + (n.lineWidth = s.strokeWidth), + this._drawToContext(n, t), + n.restore(); + break; + case "fillPath": + n.save(), (n.fillStyle = s.fill || ""), this._drawToContext(n, t), n.restore(); + break; + case "fillSketch": + this.fillSketch(n, t, s); + break; + case "path2Dfill": { + this.ctx.save(), (this.ctx.fillStyle = s.fill || ""); + const e = new Path2D(t.path); + this.ctx.fill(e), this.ctx.restore(); + break; + } + case "path2Dpattern": { + const e = this.canvas.ownerDocument || (G && document); + if (e) { + const n = t.size, + i = e.createElement("canvas"), + a = i.getContext("2d"), + o = this.computeBBox(t.path); + o && (o.width || o.height) + ? ((i.width = this.canvas.width), + (i.height = this.canvas.height), + a.translate(o.x || 0, o.y || 0)) + : ((i.width = n[0]), (i.height = n[1])), + this.fillSketch(a, t, s), + this.ctx.save(), + (this.ctx.fillStyle = this.ctx.createPattern(i, "repeat")); + const h = new Path2D(t.path); + this.ctx.fill(h), this.ctx.restore(); + } else console.error("Pattern fill fail: No defs"); + break; + } + } + } + computeBBox(e) { + if (G) + try { + const s = document.createElementNS(t, "svg"); + s.setAttribute("width", "0"), s.setAttribute("height", "0"); + const n = self.document.createElementNS(t, "path"); + n.setAttribute("d", e), s.appendChild(n), document.body.appendChild(s); + const i = n.getBBox(); + return document.body.removeChild(s), i; + } catch (t) {} + return null; + } + fillSketch(t, e, s) { + let n = s.fillWeight; + n < 0 && (n = s.strokeWidth / 2), + t.save(), + (t.strokeStyle = s.fill || ""), + (t.lineWidth = n), + this._drawToContext(t, e), + t.restore(); + } + _drawToContext(t, e) { + t.beginPath(); + for (const s of e.ops) { + const e = s.data; + switch (s.op) { + case "move": + t.moveTo(e[0], e[1]); + break; + case "bcurveTo": + t.bezierCurveTo(e[0], e[1], e[2], e[3], e[4], e[5]); + break; + case "qcurveTo": + t.quadraticCurveTo(e[0], e[1], e[2], e[3]); + break; + case "lineTo": + t.lineTo(e[0], e[1]); + } + } + "fillPath" === e.type ? t.fill() : t.stroke(); + } + get generator() { + return this.gen; + } + getDefaultOptions() { + return this.gen.defaultOptions; + } + line(t, e, s, n, i) { + const a = this.gen.line(t, e, s, n, i); + return this.draw(a), a; + } + rectangle(t, e, s, n, i) { + const a = this.gen.rectangle(t, e, s, n, i); + return this.draw(a), a; + } + ellipse(t, e, s, n, i) { + const a = this.gen.ellipse(t, e, s, n, i); + return this.draw(a), a; + } + circle(t, e, s, n) { + const i = this.gen.circle(t, e, s, n); + return this.draw(i), i; + } + linearPath(t, e) { + const s = this.gen.linearPath(t, e); + return this.draw(s), s; + } + polygon(t, e) { + const s = this.gen.polygon(t, e); + return this.draw(s), s; + } + arc(t, e, s, n, i, a, o = !1, h) { + const r = this.gen.arc(t, e, s, n, i, a, o, h); + return this.draw(r), r; + } + curve(t, e) { + const s = this.gen.curve(t, e); + return this.draw(s), s; + } + path(t, e) { + const s = this.gen.path(t, e); + return this.draw(s), s; + } +} +const j = "undefined" != typeof document; +class Q { + constructor(t, e) { + (this.svg = t), (this.gen = new B(e, this.svg)); + } + get defs() { + const e = this.svg.ownerDocument || (j && document); + if (e && !this._defs) { + const s = e.createElementNS(t, "defs"); + this.svg.firstChild + ? this.svg.insertBefore(s, this.svg.firstChild) + : this.svg.appendChild(s), + (this._defs = s); + } + return this._defs || null; + } + draw(e) { + const s = e.sets || [], + n = e.options || this.getDefaultOptions(), + i = this.svg.ownerDocument || window.document, + a = i.createElementNS(t, "g"); + for (const e of s) { + let s = null; + switch (e.type) { + case "path": + (s = i.createElementNS(t, "path")), + s.setAttribute("d", this.opsToPath(e)), + (s.style.stroke = n.stroke), + (s.style.strokeWidth = n.strokeWidth + ""), + (s.style.fill = "none"); + break; + case "fillPath": + (s = i.createElementNS(t, "path")), + s.setAttribute("d", this.opsToPath(e)), + (s.style.stroke = "none"), + (s.style.strokeWidth = "0"), + (s.style.fill = n.fill || ""); + break; + case "fillSketch": + s = this.fillSketch(i, e, n); + break; + case "path2Dfill": + (s = i.createElementNS(t, "path")), + s.setAttribute("d", e.path || ""), + (s.style.stroke = "none"), + (s.style.strokeWidth = "0"), + (s.style.fill = n.fill || ""); + break; + case "path2Dpattern": + if (this.defs) { + const a = e.size, + o = i.createElementNS(t, "pattern"), + h = `rough-${Math.floor( + Math.random() * (Number.MAX_SAFE_INTEGER || 999999) + )}`; + o.setAttribute("id", h), + o.setAttribute("x", "0"), + o.setAttribute("y", "0"), + o.setAttribute("width", "1"), + o.setAttribute("height", "1"), + o.setAttribute("height", "1"), + o.setAttribute("viewBox", `0 0 ${Math.round(a[0])} ${Math.round(a[1])}`), + o.setAttribute("patternUnits", "objectBoundingBox"); + const r = this.fillSketch(i, e, n); + o.appendChild(r), + this.defs.appendChild(o), + (s = i.createElementNS(t, "path")), + s.setAttribute("d", e.path || ""), + (s.style.stroke = "none"), + (s.style.strokeWidth = "0"), + (s.style.fill = `url(#${h})`); + } else console.error("Pattern fill fail: No defs"); + } + s && a.appendChild(s); + } + return a; + } + fillSketch(e, s, n) { + let i = n.fillWeight; + i < 0 && (i = n.strokeWidth / 2); + const a = e.createElementNS(t, "path"); + return ( + a.setAttribute("d", this.opsToPath(s)), + (a.style.stroke = n.fill || ""), + (a.style.strokeWidth = i + ""), + (a.style.fill = "none"), + a + ); + } + get generator() { + return this.gen; + } + getDefaultOptions() { + return this.gen.defaultOptions; + } + opsToPath(t) { + return this.gen.opsToPath(t); + } + line(t, e, s, n, i) { + const a = this.gen.line(t, e, s, n, i); + return this.draw(a); + } + rectangle(t, e, s, n, i) { + const a = this.gen.rectangle(t, e, s, n, i); + return this.draw(a); + } + ellipse(t, e, s, n, i) { + const a = this.gen.ellipse(t, e, s, n, i); + return this.draw(a); + } + circle(t, e, s, n) { + const i = this.gen.circle(t, e, s, n); + return this.draw(i); + } + linearPath(t, e) { + const s = this.gen.linearPath(t, e); + return this.draw(s); + } + polygon(t, e) { + const s = this.gen.polygon(t, e); + return this.draw(s); + } + arc(t, e, s, n, i, a, o = !1, h) { + const r = this.gen.arc(t, e, s, n, i, a, o, h); + return this.draw(r); + } + curve(t, e) { + const s = this.gen.curve(t, e); + return this.draw(s); + } + path(t, e) { + const s = this.gen.path(t, e); + return this.draw(s); + } +} +var rough = { + canvas: (t, e) => new V(t, e), + svg: (t, e) => new Q(t, e), + generator: (t, e) => new B(t, e), + newSeed: () => B.newSeed(), +}; diff --git a/d2renderers/d2sketch/setup.js b/d2renderers/d2sketch/setup.js new file mode 100644 index 000000000..87683d55f --- /dev/null +++ b/d2renderers/d2sketch/setup.js @@ -0,0 +1,19 @@ +const root = { + ownerDocument: { + createElementNS: (ns, tagName) => { + const children = []; + const attrs = {}; + const style = {}; + return { + style, + tagName, + attrs, + setAttribute: (key, value) => (attrs[key] = value), + appendChild: (node) => children.push(node), + children, + }; + }, + }, +}; +const rc = rough.svg(root, { seed: 1 }); +let node; diff --git a/d2renderers/d2sketch/sketch.go b/d2renderers/d2sketch/sketch.go new file mode 100644 index 000000000..4c4913a2b --- /dev/null +++ b/d2renderers/d2sketch/sketch.go @@ -0,0 +1,498 @@ +package d2sketch + +import ( + "encoding/json" + "fmt" + "strings" + + _ "embed" + + "github.com/dop251/goja" + + "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/d2/lib/svg" + "oss.terrastruct.com/util-go/go2" +) + +//go:embed fillpattern.svg +var fillPattern string + +//go:embed rough.js +var roughJS string + +//go:embed setup.js +var setupJS string + +type Runner goja.Runtime + +var baseRoughProps = `fillWeight: 2.0, +hachureGap: 16, +fillStyle: "solid", +bowing: 2, +seed: 1,` + +func (r *Runner) run(js string) (goja.Value, error) { + vm := (*goja.Runtime)(r) + return vm.RunString(js) +} + +func InitSketchVM() (*Runner, error) { + vm := goja.New() + if _, err := vm.RunString(roughJS); err != nil { + return nil, err + } + if _, err := vm.RunString(setupJS); err != nil { + return nil, err + } + r := Runner(*vm) + return &r, nil +} + +// DefineFillPattern adds a reusable pattern that is overlayed on shapes with +// fill. This gives it a subtle streaky effect that subtly looks hand-drawn but +// not distractingly so. +func DefineFillPattern() string { + return fmt.Sprintf(` + + %s + +`, fillPattern) +} + +func shapeStyle(shape d2target.Shape) string { + out := "" + + if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass { + out += fmt.Sprintf(`fill:%s;`, shape.Stroke) + out += fmt.Sprintf(`stroke:%s;`, shape.Fill) + } else { + out += fmt.Sprintf(`fill:%s;`, shape.Fill) + out += fmt.Sprintf(`stroke:%s;`, shape.Stroke) + } + out += fmt.Sprintf(`opacity:%f;`, shape.Opacity) + out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth) + if shape.StrokeDash != 0 { + dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash) + out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize) + } + + return out +} + +func Rect(r *Runner, shape d2target.Shape) (string, error) { + js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + paths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + output := "" + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape), + ) + } + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, shape.Width, shape.Height, + ) + return output, nil +} + +func Oval(r *Runner, shape d2target.Shape) (string, error) { + js := fmt.Sprintf(`node = rc.ellipse(%d, %d, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width/2, shape.Height/2, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + paths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + output := "" + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape), + ) + } + output += fmt.Sprintf( + ``, + shape.Pos.X+shape.Width/2, shape.Pos.Y+shape.Height/2, shape.Width/2, shape.Height/2, + ) + return output, nil +} + +// TODO need to personalize this per shape like we do in Terrastruct app +func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) { + output := "" + for _, path := range paths { + js := fmt.Sprintf(`node = rc.path("%s", { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, path, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + sketchPaths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range sketchPaths { + output += fmt.Sprintf( + ``, + p, shapeStyle(shape), + ) + } + for _, p := range sketchPaths { + output += fmt.Sprintf( + ``, + p, + ) + } + } + return output, nil +} + +func connectionStyle(connection d2target.Connection) string { + out := "" + + out += fmt.Sprintf(`stroke:%s;`, connection.Stroke) + out += fmt.Sprintf(`opacity:%f;`, connection.Opacity) + out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth) + if connection.StrokeDash != 0 { + dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash) + out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize) + } + + return out +} + +func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) { + roughness := 1.0 + js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness) + paths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + output := "" + for _, p := range paths { + output += fmt.Sprintf( + ``, + p, connectionStyle(connection), attrs, + ) + } + return output, nil +} + +// TODO cleanup +func Table(r *Runner, shape d2target.Shape) (string, error) { + output := "" + js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + paths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape), + ) + } + + box := geo.NewBox( + geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)), + float64(shape.Width), + float64(shape.Height), + ) + rowHeight := box.Height / float64(1+len(shape.SQLTable.Columns)) + headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight) + + js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, { + fill: "%s", + %s + });`, shape.Width, rowHeight, shape.Fill, baseRoughProps) + paths, err = computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shape.Fill, + ) + } + + if shape.Label != "" { + tl := label.InsideMiddleLeft.GetPointOnBox( + headerBox, + 20, + float64(shape.LabelWidth), + float64(shape.LabelHeight), + ) + + output += fmt.Sprintf(`%s`, + "text", + tl.X, + tl.Y+float64(shape.LabelHeight)*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", + "start", + 4+shape.FontSize, + shape.Stroke, + ), + svg.EscapeText(shape.Label), + ) + } + + var longestNameWidth int + for _, f := range shape.Columns { + longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth) + } + + rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight) + rowBox.TopLeft.Y += headerBox.Height + for _, f := range shape.Columns { + nameTL := label.InsideMiddleLeft.GetPointOnBox( + rowBox, + d2target.NamePadding, + rowBox.Width, + float64(shape.FontSize), + ) + constraintTR := label.InsideMiddleRight.GetPointOnBox( + rowBox, + d2target.TypePadding, + 0, + float64(shape.FontSize), + ) + + output += strings.Join([]string{ + fmt.Sprintf(`%s`, + nameTL.X, + nameTL.Y+float64(shape.FontSize)*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.PrimaryAccentColor), + svg.EscapeText(f.Name.Label), + ), + fmt.Sprintf(`%s`, + nameTL.X+float64(longestNameWidth)+2*d2target.NamePadding, + nameTL.Y+float64(shape.FontSize)*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", float64(shape.FontSize), shape.NeutralAccentColor), + svg.EscapeText(f.Type.Label), + ), + fmt.Sprintf(`%s`, + constraintTR.X, + constraintTR.Y+float64(shape.FontSize)*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", float64(shape.FontSize), shape.SecondaryAccentColor), + f.ConstraintAbbr(), + ), + }, "\n") + + rowBox.TopLeft.Y += rowHeight + + js = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, { + %s + });`, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, baseRoughProps) + paths, err = computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + p, shape.Fill, + ) + } + } + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, shape.Width, shape.Height, + ) + return output, nil +} + +func Class(r *Runner, shape d2target.Shape) (string, error) { + output := "" + js := fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %d, { + fill: "%s", + stroke: "%s", + strokeWidth: %d, + %s + });`, shape.Width, shape.Height, shape.Fill, shape.Stroke, shape.StrokeWidth, baseRoughProps) + paths, err := computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shapeStyle(shape), + ) + } + + box := geo.NewBox( + geo.NewPoint(float64(shape.Pos.X), float64(shape.Pos.Y)), + float64(shape.Width), + float64(shape.Height), + ) + + rowHeight := box.Height / float64(2+len(shape.Class.Fields)+len(shape.Class.Methods)) + headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight) + + js = fmt.Sprintf(`node = rc.rectangle(0, 0, %d, %f, { + fill: "%s", + %s + });`, shape.Width, headerBox.Height, shape.Fill, baseRoughProps) + paths, err = computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, p, shape.Fill, + ) + } + + output += fmt.Sprintf( + ``, + shape.Pos.X, shape.Pos.Y, shape.Width, headerBox.Height, + ) + + if shape.Label != "" { + tl := label.InsideMiddleCenter.GetPointOnBox( + headerBox, + 0, + float64(shape.LabelWidth), + float64(shape.LabelHeight), + ) + + output += fmt.Sprintf(`%s`, + "text-mono", + tl.X+float64(shape.LabelWidth)/2, + tl.Y+float64(shape.LabelHeight)*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", + "middle", + 4+shape.FontSize, + shape.Stroke, + ), + svg.EscapeText(shape.Label), + ) + } + + rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight) + rowBox.TopLeft.Y += headerBox.Height + for _, f := range shape.Fields { + output += classRow(shape, rowBox, f.VisibilityToken(), f.Name, f.Type, float64(shape.FontSize)) + rowBox.TopLeft.Y += rowHeight + } + + js = fmt.Sprintf(`node = rc.line(%f, %f, %f, %f, { +%s + });`, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, baseRoughProps) + paths, err = computeRoughPaths(r, js) + if err != nil { + return "", err + } + for _, p := range paths { + output += fmt.Sprintf( + ``, + p, shape.Fill, + ) + } + + for _, m := range shape.Methods { + output += classRow(shape, rowBox, m.VisibilityToken(), m.Name, m.Return, float64(shape.FontSize)) + rowBox.TopLeft.Y += rowHeight + } + + return output, nil +} + +func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText string, fontSize float64) string { + output := "" + prefixTL := label.InsideMiddleLeft.GetPointOnBox( + box, + d2target.PrefixPadding, + box.Width, + fontSize, + ) + typeTR := label.InsideMiddleRight.GetPointOnBox( + box, + d2target.TypePadding, + 0, + fontSize, + ) + + output += strings.Join([]string{ + fmt.Sprintf(`%s`, + prefixTL.X, + prefixTL.Y+fontSize*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor), + prefix, + ), + + fmt.Sprintf(`%s`, + prefixTL.X+d2target.PrefixWidth, + prefixTL.Y+fontSize*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill), + svg.EscapeText(nameText), + ), + + fmt.Sprintf(`%s`, + typeTR.X, + typeTR.Y+fontSize*3/4, + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;", "end", fontSize, shape.SecondaryAccentColor), + svg.EscapeText(typeText), + ), + }, "\n") + return output +} + +func computeRoughPaths(r *Runner, js string) ([]string, error) { + if _, err := r.run(js); err != nil { + return nil, err + } + return extractPaths(r) +} + +type attrs struct { + D string `json:"d"` +} + +type node struct { + Attrs attrs `json:"attrs"` +} + +func extractPaths(r *Runner) ([]string, error) { + val, err := r.run("JSON.stringify(node.children)") + if err != nil { + return nil, err + } + + var nodes []node + + err = json.Unmarshal([]byte(val.String()), &nodes) + if err != nil { + return nil, err + } + + var paths []string + for _, n := range nodes { + paths = append(paths, n.Attrs.D) + } + + return paths, nil +} diff --git a/d2renderers/d2sketch/sketch_test.go b/d2renderers/d2sketch/sketch_test.go new file mode 100644 index 000000000..e033bedfe --- /dev/null +++ b/d2renderers/d2sketch/sketch_test.go @@ -0,0 +1,344 @@ +package d2sketch_test + +import ( + "context" + "encoding/xml" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "cdr.dev/slog" + + tassert "github.com/stretchr/testify/assert" + + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" + "oss.terrastruct.com/util-go/go2" + + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2lib" + "oss.terrastruct.com/d2/d2renderers/d2fonts" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" +) + +func TestSketch(t *testing.T) { + t.Parallel() + + tcs := []testCase{ + { + name: "basic", + script: `a -> b +`, + }, + { + name: "child to child", + script: `winter.snow -> summer.sun + `, + }, + { + name: "connection label", + script: `a -> b: hello + `, + }, + { + name: "twitter", + script: `timeline mixer: "" { + explanation: |md + ## **Timeline mixer** + - Inject ads, who-to-follow, onboarding + - Conversation module + - Cursoring,pagination + - Tweat deduplication + - Served data logging + | +} +People discovery: "People discovery \nservice" +admixer: Ad mixer { + fill: "#c1a2f3" +} + +onboarding service: "Onboarding \nservice" +timeline mixer -> People discovery +timeline mixer -> onboarding service +timeline mixer -> admixer +container0: "" { + graphql + comment + tlsapi +} +container0.graphql: GraphQL\nFederated Strato Column { + shape: image + icon: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1200px-GraphQL_Logo.svg.png +} +container0.comment: |md + ## Tweet/user content hydration, visibility filtering +| +container0.tlsapi: TLS-API (being deprecated) +container0.graphql -> timeline mixer +timeline mixer <- container0.tlsapi +twitter fe: "Twitter Frontend " { + icon: https://icons.terrastruct.com/social/013-twitter-1.svg + shape: image +} +twitter fe -> container0.graphql: iPhone web +twitter fe -> container0.tlsapi: HTTP Android +web: Web { + icon: https://icons.terrastruct.com/azure/Web%20Service%20Color/App%20Service%20Domains.svg + shape: image +} + +Iphone: { + icon: 'https://ss7.vzw.com/is/image/VerizonWireless/apple-iphone-12-64gb-purple-53017-mjn13ll-a?$device-lg$' + shape: image +} +Android: { + icon: https://cdn4.iconfinder.com/data/icons/smart-phones-technologies/512/android-phone.png + shape: image +} + +web -> twitter fe +timeline scorer: "Timeline\nScorer" { + fill: "#ffdef1" +} +home ranker: Home Ranker + +timeline service: Timeline Service +timeline mixer -> timeline scorer: Thrift RPC +timeline mixer -> home ranker: { + style.stroke-dash: 4 + style.stroke: "#000E3D" +} +timeline mixer -> timeline service +home mixer: Home mixer { + # fill: "#c1a2f3" +} +container0.graphql -> home mixer: { + style.stroke-dash: 4 + style.stroke: "#000E3D" +} +home mixer -> timeline scorer +home mixer -> home ranker: { + style.stroke-dash: 4 + style.stroke: "#000E3D" +} +home mixer -> timeline service +manhattan 2: Manhattan +gizmoduck: Gizmoduck +socialgraph: Social graph +tweetypie: Tweety Pie +home mixer -> manhattan 2 +home mixer -> gizmoduck +home mixer -> socialgraph +home mixer -> tweetypie +Iphone -> twitter fe +Android -> twitter fe +prediction service2: Prediction Service { + shape: image + icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png +} +home scorer: Home Scorer { + fill: "#ffdef1" +} +manhattan: Manhattan +memcache: Memcache { + icon: https://d1q6f0aelx0por.cloudfront.net/product-logos/de041504-0ddb-43f6-b89e-fe04403cca8d-memcached.png +} + +fetch: Fetch { + multiple: true + shape: step +} +feature: Feature { + multiple: true + shape: step +} +scoring: Scoring { + multiple: true + shape: step +} +fetch -> feature +feature -> scoring + +prediction service: Prediction Service { + shape: image + icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png +} +scoring -> prediction service +fetch -> container2.crmixer + +home scorer -> manhattan: "" + +home scorer -> memcache: "" +home scorer -> prediction service2 +home ranker -> home scorer +home ranker -> container2.crmixer: Candidate Fetch +container2: "" { + style.stroke: "#000E3D" + style.fill: "#ffffff" + crmixer: CrMixer { + style.fill: "#F7F8FE" + } + earlybird: EarlyBird + utag: Utag + space: Space + communities: Communities +} +etc: ...etc + +home scorer -> etc: Feature Hydration + +feature -> manhattan +feature -> memcache +feature -> etc: Candidate sources + `, + }, + { + name: "all_shapes", + script: ` +rectangle: {shape: "rectangle"} +square: {shape: "square"} +page: {shape: "page"} +parallelogram: {shape: "parallelogram"} +document: {shape: "document"} +cylinder: {shape: "cylinder"} +queue: {shape: "queue"} +package: {shape: "package"} +step: {shape: "step"} +callout: {shape: "callout"} +stored_data: {shape: "stored_data"} +person: {shape: "person"} +diamond: {shape: "diamond"} +oval: {shape: "oval"} +circle: {shape: "circle"} +hexagon: {shape: "hexagon"} +cloud: {shape: "cloud"} + +rectangle -> square -> page +parallelogram -> document -> cylinder +queue -> package -> step +callout -> stored_data -> person +diamond -> oval -> circle +hexagon -> cloud +`, + }, + { + name: "sql_tables", + script: `users: { + shape: sql_table + id: int + name: string + email: string + password: string + last_login: datetime +} + +products: { + shape: sql_table + id: int + price: decimal + sku: string + name: string +} + +orders: { + shape: sql_table + id: int + user_id: int + product_id: int +} + +shipments: { + shape: sql_table + id: int + order_id: int + tracking_number: string {constraint: primary_key} + status: string +} + +users.id <-> orders.user_id +products.id <-> orders.product_id +shipments.order_id <-> orders.id`, + }, + { + name: "class", + script: `manager: BatchManager { + shape: class + -num: int + -timeout: int + -pid + + +getStatus(): Enum + +getJobs(): "Job[]" + +setTimeout(seconds int) +} +`, + }, + } + runa(t, tcs) +} + +type testCase struct { + name string + script string + skip bool +} + +func runa(t *testing.T, tcs []testCase) { + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.skip { + t.Skip() + } + t.Parallel() + + run(t, tc) + }) + } +} + +func run(t *testing.T, tc testCase) { + ctx := context.Background() + ctx = log.WithTB(ctx, t, nil) + ctx = log.Leveled(ctx, slog.LevelDebug) + + ruler, err := textmeasure.NewRuler() + if !tassert.Nil(t, err) { + return + } + + diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ + Ruler: ruler, + ThemeID: 0, + Layout: d2dagrelayout.Layout, + FontFamily: go2.Pointer(d2fonts.HandDrawn), + }) + if !tassert.Nil(t, err) { + return + } + + dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestSketch/")) + pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") + + svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ + Pad: d2svg.DEFAULT_PADDING, + Sketch: true, + }) + assert.Success(t, err) + err = os.MkdirAll(dataPath, 0755) + assert.Success(t, err) + err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600) + assert.Success(t, err) + defer os.Remove(pathGotSVG) + + var xmlParsed interface{} + err = xml.Unmarshal(svgBytes, &xmlParsed) + assert.Success(t, err) + + err = diff.Testdata(filepath.Join(dataPath, "sketch"), ".svg", svgBytes) + assert.Success(t, err) +} diff --git a/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg new file mode 100644 index 000000000..5b8429973 --- /dev/null +++ b/d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg @@ -0,0 +1,43 @@ + + + + + + +rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/basic/sketch.exp.svg b/d2renderers/d2sketch/testdata/basic/sketch.exp.svg new file mode 100644 index 000000000..fbc93c300 --- /dev/null +++ b/d2renderers/d2sketch/testdata/basic/sketch.exp.svg @@ -0,0 +1,43 @@ + + + + + + +ab + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg b/d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg new file mode 100644 index 000000000..2f8fbf88b --- /dev/null +++ b/d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg @@ -0,0 +1,50 @@ + + + + + + +wintersummersnowsun + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/class/sketch.exp.svg b/d2renderers/d2sketch/testdata/class/sketch.exp.svg new file mode 100644 index 000000000..5dd1ee1a5 --- /dev/null +++ b/d2renderers/d2sketch/testdata/class/sketch.exp.svg @@ -0,0 +1,55 @@ + + + + + + +BatchManager- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +void + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/connection_label/board.exp.json b/d2renderers/d2sketch/testdata/connection_label/board.exp.json new file mode 100644 index 000000000..fc640e88a --- /dev/null +++ b/d2renderers/d2sketch/testdata/connection_label/board.exp.json @@ -0,0 +1,136 @@ +{ + "name": "", + "fontFamily": "HandDrawn", + "shapes": [ + { + "id": "a", + "type": "", + "pos": { + "x": 1, + "y": 0 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "", + "pos": { + "x": 0, + "y": 226 + }, + "width": 115, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 15, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "b", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "hello", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 31, + "labelHeight": 23, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 57.5, + "y": 126 + }, + { + "x": 57.5, + "y": 166 + }, + { + "x": 57.5, + "y": 186 + }, + { + "x": 57.5, + "y": 226 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg b/d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg new file mode 100644 index 000000000..bc8c7e229 --- /dev/null +++ b/d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg @@ -0,0 +1,50 @@ + + + + + + +ab hello + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/sql_tables/sketch.exp.svg b/d2renderers/d2sketch/testdata/sql_tables/sketch.exp.svg new file mode 100644 index 000000000..68d7a9a2a --- /dev/null +++ b/d2renderers/d2sketch/testdata/sql_tables/sketch.exp.svg @@ -0,0 +1,75 @@ + + + + + + +usersid +int +name +string +email +string +password +string +last_login +datetime +productsid +int +price +decimal +sku +string +name +string +ordersid +int +user_id +int +product_id +int +shipmentsid +int +order_id +int +tracking_number +string +PKstatus +string + + + + \ No newline at end of file diff --git a/d2renderers/d2sketch/testdata/twitter/sketch.exp.svg b/d2renderers/d2sketch/testdata/twitter/sketch.exp.svg new file mode 100644 index 000000000..a45b29f99 --- /dev/null +++ b/d2renderers/d2sketch/testdata/twitter/sketch.exp.svg @@ -0,0 +1,828 @@ + + + + + + +People discovery serviceAd mixerOnboarding serviceTwitter Frontend WebIphoneAndroidTimelineScorerHome RankerTimeline ServiceHome mixerManhattanGizmoduckSocial graphTweety PiePrediction ServiceHome ScorerManhattanMemcacheFetchFeatureScoringPrediction Service...etc

Timeline mixer

+
    +
  • Inject ads, who-to-follow, onboarding
  • +
  • Conversation module
  • +
  • Cursoring,pagination
  • +
  • Tweat deduplication
  • +
  • Served data logging
  • +
+
GraphQLFederated Strato Column

Tweet/user content hydration, visibility filtering

+
TLS-API (being deprecated)CrMixerEarlyBirdUtagSpaceCommunities iPhone webHTTP AndroidThrift RPC Candidate FetchFeature HydrationCandidate sources + + + + + + + +
\ No newline at end of file diff --git a/d2renderers/d2svg/appendix/appendix.go b/d2renderers/d2svg/appendix/appendix.go new file mode 100644 index 000000000..f25dc1c02 --- /dev/null +++ b/d2renderers/d2svg/appendix/appendix.go @@ -0,0 +1,186 @@ +// appendix.go writes appendices/footnotes to SVG +// Intended to be run only for static exports, like PNG or PDF. +// SVG exports are already interactive. + +package appendix + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2renderers/d2fonts" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/textmeasure" + "oss.terrastruct.com/util-go/go2" +) + +// โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +// โ”‚ โ”‚ +// โ”‚ DIAGRAM โ”‚ +// โ”‚ โ”‚ +// PAD_ โ”‚ โ”‚ +// SIDES โ”‚ โ”‚ +// โ”‚ โ”‚ โ”‚ +// โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +// โ–ผ โ—„โ”€โ”€โ”€โ”€โ”€โ”€ PAD_TOP +// +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// +// +// 1. asdfasdf +// +// โ—„โ”€โ”€โ”€โ”€ SPACER +// 2. qwerqwer +// +// + +const ( + PAD_TOP = 50 + PAD_SIDES = 40 + SPACER = 20 + + FONT_SIZE = 16 + ICON_RADIUS = 16 +) + +var viewboxRegex = regexp.MustCompile(`viewBox=\"([0-9\- ]+)\"`) +var widthRegex = regexp.MustCompile(`width=\"([0-9]+)\"`) +var heightRegex = regexp.MustCompile(`height=\"([0-9]+)\"`) + +func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []byte { + svg := string(in) + + appendix, w, h := generateAppendix(diagram, ruler, svg) + + if h == 0 { + return in + } + + viewboxMatch := viewboxRegex.FindStringSubmatch(svg) + viewboxRaw := viewboxMatch[1] + viewboxSlice := strings.Split(viewboxRaw, " ") + viewboxPadLeft, _ := strconv.Atoi(viewboxSlice[0]) + viewboxWidth, _ := strconv.Atoi(viewboxSlice[2]) + viewboxHeight, _ := strconv.Atoi(viewboxSlice[3]) + + tl, br := diagram.BoundingBox() + seperator := fmt.Sprintf(``, + tl.X-PAD_SIDES, br.Y+PAD_TOP, go2.IntMax(w, br.X)+PAD_SIDES, br.Y+PAD_TOP) + appendix = seperator + appendix + + w -= viewboxPadLeft + w += PAD_SIDES * 2 + if viewboxWidth < w { + viewboxWidth = w + } + + viewboxHeight += h + PAD_TOP + + newViewbox := fmt.Sprintf(`viewBox="%s %s %s %s"`, viewboxSlice[0], viewboxSlice[1], strconv.Itoa(viewboxWidth), strconv.Itoa(viewboxHeight)) + + widthMatch := widthRegex.FindStringSubmatch(svg) + heightMatch := heightRegex.FindStringSubmatch(svg) + newWidth := fmt.Sprintf(`width="%s"`, strconv.Itoa(viewboxWidth)) + newHeight := fmt.Sprintf(`height="%s"`, strconv.Itoa(viewboxHeight)) + + svg = strings.Replace(svg, viewboxMatch[0], newViewbox, 1) + svg = strings.Replace(svg, widthMatch[0], newWidth, 1) + svg = strings.Replace(svg, heightMatch[0], newHeight, 1) + + if !strings.Contains(svg, `font-family: "font-regular"`) { + appendix += fmt.Sprintf(``, d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)]) + } + if !strings.Contains(svg, `font-family: "font-bold"`) { + appendix += fmt.Sprintf(``, d2fonts.FontEncodings[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)]) + } + + closingIndex := strings.LastIndex(svg, "") + svg = svg[:closingIndex] + appendix + svg[closingIndex:] + + i := 1 + for _, s := range diagram.Shapes { + if s.Tooltip != "" { + // The clip-path has a unique ID, so this won't replace any user icons + // In the existing SVG, the transform places it top-left, so we adjust + svg = strings.Replace(svg, d2svg.TooltipIcon, generateNumberedIcon(i, 0, ICON_RADIUS), 1) + i++ + } + if s.Link != "" { + svg = strings.Replace(svg, d2svg.LinkIcon, generateNumberedIcon(i, 0, ICON_RADIUS), 1) + i++ + } + } + + return []byte(svg) +} + +func generateAppendix(diagram *d2target.Diagram, ruler *textmeasure.Ruler, svg string) (string, int, int) { + tl, br := diagram.BoundingBox() + + maxWidth, totalHeight := 0, 0 + + var lines []string + i := 1 + + for _, s := range diagram.Shapes { + for _, txt := range []string{s.Tooltip, s.Link} { + if txt != "" { + line, w, h := generateLine(i, br.Y+(PAD_TOP*2)+totalHeight, txt, ruler) + i++ + lines = append(lines, line) + maxWidth = go2.IntMax(maxWidth, w) + totalHeight += h + SPACER + } + } + } + + return fmt.Sprintf(`%s +`, tl.X, br.Y, (br.X - tl.X), strings.Join(lines, "\n")), maxWidth, totalHeight +} + +func generateNumberedIcon(i, x, y int) string { + line := fmt.Sprintf(``, + x+ICON_RADIUS, y, ICON_RADIUS) + + line += fmt.Sprintf(`%d`, + x+ICON_RADIUS, y+5, FONT_SIZE, i) + + return line +} + +func generateLine(i, y int, text string, ruler *textmeasure.Ruler) (string, int, int) { + mtext := &d2target.MText{ + Text: text, + FontSize: FONT_SIZE, + } + + dims := d2graph.GetTextDimensions(nil, ruler, mtext, nil) + + line := fmt.Sprintf(`%s`, + 0, y, generateNumberedIcon(i, 0, 0)) + + line += fmt.Sprintf(`%s`, + ICON_RADIUS*3, y, FONT_SIZE, d2svg.RenderText(text, ICON_RADIUS*3, float64(dims.Height))) + + return line, dims.Width + ICON_RADIUS*3, dims.Height +} diff --git a/d2renderers/d2svg/appendix/appendix_test.go b/d2renderers/d2svg/appendix/appendix_test.go new file mode 100644 index 000000000..a4105c38e --- /dev/null +++ b/d2renderers/d2svg/appendix/appendix_test.go @@ -0,0 +1,151 @@ +package appendix_test + +import ( + "context" + "encoding/xml" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "cdr.dev/slog" + + tassert "github.com/stretchr/testify/assert" + + "oss.terrastruct.com/util-go/assert" + "oss.terrastruct.com/util-go/diff" + + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2lib" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/d2renderers/d2svg/appendix" + "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" +) + +func TestAppendix(t *testing.T) { + t.Parallel() + + tcs := []testCase{ + { + name: "tooltip_wider_than_diagram", + script: `x: { tooltip: Total abstinence is easier than perfect moderation } +y: { tooltip: Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS! } +x -> y +`, + }, + { + name: "diagram_wider_than_tooltip", + script: `shape: sequence_diagram + +customer +issuer +store: { tooltip: Like starbucks or something } +acquirer: { tooltip: I'm not sure what this is } +network +customer bank +store bank + +customer: {shape: person} +customer bank: { + shape: image + icon: https://cdn-icons-png.flaticon.com/512/858/858170.png +} +store bank: { + shape: image + icon: https://cdn-icons-png.flaticon.com/512/858/858170.png +} + +initial transaction: { + customer -> store: 1 banana please + store -> customer: '$10 dollars' +} +customer.internal -> customer.internal: "thinking: wow, inflation" +customer.internal -> customer bank: checks bank account +customer bank -> customer.internal: 'Savings: $11' +customer."An error in judgement is about to occur" +customer -> store: I can do that, here's my card +payment processor behind the scenes: { + store -> acquirer: Run this card + acquirer -> network: Process to card issuer + simplified: { + network -> issuer: Process this payment + issuer -> customer bank: '$10 debit' + acquirer -> store bank: '$10 credit' + } +} +`, + }, + { + name: "links", + script: `x: { link: https://d2lang.com } + y: { link: https://terrastruct.com; tooltip: Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS! } +x -> y +`, + }, + } + runa(t, tcs) +} + +type testCase struct { + name string + script string + skip bool +} + +func runa(t *testing.T, tcs []testCase) { + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.skip { + t.Skip() + } + t.Parallel() + + run(t, tc) + }) + } +} + +func run(t *testing.T, tc testCase) { + ctx := context.Background() + ctx = log.WithTB(ctx, t, nil) + ctx = log.Leveled(ctx, slog.LevelDebug) + + ruler, err := textmeasure.NewRuler() + if !tassert.Nil(t, err) { + return + } + + diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ + Ruler: ruler, + ThemeID: 0, + Layout: d2dagrelayout.Layout, + }) + if !tassert.Nil(t, err) { + return + } + + dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestAppendix/")) + pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") + + svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ + Pad: d2svg.DEFAULT_PADDING, + }) + assert.Success(t, err) + svgBytes = appendix.Append(diagram, ruler, svgBytes) + + err = os.MkdirAll(dataPath, 0755) + assert.Success(t, err) + err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600) + assert.Success(t, err) + defer os.Remove(pathGotSVG) + + var xmlParsed interface{} + err = xml.Unmarshal(svgBytes, &xmlParsed) + assert.Success(t, err) + + err = diff.Testdata(filepath.Join(dataPath, "sketch"), ".svg", svgBytes) + assert.Success(t, err) +} diff --git a/d2renderers/d2svg/appendix/testdata/diagram_wider_than_tooltip/sketch.exp.svg b/d2renderers/d2svg/appendix/testdata/diagram_wider_than_tooltip/sketch.exp.svg new file mode 100644 index 000000000..6c5e1e58b --- /dev/null +++ b/d2renderers/d2svg/appendix/testdata/diagram_wider_than_tooltip/sketch.exp.svg @@ -0,0 +1,64 @@ + +customerissuerstore1Like starbucks or somethingacquirer2I'm not sure what this isnetworkcustomer bankstore bankinitial transactionpayment processor behind the scenessimplified 1 banana please$10 dollarsthinking: wow, inflationchecks bank accountSavings: $11I can do that, here's my cardRun this cardProcess to card issuerProcess this payment$10 debit$10 creditAn error in judgement is about to occur + + + + + + + + + + + + + + + +1Like starbucks or something +2I'm not sure what this is + \ No newline at end of file diff --git a/d2renderers/d2svg/appendix/testdata/links/sketch.exp.svg b/d2renderers/d2svg/appendix/testdata/links/sketch.exp.svg new file mode 100644 index 000000000..e4f4e7c0d --- /dev/null +++ b/d2renderers/d2svg/appendix/testdata/links/sketch.exp.svg @@ -0,0 +1,46 @@ + +x1y2Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS!3 + + +1https://d2lang.com +2Gee, I feel kind of LIGHT in the head now,knowing I can't make my satellite dish PAYMENTS! +3https://terrastruct.com + \ No newline at end of file diff --git a/d2renderers/d2svg/appendix/testdata/tooltip_wider_than_diagram/sketch.exp.svg b/d2renderers/d2svg/appendix/testdata/tooltip_wider_than_diagram/sketch.exp.svg new file mode 100644 index 000000000..b2b23e46f --- /dev/null +++ b/d2renderers/d2svg/appendix/testdata/tooltip_wider_than_diagram/sketch.exp.svg @@ -0,0 +1,45 @@ + +x1Total abstinence is easier than perfect moderationy2Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS! + + +1Total abstinence is easier than perfect moderation +2Gee, I feel kind of LIGHT in the head now,knowing I can't make my satellite dish PAYMENTS! + \ No newline at end of file diff --git a/d2renderers/d2svg/class.go b/d2renderers/d2svg/class.go index af71ac1af..85205db32 100644 --- a/d2renderers/d2svg/class.go +++ b/d2renderers/d2svg/class.go @@ -8,11 +8,12 @@ import ( "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/d2/lib/svg" ) -func classHeader(box *geo.Box, text string, textWidth, textHeight, fontSize float64) string { - str := fmt.Sprintf(``, - box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height) +func classHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string { + str := fmt.Sprintf(``, + box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill) if text != "" { tl := label.InsideMiddleCenter.GetPointOnBox( @@ -23,79 +24,60 @@ func classHeader(box *geo.Box, text string, textWidth, textHeight, fontSize floa ) str += fmt.Sprintf(`%s`, - // TODO use monospace font - "text", + "text-mono", tl.X+textWidth/2, tl.Y+textHeight*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", + fmt.Sprintf(`text-anchor:%s;font-size:%vpx;fill:%s`, "middle", 4+fontSize, - "white", + shape.Stroke, ), - escapeText(text), + svg.EscapeText(text), ) } return str } -const ( - prefixPadding = 10 - prefixWidth = 20 - typePadding = 20 -) - -func classRow(box *geo.Box, prefix, nameText, typeText string, fontSize float64) string { +func classRow(shape d2target.Shape, box *geo.Box, prefix, nameText, typeText string, fontSize float64) string { // Row is made up of prefix, name, and type // e.g. | + firstName string | prefixTL := label.InsideMiddleLeft.GetPointOnBox( box, - prefixPadding, + d2target.PrefixPadding, box.Width, fontSize, ) typeTR := label.InsideMiddleRight.GetPointOnBox( box, - typePadding, + d2target.TypePadding, 0, fontSize, ) - accentColor := "rgb(13, 50, 178)" return strings.Join([]string{ - fmt.Sprintf(`%s`, + fmt.Sprintf(`%s`, prefixTL.X, prefixTL.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, accentColor), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor), prefix, ), - fmt.Sprintf(`%s`, - prefixTL.X+prefixWidth, + fmt.Sprintf(`%s`, + prefixTL.X+d2target.PrefixWidth, prefixTL.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, "black"), - escapeText(nameText), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.Fill), + svg.EscapeText(nameText), ), - fmt.Sprintf(`%s`, + fmt.Sprintf(`%s`, typeTR.X, typeTR.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "end", fontSize, accentColor), - escapeText(typeText), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "end", fontSize, shape.SecondaryAccentColor), + svg.EscapeText(typeText), ), }, "\n") } -func visibilityToken(visibility string) string { - switch visibility { - case "protected": - return "#" - case "private": - return "-" - default: - return "+" - } -} - func drawClass(writer io.Writer, targetShape d2target.Shape) { fmt.Fprintf(writer, ``, targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape)) @@ -109,14 +91,14 @@ func drawClass(writer io.Writer, targetShape d2target.Shape) { headerBox := geo.NewBox(box.TopLeft, box.Width, 2*rowHeight) fmt.Fprint(writer, - classHeader(headerBox, targetShape.Label, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)), + classHeader(targetShape, headerBox, targetShape.Label, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)), ) rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight) rowBox.TopLeft.Y += headerBox.Height - for _, f := range targetShape.Class.Fields { + for _, f := range targetShape.Fields { fmt.Fprint(writer, - classRow(rowBox, visibilityToken(f.Visibility), f.Name, f.Type, float64(targetShape.FontSize)), + classRow(targetShape, rowBox, f.VisibilityToken(), f.Name, f.Type, float64(targetShape.FontSize)), ) rowBox.TopLeft.Y += rowHeight } @@ -124,11 +106,11 @@ func drawClass(writer io.Writer, targetShape d2target.Shape) { fmt.Fprintf(writer, ``, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, - fmt.Sprintf("stroke-width:1;stroke:%v", targetShape.Stroke)) + fmt.Sprintf("stroke-width:1;stroke:%v", targetShape.Fill)) - for _, m := range targetShape.Class.Methods { + for _, m := range targetShape.Methods { fmt.Fprint(writer, - classRow(rowBox, visibilityToken(m.Visibility), m.Name, m.Return, float64(targetShape.FontSize)), + classRow(targetShape, rowBox, m.VisibilityToken(), m.Name, m.Return, float64(targetShape.FontSize)), ) rowBox.TopLeft.Y += rowHeight } diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 11f083c46..cc735055a 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -5,10 +5,10 @@ package d2svg import ( "bytes" _ "embed" - "encoding/xml" "errors" "fmt" "hash/fnv" + "html" "io" "sort" "strings" @@ -25,32 +25,50 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2latex" + "oss.terrastruct.com/d2/d2renderers/d2sketch" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/color" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/d2/lib/shape" + "oss.terrastruct.com/d2/lib/svg" "oss.terrastruct.com/d2/lib/textmeasure" ) const ( - padding = 100 + DEFAULT_PADDING = 100 MIN_ARROWHEAD_STROKE_WIDTH = 2 threeDeeOffset = 15 + + appendixIconRadius = 16 ) var multipleOffset = geo.NewVector(10, -10) +//go:embed tooltip.svg +var TooltipIcon string + +//go:embed link.svg +var LinkIcon string + //go:embed style.css var styleCSS string +//go:embed sketchstyle.css +var sketchStyleCSS string + //go:embed github-markdown.css var mdCSS string -func setViewbox(writer io.Writer, diagram *d2target.Diagram) (width int, height int) { +type RenderOpts struct { + Pad int + Sketch bool +} + +func setViewbox(writer io.Writer, diagram *d2target.Diagram, pad int) (width int, height int) { tl, br := diagram.BoundingBox() - w := br.X - tl.X + padding*2 - h := br.Y - tl.Y + padding*2 + w := br.X - tl.X + pad*2 + h := br.Y - tl.Y + pad*2 // TODO minify // TODO background stuff. e.g. dotted, grid, colors @@ -58,7 +76,7 @@ func setViewbox(writer io.Writer, diagram *d2target.Diagram) (width int, height `, w, h, tl.X-padding, tl.Y-padding, w, h) +width="%d" height="%d" viewBox="%d %d %d %d">`, w, h, tl.X-pad, tl.Y-pad, w, h) return w, h } @@ -346,8 +364,8 @@ func makeLabelMask(labelTL *geo.Point, width, height int) string { ) } -func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape) (labelMask string) { - fmt.Fprintf(writer, ``, escapeText(connection.ID)) +func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, _ error) { + fmt.Fprintf(writer, ``, svg.EscapeText(connection.ID)) var markerStart string if connection.SrcArrow != d2target.NoArrowhead { id := arrowheadMarkerID(false, connection) @@ -383,43 +401,26 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co labelTL.Y = math.Round(labelTL.Y) if label.Position(connection.LabelPosition).IsOnEdge() { - strokeWidth := float64(connection.StrokeWidth) - tl, br := geo.Route(connection.Route).GetBoundingBox() - tl.X -= strokeWidth - tl.Y -= strokeWidth - br.X += strokeWidth - br.Y += strokeWidth - if connection.SrcArrow != d2target.NoArrowhead { - width, height := arrowheadDimensions(connection.SrcArrow, strokeWidth) - tl.X -= width - tl.Y -= height - br.X += width - br.Y += height - } - if connection.DstArrow != d2target.NoArrowhead { - width, height := arrowheadDimensions(connection.DstArrow, strokeWidth) - tl.X -= width - tl.Y -= height - br.X += width - br.Y += height - } - - tl.X = math.Min(tl.X, labelTL.X) - tl.Y = math.Min(tl.Y, labelTL.Y) - br.X = math.Max(br.X, labelTL.X+float64(connection.LabelWidth)) - br.Y = math.Max(br.Y, labelTL.Y+float64(connection.LabelHeight)) - labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight) } } - fmt.Fprintf(writer, ``, - pathData(connection, idToShape), - connectionStyle(connection), + path := pathData(connection, idToShape) + attrs := fmt.Sprintf(`%s%smask="url(#%s)"`, markerStart, markerEnd, labelMaskID, ) + if sketchRunner != nil { + out, err := d2sketch.Connection(sketchRunner, connection, path, attrs) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + fmt.Fprintf(writer, ``, + path, connectionStyle(connection), attrs) + } if connection.Label != "" { fontClass := "text" @@ -432,6 +433,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) @@ -439,7 +445,7 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co fontClass, x, y, textStyle, - renderText(connection.Label, x, float64(connection.LabelHeight)), + RenderText(connection.Label, x, float64(connection.LabelHeight)), ) } @@ -475,7 +481,7 @@ func renderArrowheadLabel(connection d2target.Connection, text string, position, return fmt.Sprintf(`%s`, x, y, textStyle, - renderText(text, x, height), + RenderText(text, x, height), ) } @@ -538,7 +544,7 @@ func render3dRect(targetShape d2target.Shape) string { strings.Join(borderSegments, " "), borderStyle) // create mask from border stroke, to cut away from the shape fills - maskID := fmt.Sprintf("border-mask-%v", escapeText(targetShape.ID)) + maskID := fmt.Sprintf("border-mask-%v", svg.EscapeText(targetShape.ID)) borderMask := strings.Join([]string{ fmt.Sprintf(``, maskID, targetShape.Pos.X, targetShape.Pos.Y-threeDeeOffset, targetShape.Width+threeDeeOffset, targetShape.Height+threeDeeOffset, @@ -584,8 +590,13 @@ func render3dRect(targetShape d2target.Shape) string { return borderMask + mainRect + renderedSides + renderedBorder } -func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, err error) { - fmt.Fprintf(writer, ``, escapeText(targetShape.ID)) +func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, err error) { + closingTag := "" + if targetShape.Link != "" { + fmt.Fprintf(writer, ``, targetShape.Link) + closingTag += "" + } + fmt.Fprintf(writer, ``, svg.EscapeText(targetShape.ID)) tl := geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)) width := float64(targetShape.Width) height := float64(targetShape.Height) @@ -620,22 +631,48 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, switch targetShape.Type { case d2target.ShapeClass: - drawClass(writer, targetShape) - fmt.Fprintf(writer, ``) + if sketchRunner != nil { + out, err := d2sketch.Class(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + drawClass(writer, targetShape) + } + fmt.Fprintf(writer, ``) + fmt.Fprintf(writer, closingTag) return labelMask, nil case d2target.ShapeSQLTable: - drawTable(writer, targetShape) - fmt.Fprintf(writer, ``) + if sketchRunner != nil { + out, err := d2sketch.Table(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + drawTable(writer, targetShape) + } + fmt.Fprintf(writer, ``) + fmt.Fprintf(writer, closingTag) return labelMask, nil case d2target.ShapeOval: if targetShape.Multiple { fmt.Fprint(writer, renderOval(multipleTL, width, height, style)) } - fmt.Fprint(writer, renderOval(tl, width, height, style)) + if sketchRunner != nil { + out, err := d2sketch.Oval(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + fmt.Fprint(writer, renderOval(tl, width, height, style)) + } case d2target.ShapeImage: fmt.Fprintf(writer, ``, - targetShape.Icon.String(), + html.EscapeString(targetShape.Icon.String()), targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) // TODO should standardize "" to rectangle @@ -647,8 +684,16 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, fmt.Fprintf(writer, ``, targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style) } - fmt.Fprintf(writer, ``, - targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) + if sketchRunner != nil { + out, err := d2sketch.Rect(sketchRunner, targetShape) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + fmt.Fprintf(writer, ``, + targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) + } } case d2target.ShapeText, d2target.ShapeCode: default: @@ -659,11 +704,20 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, } } - for _, pathData := range s.GetSVGPathData() { - fmt.Fprintf(writer, ``, pathData, style) + if sketchRunner != nil { + out, err := d2sketch.Paths(sketchRunner, targetShape, s.GetSVGPathData()) + if err != nil { + return "", err + } + fmt.Fprintf(writer, out) + } else { + for _, pathData := range s.GetSVGPathData() { + fmt.Fprintf(writer, ``, pathData, style) + } } } + // Closes the class=shape fmt.Fprintf(writer, ``) if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage { @@ -679,7 +733,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize)) fmt.Fprintf(writer, ``, - targetShape.Icon.String(), + html.EscapeString(targetShape.Icon.String()), tl.X, tl.Y, iconSize, @@ -703,9 +757,11 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, } else if targetShape.Italic { fontClass += "-italic" } + if targetShape.Underline { + fontClass += " text-underline" + } - switch targetShape.Type { - case d2target.ShapeCode: + if targetShape.Type == d2target.ShapeCode { lexer := lexers.Get(targetShape.Language) if lexer == nil { return labelMask, fmt.Errorf("code snippet lexer for %s not found", targetShape.Language) @@ -746,29 +802,36 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, fmt.Fprint(writer, "") } fmt.Fprintf(writer, "") - case d2target.ShapeText: - if targetShape.Language == "latex" { - render, err := d2latex.Render(targetShape.Label) - if err != nil { - return labelMask, err - } - fmt.Fprintf(writer, ``, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity) - fmt.Fprint(writer, render) - fmt.Fprintf(writer, "") - } else { - render, err := textmeasure.RenderMarkdown(targetShape.Label) - if err != nil { - return labelMask, err - } - fmt.Fprintf(writer, ``, - box.TopLeft.X, box.TopLeft.Y, targetShape.Width, targetShape.Height, - ) - // we need the self closing form in this svg/xhtml context - render = strings.ReplaceAll(render, "
", "
") - fmt.Fprintf(writer, `
%v
`, render) - fmt.Fprint(writer, `
`) + } else if targetShape.Type == d2target.ShapeText && targetShape.Language == "latex" { + render, err := d2latex.Render(targetShape.Label) + if err != nil { + return labelMask, err } - default: + fmt.Fprintf(writer, ``, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity) + fmt.Fprint(writer, render) + fmt.Fprintf(writer, "") + } else if targetShape.Type == d2target.ShapeText && targetShape.Language != "" { + render, err := textmeasure.RenderMarkdown(targetShape.Label) + if err != nil { + return labelMask, err + } + fmt.Fprintf(writer, ``, + box.TopLeft.X, box.TopLeft.Y, targetShape.Width, targetShape.Height, + ) + // we need the self closing form in this svg/xhtml context + render = strings.ReplaceAll(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, `
`) + } else { fontColor := "black" if targetShape.Color != "" { fontColor = targetShape.Color @@ -781,26 +844,40 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) (labelMask string, fontClass, x, y, textStyle, - renderText(targetShape.Label, x, float64(targetShape.LabelHeight)), + RenderText(targetShape.Label, x, float64(targetShape.LabelHeight)), ) if targetShape.Blend { labelMask = makeLabelMask(labelTL, targetShape.LabelWidth, targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING) } } } - fmt.Fprintf(writer, ``) + + rightPadForTooltip := 0 + if targetShape.Tooltip != "" { + rightPadForTooltip = 2 * appendixIconRadius + fmt.Fprintf(writer, `%s`, + targetShape.Pos.X+targetShape.Width-appendixIconRadius, + targetShape.Pos.Y-appendixIconRadius, + TooltipIcon, + ) + fmt.Fprintf(writer, `%s`, targetShape.Tooltip) + } + + if targetShape.Link != "" { + fmt.Fprintf(writer, `%s`, + targetShape.Pos.X+targetShape.Width-appendixIconRadius-rightPadForTooltip, + targetShape.Pos.Y-appendixIconRadius, + LinkIcon, + ) + } + + fmt.Fprintf(writer, closingTag) return labelMask, nil } -func escapeText(text string) string { - buf := new(bytes.Buffer) - _ = xml.EscapeText(buf, []byte(text)) - return buf.String() -} - -func renderText(text string, x, height float64) string { +func RenderText(text string, x, height float64) string { if !strings.Contains(text, "\n") { - return escapeText(text) + return svg.EscapeText(text) } rendered := []string{} lines := strings.Split(text, "\n") @@ -809,7 +886,7 @@ func renderText(text string, x, height float64) string { if i == 0 { dy = 0 } - escaped := escapeText(line) + escaped := svg.EscapeText(line) if escaped == "" { // if there are multiple newlines in a row we still need text for the tspan to render escaped = " " @@ -822,12 +899,20 @@ func renderText(text string, x, height float64) string { func shapeStyle(shape d2target.Shape) string { out := "" - out += fmt.Sprintf(`fill:%s;`, shape.Fill) - out += fmt.Sprintf(`stroke:%s;`, shape.Stroke) + if shape.Type == d2target.ShapeSQLTable || shape.Type == d2target.ShapeClass { + // Fill is used for header fill in these types + // This fill property is just background of rows + out += fmt.Sprintf(`fill:%s;`, shape.Stroke) + // Stroke (border) of these shapes should match the header fill + out += fmt.Sprintf(`stroke:%s;`, shape.Fill) + } else { + out += fmt.Sprintf(`fill:%s;`, shape.Fill) + out += fmt.Sprintf(`stroke:%s;`, shape.Stroke) + } out += fmt.Sprintf(`opacity:%f;`, shape.Opacity) out += fmt.Sprintf(`stroke-width:%d;`, shape.StrokeWidth) if shape.StrokeDash != 0 { - dashSize, gapSize := getStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash) + dashSize, gapSize := svg.GetStrokeDashAttributes(float64(shape.StrokeWidth), shape.StrokeDash) out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize) } @@ -841,27 +926,20 @@ func connectionStyle(connection d2target.Connection) string { out += fmt.Sprintf(`opacity:%f;`, connection.Opacity) out += fmt.Sprintf(`stroke-width:%d;`, connection.StrokeWidth) if connection.StrokeDash != 0 { - dashSize, gapSize := getStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash) + dashSize, gapSize := svg.GetStrokeDashAttributes(float64(connection.StrokeWidth), connection.StrokeDash) out += fmt.Sprintf(`stroke-dasharray:%f,%f;`, dashSize, gapSize) } return out } -func getStrokeDashAttributes(strokeWidth, dashGapSize float64) (float64, float64) { - // as the stroke width gets thicker, the dash gap gets smaller - scale := math.Log10(-0.6*strokeWidth+10.6)*0.5 + 0.5 - scaledDashSize := strokeWidth * dashGapSize - scaledGapSize := scale * scaledDashSize - return scaledDashSize, scaledGapSize -} - -func embedFonts(buf *bytes.Buffer) { +func embedFonts(buf *bytes.Buffer, fontFamily *d2fonts.FontFamily) { content := buf.String() buf.WriteString(``, styleCSS)) +`, styleCSS, styleCSS2)) hasMarkdown := false for _, s := range diagram.Shapes { @@ -969,6 +1092,9 @@ func Render(diagram *d2target.Diagram) ([]byte, error) { if hasMarkdown { fmt.Fprintf(buf, ``, mdCSS) } + if sketchRunner != nil { + fmt.Fprintf(buf, d2sketch.DefineFillPattern()) + } // only define shadow filter if a shape uses it for _, s := range diagram.Shapes { @@ -1003,37 +1129,38 @@ func Render(diagram *d2target.Diagram) ([]byte, error) { markers := map[string]struct{}{} for _, obj := range allObjects { if c, is := obj.(d2target.Connection); is { - labelMask := drawConnection(buf, labelMaskID, c, markers, idToShape) + labelMask, err := drawConnection(buf, labelMaskID, c, markers, idToShape, sketchRunner) + if err != nil { + return nil, err + } if labelMask != "" { labelMasks = append(labelMasks, labelMask) } } else if s, is := obj.(d2target.Shape); is { - labelMask, err := drawShape(buf, s) + labelMask, err := drawShape(buf, s, sketchRunner) if err != nil { return nil, err } else if labelMask != "" { 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, -pad, -pad, w, h, + ), + fmt.Sprintf(``, + -pad, -pad, w, h, + ), + strings.Join(labelMasks, "\n"), + ``, + }, "\n")) - embedFonts(buf) + embedFonts(buf, diagram.FontFamily) buf.WriteString(`
`) return buf.Bytes(), nil diff --git a/d2renderers/d2svg/github-markdown.css b/d2renderers/d2svg/github-markdown.css index a1927dd09..dc75ed64f 100644 --- a/d2renderers/d2svg/github-markdown.css +++ b/d2renderers/d2svg/github-markdown.css @@ -257,6 +257,7 @@ margin-bottom: 16px; font-weight: 600; line-height: 1.25; + font-family: "font-regular"; } .md h2 { diff --git a/d2renderers/d2svg/link.svg b/d2renderers/d2svg/link.svg new file mode 100644 index 000000000..ff5bc3fb1 --- /dev/null +++ b/d2renderers/d2svg/link.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/d2renderers/d2svg/sketchstyle.css b/d2renderers/d2svg/sketchstyle.css new file mode 100644 index 000000000..33654aba2 --- /dev/null +++ b/d2renderers/d2svg/sketchstyle.css @@ -0,0 +1,4 @@ +.sketch-overlay { + fill: url(#streaks); + mix-blend-mode: overlay; +} diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go index ce9e79536..46bdef37c 100644 --- a/d2renderers/d2svg/table.go +++ b/d2renderers/d2svg/table.go @@ -3,23 +3,24 @@ package d2svg import ( "fmt" "io" - "math" "strings" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/d2/lib/svg" + "oss.terrastruct.com/util-go/go2" ) -func tableHeader(box *geo.Box, text string, textWidth, textHeight, fontSize float64) string { +func tableHeader(shape d2target.Shape, box *geo.Box, text string, textWidth, textHeight, fontSize float64) string { str := fmt.Sprintf(``, - box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, "#0a0f25") + box.TopLeft.X, box.TopLeft.Y, box.Width, box.Height, shape.Fill) if text != "" { tl := label.InsideMiddleLeft.GetPointOnBox( box, - 20, - textWidth, + float64(d2target.HeaderPadding), + float64(shape.Width), textHeight, ) @@ -30,73 +31,54 @@ func tableHeader(box *geo.Box, text string, textWidth, textHeight, fontSize floa fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", 4+fontSize, - "white", + shape.Stroke, ), - escapeText(text), + svg.EscapeText(text), ) } return str } -func tableRow(box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth float64) string { +func tableRow(shape d2target.Shape, box *geo.Box, nameText, typeText, constraintText string, fontSize, longestNameWidth float64) string { // Row is made up of name, type, and constraint // e.g. | diagram int FK | nameTL := label.InsideMiddleLeft.GetPointOnBox( box, - prefixPadding, + d2target.NamePadding, box.Width, fontSize, ) constraintTR := label.InsideMiddleRight.GetPointOnBox( box, - typePadding, + d2target.TypePadding, 0, fontSize, ) - // TODO theme based - primaryColor := "rgb(13, 50, 178)" - accentColor := "rgb(74, 111, 243)" - neutralColor := "rgb(103, 108, 126)" - return strings.Join([]string{ fmt.Sprintf(`%s`, nameTL.X, nameTL.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, primaryColor), - escapeText(nameText), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.PrimaryAccentColor), + svg.EscapeText(nameText), ), - // TODO light font fmt.Sprintf(`%s`, - nameTL.X+longestNameWidth, + nameTL.X+longestNameWidth+2*d2target.NamePadding, nameTL.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, neutralColor), - escapeText(typeText), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s", "start", fontSize, shape.NeutralAccentColor), + svg.EscapeText(typeText), ), fmt.Sprintf(`%s`, constraintTR.X, constraintTR.Y+fontSize*3/4, - fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", fontSize, accentColor), + fmt.Sprintf("text-anchor:%s;font-size:%vpx;fill:%s;letter-spacing:2px;", "end", fontSize, shape.SecondaryAccentColor), constraintText, ), }, "\n") } -func constraintAbbr(constraint string) string { - switch constraint { - case "primary_key": - return "PK" - case "foreign_key": - return "FK" - case "unique": - return "UNQ" - default: - return "" - } -} - func drawTable(writer io.Writer, targetShape d2target.Shape) { fmt.Fprintf(writer, ``, targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape)) @@ -110,27 +92,26 @@ func drawTable(writer io.Writer, targetShape d2target.Shape) { headerBox := geo.NewBox(box.TopLeft, box.Width, rowHeight) fmt.Fprint(writer, - tableHeader(headerBox, targetShape.Label, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)), + tableHeader(targetShape, headerBox, targetShape.Label, + float64(targetShape.LabelWidth), float64(targetShape.LabelHeight), float64(targetShape.FontSize)), ) - fontSize := float64(targetShape.FontSize) - var longestNameWidth float64 - for _, f := range targetShape.SQLTable.Columns { - // TODO measure text - longestNameWidth = math.Max(longestNameWidth, float64(len(f.Name))*fontSize*5/9) + var longestNameWidth int + for _, f := range targetShape.Columns { + longestNameWidth = go2.Max(longestNameWidth, f.Name.LabelWidth) } rowBox := geo.NewBox(box.TopLeft.Copy(), box.Width, rowHeight) rowBox.TopLeft.Y += headerBox.Height - for _, f := range targetShape.SQLTable.Columns { + for _, f := range targetShape.Columns { fmt.Fprint(writer, - tableRow(rowBox, f.Name, f.Type, constraintAbbr(f.Constraint), fontSize, longestNameWidth), + tableRow(targetShape, rowBox, f.Name.Label, f.Type.Label, f.ConstraintAbbr(), float64(targetShape.FontSize), float64(longestNameWidth)), ) rowBox.TopLeft.Y += rowHeight - fmt.Fprintf(writer, ``, + fmt.Fprintf(writer, ``, rowBox.TopLeft.X, rowBox.TopLeft.Y, rowBox.TopLeft.X+rowBox.Width, rowBox.TopLeft.Y, + targetShape.Fill, ) } - } diff --git a/d2renderers/d2svg/tooltip.svg b/d2renderers/d2svg/tooltip.svg new file mode 100644 index 000000000..eb29cc2eb --- /dev/null +++ b/d2renderers/d2svg/tooltip.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/d2target/class.go b/d2target/class.go index 8a0a7e71e..f62a3ea43 100644 --- a/d2target/class.go +++ b/d2target/class.go @@ -6,6 +6,11 @@ import ( "oss.terrastruct.com/d2/d2renderers/d2fonts" ) +const ( + PrefixPadding = 10 + PrefixWidth = 20 +) + type Class struct { Fields []ClassField `json:"fields"` Methods []ClassMethod `json:"methods"` @@ -27,6 +32,17 @@ func (cf ClassField) Text() *MText { } } +func (cf ClassField) VisibilityToken() string { + switch cf.Visibility { + case "protected": + return "#" + case "private": + return "-" + default: + return "+" + } +} + type ClassMethod struct { Name string `json:"name"` Return string `json:"return"` @@ -42,3 +58,14 @@ func (cm ClassMethod) Text() *MText { Shape: "class", } } + +func (cm ClassMethod) VisibilityToken() string { + switch cm.Visibility { + case "protected": + return "#" + case "private": + return "-" + default: + return "+" + } +} diff --git a/d2target/d2target.go b/d2target/d2target.go index 41dc4c718..90974c7f1 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -10,6 +10,7 @@ import ( "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" @@ -22,8 +23,9 @@ const ( ) type Diagram struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + FontFamily *d2fonts.FontFamily `json:"fontFamily,omitempty"` Shapes []Shape `json:"shapes"` Connections []Connection `json:"connections"` @@ -144,6 +146,11 @@ type Shape struct { ZIndex int `json:"zIndex"` Level int `json:"level"` + + // These are used for special shapes, sql_table and class + PrimaryAccentColor string `json:"primaryAccentColor,omitempty"` + SecondaryAccentColor string `json:"secondaryAccentColor,omitempty"` + NeutralAccentColor string `json:"neutralAccentColor,omitempty"` } func (s *Shape) SetType(t string) { @@ -207,6 +214,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/d2target/sqltable.go b/d2target/sqltable.go index 3007dd178..8332a34c8 100644 --- a/d2target/sqltable.go +++ b/d2target/sqltable.go @@ -1,9 +1,11 @@ package d2target -import ( - "fmt" +import "oss.terrastruct.com/d2/d2renderers/d2fonts" - "oss.terrastruct.com/d2/d2renderers/d2fonts" +const ( + NamePadding = 10 + TypePadding = 20 + HeaderPadding = 20 ) type SQLTable struct { @@ -11,18 +13,40 @@ type SQLTable struct { } type SQLColumn struct { - Name string `json:"name"` - Type string `json:"type"` + Name Text `json:"name"` + Type Text `json:"type"` Constraint string `json:"constraint"` Reference string `json:"reference"` } -func (c SQLColumn) Text() *MText { - return &MText{ - Text: fmt.Sprintf("%s%s%s%s", c.Name, c.Type, c.Constraint, c.Reference), - FontSize: d2fonts.FONT_SIZE_L, - IsBold: false, - IsItalic: false, - Shape: "sql_table", +func (c SQLColumn) Texts() []*MText { + return []*MText{ + { + Text: c.Name.Label, + FontSize: d2fonts.FONT_SIZE_L, + IsBold: false, + IsItalic: false, + Shape: "sql_table", + }, + { + Text: c.Type.Label, + FontSize: d2fonts.FONT_SIZE_L, + IsBold: false, + IsItalic: false, + Shape: "sql_table", + }, + } +} + +func (c SQLColumn) ConstraintAbbr() string { + switch c.Constraint { + case "primary_key": + return "PK" + case "foreign_key": + return "FK" + case "unique": + return "UNQ" + default: + return "" } } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 784636109..34ef01aba 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 @@ -44,12 +39,6 @@ The simplified D2 flow at a package level looks like: ## Logistics -- **Important**: Contributions to D2 require a CLA. We will never relicense D2, but we - need to retain full copyright for any modifications we might need to make in our - commercial offerings. Please email cla@terrastruct.com with your name and Github - username stating that you agree to [Terrastruct's - CLA](https://terrastruct-site-assets.s3.us-west-1.amazonaws.com/documents/terrastruct_cla.pdf). - You only have to do this the first time you contribute. - D2 uses Issues as TODOs. No auto-closing on staleness. - Branch off `master`. - Prefix pull request titles with a short descriptor of the domain, e.g. `d2renderer: Add diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 7d29525d2..99793ad0e 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -6,14 +6,17 @@ You may install `d2` through any of the following methods. - install.sh - Security - macOS (Homebrew) +- Linux + - Void Linux - Standalone - Manual - PREFIX - From source - Source Release - Windows - - MSYS2 + - Release archives - WSL +- Docker - Coming soon ## install.sh @@ -71,13 +74,30 @@ but that is coming soon. [#315](https://github.com/terrastruct/d2/issues/315) If you're on macOS, you can install with `brew`. ```sh -brew tap terrastruct/d2 brew install d2 ``` > The install script above does this automatically if you have `brew` installed and > are running it on macOS. +You can also install from source with: + +```d2 +brew install d2 --HEAD +``` + +## Linux + +The following distributions have packages for d2: + +### Void Linux + +All supported platforms: + +```sh +xbps-install d2 +``` + ## Standalone We publish standalone release archives for every release on Github. @@ -138,9 +158,11 @@ 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 proper release from source clone the repository and then: +To install a release from source clone the repository and then: ```sh ./ci/release/build.sh --install @@ -149,35 +171,30 @@ To install a proper release from source clone the repository and then: ``` Installing a real release will also install manpages and in the future other assets like -fonts and icons. Furthermore, when installing a non versioned commit, installing a proper -release will ensure that `d2 --version` works correctly by embedding the commit hash into -the `d2` binary. +fonts and icons. Furthermore, when installing a non versioned commit, installing a release +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. So after extracting a release, you'll have to manually put the d2 -binary into your `$PATH` or add the `bin` directory of the release into your `$PATH`. +### Release archives -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 proper +[Cygwin](https://www.cygwin.com/)). MSYS2 allows `install.sh` to work, enables automatic installation of our standalone releases via `make install` and makes the manpage accessible via `man d2`. @@ -192,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:v0.1.2 --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..b08fe380d Binary files /dev/null and b/docs/assets/playground_button.png differ diff --git a/docs/examples/lib/1-d2lib/d2lib.go b/docs/examples/lib/1-d2lib/d2lib.go index a28b97f07..3b0114c88 100644 --- a/docs/examples/lib/1-d2lib/d2lib.go +++ b/docs/examples/lib/1-d2lib/d2lib.go @@ -20,6 +20,8 @@ func main() { Ruler: ruler, ThemeID: d2themescatalog.GrapeSoda.ID, }) - out, _ := d2svg.Render(diagram) + out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{ + Pad: d2svg.DEFAULT_PADDING, + }) _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) } diff --git a/docs/examples/lib/3-lowlevel/lowlevel.go b/docs/examples/lib/3-lowlevel/lowlevel.go index e8f7a70a8..1ad6443b0 100644 --- a/docs/examples/lib/3-lowlevel/lowlevel.go +++ b/docs/examples/lib/3-lowlevel/lowlevel.go @@ -18,9 +18,11 @@ import ( func main() { graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil) ruler, _ := textmeasure.NewRuler() - _ = graph.SetDimensions(nil, ruler) + _ = graph.SetDimensions(nil, ruler, nil) _ = d2dagrelayout.Layout(context.Background(), graph) - diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID) - out, _ := d2svg.Render(diagram) + diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID, nil) + out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{ + Pad: d2svg.DEFAULT_PADDING, + }) _ = ioutil.WriteFile(filepath.Join("out.svg"), out, 0600) } diff --git a/docs/examples/lib/README.md b/docs/examples/lib/README.md index 979e9af7e..4ef1efcef 100644 --- a/docs/examples/lib/README.md +++ b/docs/examples/lib/README.md @@ -15,7 +15,7 @@ D2 is built to be hackable -- the language has an API built on top of it to make programmatically. Modifying the previous example, this example demonstrates how -[d2oracle](../../../d2oracle) can be used to create a new shape, style it programatically +[d2oracle](../../../d2oracle) can be used to create a new shape, style it programmatically and then output the modified d2 script. This makes it easy to build functionality on top of D2. Terrastruct uses the @@ -27,4 +27,4 @@ visual interface. `d2lib` from the first example is just a wrapper around the lower level APIs. They can be used directly and this example demonstrates such usage. -This shouldn't be necessary for most usecases. +This shouldn't be necessary for most use cases. diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index b77a4cc59..994770426 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -17,9 +17,12 @@ import ( "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/util-go/diff" + "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2elklayout" + "oss.terrastruct.com/d2/d2layouts/d2near" + "oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2renderers/d2svg" "oss.terrastruct.com/d2/d2target" @@ -88,6 +91,28 @@ func runa(t *testing.T, tcs []testCase) { } } +// serde exercises serializing and deserializing the graph +// We want to run all the steps leading up to serialization in the course of regular layout +func serde(t *testing.T, tc testCase, ruler *textmeasure.Ruler) { + ctx := context.Background() + ctx = log.WithTB(ctx, t, nil) + g, err := d2compiler.Compile("", strings.NewReader(tc.script), &d2compiler.CompileOptions{ + UTF16: false, + }) + tassert.Nil(t, err) + if len(g.Objects) > 0 { + err = g.SetDimensions(nil, ruler, nil) + tassert.Nil(t, err) + d2near.WithoutConstantNears(ctx, g) + d2sequence.WithoutSequenceDiagrams(ctx, g) + } + b, err := d2graph.SerializeGraph(g) + tassert.Nil(t, err) + var newG d2graph.Graph + err = d2graph.DeserializeGraph(b, &newG) + tassert.Nil(t, err) +} + func run(t *testing.T, tc testCase) { ctx := context.Background() ctx = log.WithTB(ctx, t, nil) @@ -98,6 +123,8 @@ func run(t *testing.T, tc testCase) { return } + serde(t, tc, ruler) + layoutsTested := []string{"dagre", "elk"} for _, layoutName := range layoutsTested { @@ -125,14 +152,21 @@ func run(t *testing.T, tc testCase) { dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestE2E/"), layoutName) pathGotSVG := filepath.Join(dataPath, "sketch.got.svg") - svgBytes, err := d2svg.Render(diagram) + svgBytes, err := d2svg.Render(diagram, &d2svg.RenderOpts{ + Pad: d2svg.DEFAULT_PADDING, + }) assert.Success(t, err) err = os.MkdirAll(dataPath, 0755) assert.Success(t, err) err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600) assert.Success(t, err) - defer os.Remove(pathGotSVG) + // if running from e2ereport.sh, we want to keep .got.svg on a failure + forReport := os.Getenv("E2E_REPORT") != "" + if !forReport { + defer os.Remove(pathGotSVG) + } + // Check that it's valid SVG var xmlParsed interface{} err = xml.Unmarshal(svgBytes, &xmlParsed) assert.Success(t, err) @@ -143,6 +177,9 @@ func run(t *testing.T, tc testCase) { err = diff.Testdata(filepath.Join(dataPath, "sketch"), ".svg", svgBytes) assert.Success(t, err) } + if forReport { + os.Remove(pathGotSVG) + } } } diff --git a/e2etests/regression_test.go b/e2etests/regression_test.go index a7eef68c1..aa8ca3b37 100644 --- a/e2etests/regression_test.go +++ b/e2etests/regression_test.go @@ -34,6 +34,255 @@ A->B`, script: `shape: sequence_diagram b.1 -> b.1 b.1 -> b.1`, + }, { + name: "sequence_diagram_no_message", + script: `shape: sequence_diagram +a: A +b: B`, + }, + { + name: "sequence_diagram_name_crash", + script: `foo: { + shape: sequence_diagram + a -> b +} +foobar: { + shape: sequence_diagram + c -> d +} +foo -> foobar`, + }, + { + name: "sql_table_overflow", + script: ` +table: sql_table_overflow { + shape: sql_table + short: loooooooooooooooooooong + loooooooooooooooooooong: short +} +table_constrained: sql_table_constrained_overflow { + shape: sql_table + short: loooooooooooooooooooong { + constraint: unique + } + loooooooooooooooooooong: short { + constraint: foreign_key + } +} +`, + }, + { + name: "elk_alignment", + script: ` +direction: down + +build_workflow: lambda-build.yaml { + + push: Push to main branch { + style.font-size: 25 + } + + GHA: GitHub Actions { + style.font-size: 25 + } + + S3.style.font-size: 25 + Terraform.style.font-size: 25 + AWS.style.font-size: 25 + + push -> GHA: Triggers { + style.font-size: 20 + } + + GHA -> S3: Builds zip and pushes it { + style.font-size: 20 + } + + S3 <-> Terraform: Pulls zip to deploy { + style.font-size: 20 + } + + Terraform -> AWS: Changes live lambdas { + style.font-size: 20 + } +} + +deploy_workflow: lambda-deploy.yaml { + + manual: Manual Trigger { + style.font-size: 25 + } + + GHA: GitHub Actions { + style.font-size: 25 + } + + AWS.style.font-size: 25 + + Manual -> GHA: Launches { + style.font-size: 20 + } + + GHA -> AWS: Builds zip\npushes them to S3.\n\nDeploys lambdas\nusing Terraform { + style.font-size: 20 + } +} + +apollo_workflow: apollo-deploy.yaml { + + apollo: Apollo Repo { + style.font-size: 25 + } + + GHA: GitHub Actions { + style.font-size: 25 + } + + AWS.style.font-size: 25 + + apollo -> GHA: Triggered manually/push to master test test test test test test test { + style.font-size: 20 + } + + GHA -> AWS: test { + style.font-size: 20 + } +} +`, + }, + { + name: "dagre_edge_label_spacing", + script: `direction: right + +build_workflow: lambda-build.yaml { + + push: Push to main branch { + style.font-size: 25 + } + GHA: GitHub Actions { + style.font-size: 25 + } + S3.style.font-size: 25 + Terraform.style.font-size: 25 + AWS.style.font-size: 25 + + push -> GHA: Triggers + GHA -> S3: Builds zip & pushes it + S3 <-> Terraform: Pulls zip to deploy + Terraform -> AWS: Changes the live lambdas +} +`, + }, + { + name: "query_param_escape", + script: `my network: { + icon: https://icons.terrastruct.com/infra/019-network.svg?fuga=1&hoge +} +`, + }, + { + name: "elk_order", + script: `queue: { + shape: queue + label: '' + + M0 + M1 + M2 + M3 + M4 + M5 + M6 +} + +m0_desc: |md + Oldest message +| +m0_desc -> queue.M0 + +m2_desc: |md + Offset +| +m2_desc -> queue.M2 + +m5_desc: |md + Last message +| +m5_desc -> queue.M5 + +m6_desc: |md + Next message will be\ + inserted here +| +m6_desc -> queue.M6 +`, + }, + { + name: "unnamed_class_table_code", + script: ` + +class -> users -> code + +class: "" { + shape: class + -num: int + -timeout: int + -pid + + +getStatus(): Enum + +getJobs(): "Job[]" + +setTimeout(seconds int) +} + +users: "" { + shape: sql_table + id: int + name: string + email: string + password: string + last_login: datetime +} + +code: |go + a := 5 + b := a + 7 + fmt.Printf("%d", b) +| +`, + }, + { + name: "elk_img_empty_label_panic", + script: ` +img: { + label: "" + shape: image + icon: https://icons.terrastruct.com/infra/019-network.svg +} +ico: { + label: "" + icon: https://icons.terrastruct.com/infra/019-network.svg +} +`, + }, + { + name: "only_header_class_table", + script: ` + +class: RefreshAuthorizationPolicyProtocolServerSideTranslatorProtocolBuffer { + shape: class +} + +table: RefreshAuthorizationPolicyCache { + shape: sql_table +} + +table with short col: RefreshAuthorizationPolicyCache { + shape: sql_table + ok +} + +class -> table -> table with short col +`, }, } diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 29eeb130b..6590d295e 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -840,7 +840,7 @@ a -> md -> b name: string email: string password: string - last_login: datetime + last_login: datetime { constraint: primary_key } } products: { @@ -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,245 @@ 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" +} +`, + }, + { + name: "overlapping_image_container_labels", + script: ` +root: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg +} + +root -> container.root + +container: { + root: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + + left: { + root: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + inner: { + left: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + right: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + } + root -> inner.left: { + label: to inner left + } + root -> inner.right: { + label: to inner right + } + } + + right: { + root: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + inner: { + left: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + right: { + shape: image + icon: https://icons.terrastruct.com/essentials/004-picture.svg + } + } + root -> inner.left: { + label: to inner left + } + root -> inner.right: { + label: to inner right + } + } + + root -> left.root: { + label: to left container root + } + + root -> right.root: { + label: to right container root + } +} +`, + }, + { + name: "constant_near_stress", + script: `x -> y +The top of the mountain: { shape: text; near: top-center } +Joe: { shape: person; near: center-left } +Donald: { shape: person; near: center-right } +bottom: |md + # Cats, no less liquid than their shadows, offer no angles to the wind. + + If we can't fix it, it ain't broke. + + Dieters live life in the fasting lane. +| { near: bottom-center } +i am top left: { shape: text; near: top-left } +i am top right: { shape: text; near: top-right } +i am bottom left: { shape: text; near: bottom-left } +i am bottom right: { shape: text; near: bottom-right } +`, + }, + { + name: "constant_near_title", + script: `title: |md + # A winning strategy +| { near: top-center } + +poll the people -> results +results -> unfavorable -> poll the people +results -> favorable -> will of the people +`, + }, + { + name: "text_font_sizes", + script: `bear: { shape: text; style.font-size: 22; style.bold: true } +mama bear: { shape: text; style.font-size: 28; style.italic: true } +papa bear: { shape: text; style.font-size: 32; style.underline: true } +mama bear -> bear +papa bear -> bear +`, + }, + { + name: "tooltips", + script: `x: { tooltip: Total abstinence is easier than perfect moderation } +y: { tooltip: Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS! } +x -> y +`, + }, + { + name: "links", + script: `x: { link: https://d2lang.com } + y: { link: https://terrastruct.com; tooltip: Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS! } +x -> y +`, + }, + { + name: "unnamed_only_width", + script: ` + +class -> users -> code -> package -> no width + +class: "" { + shape: class + -num: int + -timeout: int + -pid + + +getStatus(): Enum + +getJobs(): "Job[]" + +setTimeout(seconds int) +} + +users: "" { + shape: sql_table + id: int + name: string + email: string + password: string + last_login: datetime +} + +code: |go + a := 5 + b := a + 7 + fmt.Printf("%d", b) +| + +package: "" { shape: package } +no width: "" + + +class.width: 512 +users.width: 512 +code.width: 512 +package.width: 512 +`, + }, + { + name: "unnamed_only_height", + script: ` + +class -> users -> code -> package -> no height + +class: "" { + shape: class + -num: int + -timeout: int + -pid + + +getStatus(): Enum + +getJobs(): "Job[]" + +setTimeout(seconds int) +} + +users: "" { + shape: sql_table + id: int + name: string + email: string + password: string + last_login: datetime +} + +code: |go + a := 5 + b := a + 7 + fmt.Printf("%d", b) +| + +package: "" { shape: package } +no height: "" + + +class.height: 512 +users.height: 512 +code.height: 512 +package.height: 512 `, }, } diff --git a/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/board.exp.json b/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/board.exp.json new file mode 100644 index 000000000..0ba8aaac5 --- /dev/null +++ b/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/board.exp.json @@ -0,0 +1,440 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "build_workflow", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 2148, + "height": 237, + "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": "lambda-build.yaml", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 226, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "build_workflow.push", + "type": "", + "pos": { + "x": 105, + "y": 50 + }, + "width": 330, + "height": 137, + "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": "Push to main branch", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 230, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.GHA", + "type": "", + "pos": { + "x": 644, + "y": 50 + }, + "width": 269, + "height": 137, + "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": "GitHub Actions", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 169, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.S3", + "type": "", + "pos": { + "x": 1122, + "y": 50 + }, + "width": 131, + "height": 137, + "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": "S3", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 31, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.Terraform", + "type": "", + "pos": { + "x": 1462, + "y": 50 + }, + "width": 218, + "height": 137, + "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": "Terraform", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 118, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.AWS", + "type": "", + "pos": { + "x": 1889, + "y": 50 + }, + "width": 155, + "height": 137, + "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": "AWS", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "build_workflow.(push -> GHA)[0]", + "src": "build_workflow.push", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.GHA", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Triggers", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 435.5, + "y": 118.5 + }, + { + "x": 518.3, + "y": 118.5 + }, + { + "x": 559.9, + "y": 118.5 + }, + { + "x": 643.5, + "y": 118.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(GHA -> S3)[0]", + "src": "build_workflow.GHA", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.S3", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Builds zip & pushes it", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 138, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 913.5, + "y": 118.5 + }, + { + "x": 996.3, + "y": 118.5 + }, + { + "x": 1037.9, + "y": 118.5 + }, + { + "x": 1121.5, + "y": 118.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(S3 <-> Terraform)[0]", + "src": "build_workflow.S3", + "srcArrow": "triangle", + "srcLabel": "", + "dst": "build_workflow.Terraform", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Pulls zip to deploy", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 119, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1253.5, + "y": 118.5 + }, + { + "x": 1336.3, + "y": 118.5 + }, + { + "x": 1377.9, + "y": 118.5 + }, + { + "x": 1461.5, + "y": 118.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(Terraform -> AWS)[0]", + "src": "build_workflow.Terraform", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.AWS", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Changes the live lambdas", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 169, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1680.5, + "y": 118.5 + }, + { + "x": 1763.3, + "y": 118.5 + }, + { + "x": 1804.9, + "y": 118.5 + }, + { + "x": 1888.5, + "y": 118.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/sketch.exp.svg b/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/sketch.exp.svg new file mode 100644 index 000000000..6da3ce848 --- /dev/null +++ b/e2etests/testdata/regression/dagre_edge_label_spacing/dagre/sketch.exp.svg @@ -0,0 +1,48 @@ + +lambda-build.yamlPush to main branchGitHub ActionsS3TerraformAWS TriggersBuilds zip & pushes it Pulls zip to deployChanges the live lambdas + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/dagre_edge_label_spacing/elk/board.exp.json b/e2etests/testdata/regression/dagre_edge_label_spacing/elk/board.exp.json new file mode 100644 index 000000000..363039200 --- /dev/null +++ b/e2etests/testdata/regression/dagre_edge_label_spacing/elk/board.exp.json @@ -0,0 +1,404 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "build_workflow", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 1893, + "height": 287, + "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": "lambda-build.yaml", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 226, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "build_workflow.push", + "type": "", + "pos": { + "x": 87, + "y": 87 + }, + "width": 330, + "height": 137, + "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": "Push to main branch", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 230, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.GHA", + "type": "", + "pos": { + "x": 511, + "y": 87 + }, + "width": 269, + "height": 137, + "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": "GitHub Actions", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 169, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.S3", + "type": "", + "pos": { + "x": 958, + "y": 87 + }, + "width": 131, + "height": 137, + "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": "S3", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 31, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.Terraform", + "type": "", + "pos": { + "x": 1248, + "y": 87 + }, + "width": 218, + "height": 137, + "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": "Terraform", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 118, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.AWS", + "type": "", + "pos": { + "x": 1675, + "y": 87 + }, + "width": 155, + "height": 137, + "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": "AWS", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "build_workflow.(push -> GHA)[0]", + "src": "build_workflow.push", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.GHA", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Triggers", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 417, + "y": 155.5 + }, + { + "x": 511, + "y": 155.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(GHA -> S3)[0]", + "src": "build_workflow.GHA", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.S3", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Builds zip & pushes it", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 138, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 780, + "y": 155.5 + }, + { + "x": 958, + "y": 155.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(S3 <-> Terraform)[0]", + "src": "build_workflow.S3", + "srcArrow": "triangle", + "srcLabel": "", + "dst": "build_workflow.Terraform", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Pulls zip to deploy", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 119, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1089, + "y": 155.5 + }, + { + "x": 1248, + "y": 155.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(Terraform -> AWS)[0]", + "src": "build_workflow.Terraform", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.AWS", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Changes the live lambdas", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 169, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1466, + "y": 155.5 + }, + { + "x": 1675, + "y": 155.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/dagre_edge_label_spacing/elk/sketch.exp.svg b/e2etests/testdata/regression/dagre_edge_label_spacing/elk/sketch.exp.svg new file mode 100644 index 000000000..814c60100 --- /dev/null +++ b/e2etests/testdata/regression/dagre_edge_label_spacing/elk/sketch.exp.svg @@ -0,0 +1,48 @@ + +lambda-build.yamlPush to main branchGitHub ActionsS3TerraformAWS TriggersBuilds zip & pushes it Pulls zip to deployChanges the live lambdas + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json b/e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json index 3684ac8af..b0d5e092e 100644 --- a/e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json +++ b/e2etests/testdata/regression/dagre_special_ids/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "\"ninety\\nnine\"", 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..d5d44ead7 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 + + +lambda-build.yamllambda-deploy.yamlapollo-deploy.yamlPush to main branchGitHub ActionsS3TerraformAWSManual TriggerGitHub ActionsAWSApollo RepoGitHub ActionsAWS TriggersBuilds zip and pushes it Pulls zip to deployChanges live lambdasLaunchesBuilds zippushes them to S3. Deploys lambdasusing TerraformTriggered manually/push to master test test test test test test testtest + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_alignment/elk/board.exp.json b/e2etests/testdata/regression/elk_alignment/elk/board.exp.json new file mode 100644 index 000000000..257234eb5 --- /dev/null +++ b/e2etests/testdata/regression/elk_alignment/elk/board.exp.json @@ -0,0 +1,880 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "build_workflow", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 480, + "height": 1099, + "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": "lambda-build.yaml", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 226, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "build_workflow.push", + "type": "", + "pos": { + "x": 87, + "y": 87 + }, + "width": 330, + "height": 137, + "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": "Push to main branch", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 230, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.GHA", + "type": "", + "pos": { + "x": 117, + "y": 290 + }, + "width": 269, + "height": 137, + "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": "GitHub Actions", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 169, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.S3", + "type": "", + "pos": { + "x": 186, + "y": 493 + }, + "width": 131, + "height": 137, + "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": "S3", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 31, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.Terraform", + "type": "", + "pos": { + "x": 143, + "y": 696 + }, + "width": 218, + "height": 137, + "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": "Terraform", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 118, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "build_workflow.AWS", + "type": "", + "pos": { + "x": 174, + "y": 899 + }, + "width": 155, + "height": 137, + "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": "AWS", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "deploy_workflow", + "type": "", + "pos": { + "x": 512, + "y": 175 + }, + "width": 421, + "height": 773, + "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": "lambda-deploy.yaml", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 247, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "deploy_workflow.manual", + "type": "", + "pos": { + "x": 587, + "y": 250 + }, + "width": 271, + "height": 137, + "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": "Manual Trigger", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 171, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "deploy_workflow.GHA", + "type": "", + "pos": { + "x": 588, + "y": 453 + }, + "width": 269, + "height": 137, + "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": "GitHub Actions", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 169, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "deploy_workflow.AWS", + "type": "", + "pos": { + "x": 645, + "y": 736 + }, + "width": 155, + "height": 137, + "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": "AWS", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "apollo_workflow", + "type": "", + "pos": { + "x": 953, + "y": 215 + }, + "width": 684, + "height": 693, + "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": "apollo-deploy.yaml", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 232, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "apollo_workflow.apollo", + "type": "", + "pos": { + "x": 1175, + "y": 290 + }, + "width": 238, + "height": 137, + "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": "Apollo Repo", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 138, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "apollo_workflow.GHA", + "type": "", + "pos": { + "x": 1160, + "y": 493 + }, + "width": 269, + "height": 137, + "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": "GitHub Actions", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 169, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "apollo_workflow.AWS", + "type": "", + "pos": { + "x": 1217, + "y": 696 + }, + "width": 155, + "height": 137, + "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": "AWS", + "fontSize": 25, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "build_workflow.(push -> GHA)[0]", + "src": "build_workflow.push", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.GHA", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Triggers", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 67, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 252, + "y": 224 + }, + { + "x": 252, + "y": 290 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(GHA -> S3)[0]", + "src": "build_workflow.GHA", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.S3", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Builds zip and pushes it", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 192, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 252, + "y": 427 + }, + { + "x": 252, + "y": 493 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(S3 <-> Terraform)[0]", + "src": "build_workflow.S3", + "srcArrow": "triangle", + "srcLabel": "", + "dst": "build_workflow.Terraform", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Pulls zip to deploy", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 149, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 252, + "y": 630 + }, + { + "x": 252, + "y": 696 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "build_workflow.(Terraform -> AWS)[0]", + "src": "build_workflow.Terraform", + "srcArrow": "none", + "srcLabel": "", + "dst": "build_workflow.AWS", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Changes live lambdas", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 179, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 252, + "y": 833 + }, + { + "x": 252, + "y": 899 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "deploy_workflow.(manual -> GHA)[0]", + "src": "deploy_workflow.manual", + "srcArrow": "none", + "srcLabel": "", + "dst": "deploy_workflow.GHA", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Launches", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 78, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 722.5, + "y": 387 + }, + { + "x": 722.5, + "y": 453 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "deploy_workflow.(GHA -> AWS)[0]", + "src": "deploy_workflow.GHA", + "srcArrow": "none", + "srcLabel": "", + "dst": "deploy_workflow.AWS", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Builds zip\npushes them to S3.\n\nDeploys lambdas\nusing Terraform", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 153, + "labelHeight": 106, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 722.5, + "y": 590 + }, + { + "x": 722.5, + "y": 736 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "apollo_workflow.(apollo -> GHA)[0]", + "src": "apollo_workflow.apollo", + "srcArrow": "none", + "srcLabel": "", + "dst": "apollo_workflow.GHA", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "Triggered manually/push to master test test test test test test test", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 533, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1294.5, + "y": 427 + }, + { + "x": 1294.5, + "y": 493 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "apollo_workflow.(GHA -> AWS)[0]", + "src": "apollo_workflow.GHA", + "srcArrow": "none", + "srcLabel": "", + "dst": "apollo_workflow.AWS", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "test", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 31, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1294.5, + "y": 630 + }, + { + "x": 1294.5, + "y": 696 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/elk_alignment/elk/sketch.exp.svg b/e2etests/testdata/regression/elk_alignment/elk/sketch.exp.svg new file mode 100644 index 000000000..593ff3bb8 --- /dev/null +++ b/e2etests/testdata/regression/elk_alignment/elk/sketch.exp.svg @@ -0,0 +1,52 @@ + +lambda-build.yamllambda-deploy.yamlapollo-deploy.yamlPush to main branchGitHub ActionsS3TerraformAWSManual TriggerGitHub ActionsAWSApollo RepoGitHub ActionsAWS TriggersBuilds zip and pushes it Pulls zip to deployChanges live lambdasLaunchesBuilds zippushes them to S3. Deploys lambdasusing TerraformTriggered manually/push to master test test test test test test testtest + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/board.exp.json b/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/board.exp.json new file mode 100644 index 000000000..f1b566716 --- /dev/null +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/board.exp.json @@ -0,0 +1,107 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "img", + "type": "image", + "pos": { + "x": 0, + "y": 0 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "ico", + "type": "", + "pos": { + "x": 188, + "y": 14 + }, + "width": 100, + "height": 100, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/sketch.exp.svg b/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/sketch.exp.svg new file mode 100644 index 000000000..3726c5c2d --- /dev/null +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/dagre/sketch.exp.svg @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json new file mode 100644 index 000000000..e37d0a2b3 --- /dev/null +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/board.exp.json @@ -0,0 +1,107 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "img", + "type": "image", + "pos": { + "x": 12, + "y": 12 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "ico", + "type": "", + "pos": { + "x": 160, + "y": 26 + }, + "width": 100, + "height": 100, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg new file mode 100644 index 000000000..81e2051b4 --- /dev/null +++ b/e2etests/testdata/regression/elk_img_empty_label_panic/elk/sketch.exp.svg @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/elk_order/dagre/board.exp.json b/e2etests/testdata/regression/elk_order/dagre/board.exp.json new file mode 100644 index 000000000..a23eacad4 --- /dev/null +++ b/e2etests/testdata/regression/elk_order/dagre/board.exp.json @@ -0,0 +1,675 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "queue", + "type": "queue", + "pos": { + "x": 0, + "y": 148 + }, + "width": 1336, + "height": 226, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#DEE1EB", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "queue.M0", + "type": "", + "pos": { + "x": 50, + "y": 198 + }, + "width": 125, + "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": "M0", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M1", + "type": "", + "pos": { + "x": 235, + "y": 198 + }, + "width": 125, + "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": "M1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M2", + "type": "", + "pos": { + "x": 420, + "y": 198 + }, + "width": 125, + "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": "M2", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M3", + "type": "", + "pos": { + "x": 605, + "y": 198 + }, + "width": 125, + "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": "M3", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M4", + "type": "", + "pos": { + "x": 790, + "y": 198 + }, + "width": 126, + "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": "M4", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 26, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M5", + "type": "", + "pos": { + "x": 976, + "y": 198 + }, + "width": 125, + "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": "M5", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M6", + "type": "", + "pos": { + "x": 1161, + "y": 198 + }, + "width": 125, + "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": "M6", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "m0_desc", + "type": "text", + "pos": { + "x": 60, + "y": 12 + }, + "width": 106, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Oldest message", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 106, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m2_desc", + "type": "text", + "pos": { + "x": 462, + "y": 12 + }, + "width": 41, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Offset", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 41, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m5_desc", + "type": "text", + "pos": { + "x": 994, + "y": 12 + }, + "width": 90, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Last message", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 90, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m6_desc", + "type": "text", + "pos": { + "x": 1154, + "y": 0 + }, + "width": 140, + "height": 48, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Next message will be\\\ninserted here", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 140, + "labelHeight": 48, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(m0_desc -> queue.M0)[0]", + "src": "m0_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M0", + "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": 112.5, + "y": 36 + }, + { + "x": 112.5, + "y": 85.6 + }, + { + "x": 112.5, + "y": 158 + }, + { + "x": 112.5, + "y": 198 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m2_desc -> queue.M2)[0]", + "src": "m2_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M2", + "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": 482.5, + "y": 36 + }, + { + "x": 482.5, + "y": 85.6 + }, + { + "x": 482.5, + "y": 158 + }, + { + "x": 482.5, + "y": 198 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m5_desc -> queue.M5)[0]", + "src": "m5_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M5", + "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": 1038.5, + "y": 36 + }, + { + "x": 1038.5, + "y": 85.6 + }, + { + "x": 1038.5, + "y": 158 + }, + { + "x": 1038.5, + "y": 198 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m6_desc -> queue.M6)[0]", + "src": "m6_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M6", + "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": 1223.5, + "y": 48 + }, + { + "x": 1223.5, + "y": 88 + }, + { + "x": 1223.5, + "y": 158 + }, + { + "x": 1223.5, + "y": 198 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/elk_order/dagre/sketch.exp.svg b/e2etests/testdata/regression/elk_order/dagre/sketch.exp.svg new file mode 100644 index 000000000..64e3079f2 --- /dev/null +++ b/e2etests/testdata/regression/elk_order/dagre/sketch.exp.svg @@ -0,0 +1,800 @@ + +

Oldest message

+

Offset

+

Last message

+

Next message will be
+inserted here

+
M0M1M2M3M4M5M6 + + +
\ No newline at end of file diff --git a/e2etests/testdata/regression/elk_order/elk/board.exp.json b/e2etests/testdata/regression/elk_order/elk/board.exp.json new file mode 100644 index 000000000..b59278418 --- /dev/null +++ b/e2etests/testdata/regression/elk_order/elk/board.exp.json @@ -0,0 +1,639 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "queue", + "type": "queue", + "pos": { + "x": 12, + "y": 165 + }, + "width": 1146, + "height": 276, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#DEE1EB", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "queue.M0", + "type": "", + "pos": { + "x": 87, + "y": 240 + }, + "width": 125, + "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": "M0", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M1", + "type": "", + "pos": { + "x": 232, + "y": 240 + }, + "width": 125, + "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": "M1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M2", + "type": "", + "pos": { + "x": 377, + "y": 240 + }, + "width": 125, + "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": "M2", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M3", + "type": "", + "pos": { + "x": 522, + "y": 240 + }, + "width": 125, + "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": "M3", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M4", + "type": "", + "pos": { + "x": 667, + "y": 240 + }, + "width": 126, + "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": "M4", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 26, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M5", + "type": "", + "pos": { + "x": 813, + "y": 240 + }, + "width": 125, + "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": "M5", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "queue.M6", + "type": "", + "pos": { + "x": 958, + "y": 240 + }, + "width": 125, + "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": "M6", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 25, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "m0_desc", + "type": "text", + "pos": { + "x": 96, + "y": 36 + }, + "width": 106, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Oldest message", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 106, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m2_desc", + "type": "text", + "pos": { + "x": 419, + "y": 36 + }, + "width": 41, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Offset", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 41, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m5_desc", + "type": "text", + "pos": { + "x": 830, + "y": 36 + }, + "width": 90, + "height": 24, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Last message", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 90, + "labelHeight": 24, + "zIndex": 0, + "level": 1 + }, + { + "id": "m6_desc", + "type": "text", + "pos": { + "x": 950, + "y": 12 + }, + "width": 140, + "height": 48, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Next message will be\\\ninserted here", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 140, + "labelHeight": 48, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(m0_desc -> queue.M0)[0]", + "src": "m0_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M0", + "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": 149.5, + "y": 60 + }, + { + "x": 149.5, + "y": 240 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m2_desc -> queue.M2)[0]", + "src": "m2_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M2", + "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": 439.5, + "y": 60 + }, + { + "x": 439.5, + "y": 240 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m5_desc -> queue.M5)[0]", + "src": "m5_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M5", + "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": 875.5, + "y": 60 + }, + { + "x": 875.5, + "y": 240 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(m6_desc -> queue.M6)[0]", + "src": "m6_desc", + "srcArrow": "none", + "srcLabel": "", + "dst": "queue.M6", + "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": 1020.5, + "y": 60 + }, + { + "x": 1020.5, + "y": 240 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/elk_order/elk/sketch.exp.svg b/e2etests/testdata/regression/elk_order/elk/sketch.exp.svg new file mode 100644 index 000000000..6ccb94a1b --- /dev/null +++ b/e2etests/testdata/regression/elk_order/elk/sketch.exp.svg @@ -0,0 +1,800 @@ + +

Oldest message

+

Offset

+

Last message

+

Next message will be
+inserted here

+
M0M1M2M3M4M5M6 + + +
\ No newline at end of file diff --git a/e2etests/testdata/regression/empty_sequence/dagre/board.exp.json b/e2etests/testdata/regression/empty_sequence/dagre/board.exp.json index a2abea372..63644901e 100644 --- a/e2etests/testdata/regression/empty_sequence/dagre/board.exp.json +++ b/e2etests/testdata/regression/empty_sequence/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "A", diff --git a/e2etests/testdata/regression/empty_sequence/dagre/sketch.exp.svg b/e2etests/testdata/regression/empty_sequence/dagre/sketch.exp.svg index c8610a055..192e116cb 100644 --- a/e2etests/testdata/regression/empty_sequence/dagre/sketch.exp.svg +++ b/e2etests/testdata/regression/empty_sequence/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="366" height="552" viewBox="-100 -100 366 552">hellogoodbye hellogoodbye + + +hellogoodbye hellogoodbye + + +RefreshAuthorizationPolicyProtocolServerSideTranslatorProtocolBufferRefreshAuthorizationPolicyCacheRefreshAuthorizationPolicyCacheok + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/only_header_class_table/elk/board.exp.json b/e2etests/testdata/regression/only_header_class_table/elk/board.exp.json new file mode 100644 index 000000000..31a428a6c --- /dev/null +++ b/e2etests/testdata/regression/only_header_class_table/elk/board.exp.json @@ -0,0 +1,241 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 12, + "y": 12 + }, + "width": 1082, + "height": 36, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "RefreshAuthorizationPolicyProtocolServerSideTranslatorProtocolBuffer", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 982, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "table", + "type": "sql_table", + "pos": { + "x": 352, + "y": 148 + }, + "width": 401, + "height": 36, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "RefreshAuthorizationPolicyCache", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 361, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "table with short col", + "type": "sql_table", + "pos": { + "x": 352, + "y": 284 + }, + "width": 401, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "ok", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 21, + "labelHeight": 26 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": "", + "reference": "" + } + ], + "label": "RefreshAuthorizationPolicyCache", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 361, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + } + ], + "connections": [ + { + "id": "(class -> table)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "table", + "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": 553, + "y": 48 + }, + { + "x": 553, + "y": 148 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(table -> table with short col)[0]", + "src": "table", + "srcArrow": "none", + "srcLabel": "", + "dst": "table with short col", + "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": 553, + "y": 184 + }, + { + "x": 553, + "y": 284 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/only_header_class_table/elk/sketch.exp.svg b/e2etests/testdata/regression/only_header_class_table/elk/sketch.exp.svg new file mode 100644 index 000000000..546757264 --- /dev/null +++ b/e2etests/testdata/regression/only_header_class_table/elk/sketch.exp.svg @@ -0,0 +1,40 @@ + +RefreshAuthorizationPolicyProtocolServerSideTranslatorProtocolBufferRefreshAuthorizationPolicyCacheRefreshAuthorizationPolicyCacheok + + + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/query_param_escape/dagre/board.exp.json b/e2etests/testdata/regression/query_param_escape/dagre/board.exp.json new file mode 100644 index 000000000..fb0febb97 --- /dev/null +++ b/e2etests/testdata/regression/query_param_escape/dagre/board.exp.json @@ -0,0 +1,58 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "my network", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 190, + "height": 152, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "fuga=1&hoge", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "my network", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 90, + "labelHeight": 26, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/regression/query_param_escape/dagre/sketch.exp.svg b/e2etests/testdata/regression/query_param_escape/dagre/sketch.exp.svg new file mode 100644 index 000000000..f9fe5a482 --- /dev/null +++ b/e2etests/testdata/regression/query_param_escape/dagre/sketch.exp.svg @@ -0,0 +1,31 @@ + +my network + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/query_param_escape/elk/board.exp.json b/e2etests/testdata/regression/query_param_escape/elk/board.exp.json new file mode 100644 index 000000000..bce062b6f --- /dev/null +++ b/e2etests/testdata/regression/query_param_escape/elk/board.exp.json @@ -0,0 +1,58 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "my network", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 190, + "height": 152, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/infra/019-network.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "fuga=1&hoge", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "my network", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 90, + "labelHeight": 26, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/regression/query_param_escape/elk/sketch.exp.svg b/e2etests/testdata/regression/query_param_escape/elk/sketch.exp.svg new file mode 100644 index 000000000..0a13f7b0b --- /dev/null +++ b/e2etests/testdata/regression/query_param_escape/elk/sketch.exp.svg @@ -0,0 +1,31 @@ + +my network + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/board.exp.json b/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/board.exp.json new file mode 100644 index 000000000..d70cfb8a1 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/board.exp.json @@ -0,0 +1,530 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "foo", + "type": "sequence_diagram", + "pos": { + "x": 0, + "y": 0 + }, + "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": 0, + "y": 620 + }, + "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": 24, + "y": 110 + }, + "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": 274, + "y": 110 + }, + "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": 24, + "y": 730 + }, + "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": 274, + "y": 730 + }, + "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": 99, + "y": 366 + }, + { + "x": 349, + "y": 366 + } + ], + "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": 99, + "y": 986 + }, + { + "x": 349, + "y": 986 + } + ], + "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": 224, + "y": 520 + }, + { + "x": 224, + "y": 560 + }, + { + "x": 224, + "y": 580 + }, + { + "x": 224, + "y": 620 + } + ], + "isCurve": true, + "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": 99, + "y": 236 + }, + { + "x": 99, + "y": 496 + } + ], + "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": 349, + "y": 236 + }, + { + "x": 349, + "y": 496 + } + ], + "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": 99, + "y": 856 + }, + { + "x": 99, + "y": 1116 + } + ], + "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": 349, + "y": 856 + }, + { + "x": 349, + "y": 1116 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + } + ] +} diff --git a/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/sketch.exp.svg b/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/sketch.exp.svg new file mode 100644 index 000000000..6782c7775 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_name_crash/dagre/sketch.exp.svg @@ -0,0 +1,31 @@ + +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..019cb8809 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_name_crash/elk/board.exp.json @@ -0,0 +1,521 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "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..4da5a4a8e --- /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/board.exp.json b/e2etests/testdata/regression/sequence_diagram_no_message/dagre/board.exp.json new file mode 100644 index 000000000..379bdcc9d --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_no_message/dagre/board.exp.json @@ -0,0 +1,166 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "", + "pos": { + "x": 24, + "y": 74 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "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": "A", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "", + "pos": { + "x": 274, + "y": 74 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "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": "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": 1 + } + ], + "connections": [ + { + "id": "(a -- )[0]", + "src": "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": 99, + "y": 200 + }, + { + "x": 99, + "y": 330 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + }, + { + "id": "(b -- )[0]", + "src": "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": 349, + "y": 200 + }, + { + "x": 349, + "y": 330 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + } + ] +} 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 new file mode 100644 index 000000000..55d07e025 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_no_message/dagre/sketch.exp.svg @@ -0,0 +1,31 @@ + +AB + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sequence_diagram_no_message/elk/board.exp.json b/e2etests/testdata/regression/sequence_diagram_no_message/elk/board.exp.json new file mode 100644 index 000000000..379bdcc9d --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_no_message/elk/board.exp.json @@ -0,0 +1,166 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "", + "pos": { + "x": 24, + "y": 74 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "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": "A", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "", + "pos": { + "x": 274, + "y": 74 + }, + "width": 150, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "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": "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": 1 + } + ], + "connections": [ + { + "id": "(a -- )[0]", + "src": "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": 99, + "y": 200 + }, + { + "x": 99, + "y": 330 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + }, + { + "id": "(b -- )[0]", + "src": "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": 349, + "y": 200 + }, + { + "x": 349, + "y": 330 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 1 + } + ] +} diff --git a/e2etests/testdata/regression/sequence_diagram_no_message/elk/sketch.exp.svg b/e2etests/testdata/regression/sequence_diagram_no_message/elk/sketch.exp.svg new file mode 100644 index 000000000..55d07e025 --- /dev/null +++ b/e2etests/testdata/regression/sequence_diagram_no_message/elk/sketch.exp.svg @@ -0,0 +1,31 @@ + +AB + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json b/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json index 8966eb315..20808f117 100644 --- a/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json +++ b/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "b", diff --git a/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/sketch.exp.svg b/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/sketch.exp.svg index b1b935e3c..ac229bb1a 100644 --- a/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/sketch.exp.svg +++ b/e2etests/testdata/regression/sequence_diagram_span_cover/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="375" height="796" viewBox="-76 -26 375 796">b b + + +b b + + +sql_table_overflowshort +loooooooooooooooooooong +loooooooooooooooooooong +short +sql_table_constrained_overflowshort +loooooooooooooooooooong +UNQloooooooooooooooooooong +short +FK + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json b/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json new file mode 100644 index 000000000..6b0307c7c --- /dev/null +++ b/e2etests/testdata/regression/sql_table_overflow/elk/board.exp.json @@ -0,0 +1,205 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "table", + "type": "sql_table", + "pos": { + "x": 12, + "y": 12 + }, + "width": 534, + "height": 108, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "short", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 45, + "labelHeight": 26 + }, + "type": { + "label": "loooooooooooooooooooong", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 242, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "loooooooooooooooooooong", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 242, + "labelHeight": 26 + }, + "type": { + "label": "short", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 45, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "sql_table_overflow", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 211, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "table_constrained", + "type": "sql_table", + "pos": { + "x": 566, + "y": 12 + }, + "width": 594, + "height": 108, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "short", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 45, + "labelHeight": 26 + }, + "type": { + "label": "loooooooooooooooooooong", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 242, + "labelHeight": 26 + }, + "constraint": "unique", + "reference": "" + }, + { + "name": { + "label": "loooooooooooooooooooong", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 242, + "labelHeight": 26 + }, + "type": { + "label": "short", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 45, + "labelHeight": 26 + }, + "constraint": "foreign_key", + "reference": "" + } + ], + "label": "sql_table_constrained_overflow", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 350, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + } + ], + "connections": [] +} diff --git a/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg b/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg new file mode 100644 index 000000000..2fb34aef3 --- /dev/null +++ b/e2etests/testdata/regression/sql_table_overflow/elk/sketch.exp.svg @@ -0,0 +1,39 @@ + +sql_table_overflowshort +loooooooooooooooooooong +loooooooooooooooooooong +short +sql_table_constrained_overflowshort +loooooooooooooooooooong +UNQloooooooooooooooooooong +short +FK + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/unnamed_class_table_code/dagre/board.exp.json b/e2etests/testdata/regression/unnamed_class_table_code/dagre/board.exp.json new file mode 100644 index 000000000..2d10adc2e --- /dev/null +++ b/e2etests/testdata/regression/unnamed_class_table_code/dagre/board.exp.json @@ -0,0 +1,400 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 0, + "y": 0 + }, + "width": 422, + "height": 368, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 107, + "y": 468 + }, + "width": 208, + "height": 186, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 59, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 113, + "y": 754 + }, + "width": 196, + "height": 70, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(class -> users)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 211, + "y": 368 + }, + { + "x": 211, + "y": 408 + }, + { + "x": 211, + "y": 428 + }, + { + "x": 211, + "y": 468 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(users -> code)[0]", + "src": "users", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 211, + "y": 654 + }, + { + "x": 211, + "y": 694 + }, + { + "x": 211, + "y": 714 + }, + { + "x": 211, + "y": 754 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/unnamed_class_table_code/dagre/sketch.exp.svg b/e2etests/testdata/regression/unnamed_class_table_code/dagre/sketch.exp.svg new file mode 100644 index 000000000..2dc724baf --- /dev/null +++ b/e2etests/testdata/regression/unnamed_class_table_code/dagre/sketch.exp.svg @@ -0,0 +1,62 @@ + +- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/regression/unnamed_class_table_code/elk/board.exp.json b/e2etests/testdata/regression/unnamed_class_table_code/elk/board.exp.json new file mode 100644 index 000000000..0a375e8ae --- /dev/null +++ b/e2etests/testdata/regression/unnamed_class_table_code/elk/board.exp.json @@ -0,0 +1,382 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 12, + "y": 12 + }, + "width": 422, + "height": 368, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 119, + "y": 480 + }, + "width": 208, + "height": 186, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 59, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 125, + "y": 766 + }, + "width": 196, + "height": 70, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(class -> users)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 223, + "y": 380 + }, + { + "x": 223, + "y": 480 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(users -> code)[0]", + "src": "users", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 223, + "y": 666 + }, + { + "x": 223, + "y": 766 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/regression/unnamed_class_table_code/elk/sketch.exp.svg b/e2etests/testdata/regression/unnamed_class_table_code/elk/sketch.exp.svg new file mode 100644 index 000000000..5e8d36e19 --- /dev/null +++ b/e2etests/testdata/regression/unnamed_class_table_code/elk/sketch.exp.svg @@ -0,0 +1,62 @@ + +- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/sanity/1_to_2/dagre/board.exp.json b/e2etests/testdata/sanity/1_to_2/dagre/board.exp.json index 53c443478..57e3d9c03 100644 --- a/e2etests/testdata/sanity/1_to_2/dagre/board.exp.json +++ b/e2etests/testdata/sanity/1_to_2/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", diff --git a/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg b/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg index 9aeea457d..4f3855a01 100644 --- a/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg +++ b/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="486" height="552" viewBox="-100 -100 486 552">abc abc + + +abc abc + + +ab ab + + +ab ab + + +acbd acbd + + +acbd acbd + + +ab hello - +ab hello + ab hello - +ab hello + \ No newline at end of file + + + + \ No newline at end of file diff --git a/e2etests/testdata/sanity/empty/elk/board.exp.json b/e2etests/testdata/sanity/empty/elk/board.exp.json index 8591adc9e..57a381554 100644 --- a/e2etests/testdata/sanity/empty/elk/board.exp.json +++ b/e2etests/testdata/sanity/empty/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [], "connections": [] } diff --git a/e2etests/testdata/sanity/empty/elk/sketch.exp.svg b/e2etests/testdata/sanity/empty/elk/sketch.exp.svg index 9563e1411..243722dc6 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/board.exp.json b/e2etests/testdata/stable/all_shapes/dagre/board.exp.json index 250620797..97f3baf20 100644 --- a/e2etests/testdata/stable/all_shapes/dagre/board.exp.json +++ b/e2etests/testdata/stable/all_shapes/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "rectangle", diff --git a/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/all_shapes/dagre/sketch.exp.svg index 864693eac..0ff95d243 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 * + + +ab To err is human, to moo bovine1* - +ab To err is human, to moo bovine1* + ab To err is human, to moo bovine1* - - +ab To err is human, to moo bovine1* + + abcdefghijklmno abcdefghijklmno + + +abcdefghijklmno abcdefghijklmno + + +aaadddeeebbbccc111 222 - +aaadddeeebbbccc111 222 + aaadddeeebbbccc111 222 - +aaadddeeebbbccc111 222 + - + aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff1122 334455667788 - +aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 + - + - + aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff1122 334455667788 - - - - - - - - - +aabbllmmnnoocciikkddgghhjjeeff1122 334455667788 + + + + + + + + + abcd abcd + + +abcd abcd + + +abc abc + + +abc abc + + +BatchManager- -num -int- -timeout -int- -pid -+ -getStatus() -Enum+ -getJobs() -Job[]+ -setTimeout(seconds int) -voidBatchManager- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +void + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/class/elk/board.exp.json b/e2etests/testdata/stable/class/elk/board.exp.json index 85549a957..438953ee7 100644 --- a/e2etests/testdata/stable/class/elk/board.exp.json +++ b/e2etests/testdata/stable/class/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "manager", @@ -8,14 +9,14 @@ "x": 12, "y": 12 }, - "width": 339, + "width": 422, "height": 368, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0A0F25", + "fill": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -67,10 +68,13 @@ "italic": false, "bold": false, "underline": false, - "labelWidth": 150, + "labelWidth": 175, "labelHeight": 36, "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" } ], "connections": [] diff --git a/e2etests/testdata/stable/class/elk/sketch.exp.svg b/e2etests/testdata/stable/class/elk/sketch.exp.svg index d1ad09c80..a433f42f0 100644 --- a/e2etests/testdata/stable/class/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/class/elk/sketch.exp.svg @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/e2etests/testdata/stable/code_snippet/dagre/board.exp.json b/e2etests/testdata/stable/code_snippet/dagre/board.exp.json index c058364e1..6401cabbb 100644 --- a/e2etests/testdata/stable/code_snippet/dagre/board.exp.json +++ b/e2etests/testdata/stable/code_snippet/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "hey", diff --git a/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg b/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg index 15c9a88be..a737297f9 100644 --- a/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg @@ -26,7 +26,10 @@ width="955" height="818" viewBox="-100 -100 955 818">acfbdhg acfbdhg + + +acfbdhg acfbdhg + + +xyThe top of the mountainJoeDonald

Cats, no less liquid than their shadows, offer no angles to the wind.

+

If we can't fix it, it ain't broke.

+

Dieters live life in the fasting lane.

+
i am top lefti am top righti am bottom lefti am bottom right + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/constant_near_stress/elk/board.exp.json b/e2etests/testdata/stable/constant_near_stress/elk/board.exp.json new file mode 100644 index 000000000..56b5e3d52 --- /dev/null +++ b/e2etests/testdata/stable/constant_near_stress/elk/board.exp.json @@ -0,0 +1,447 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "x", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 113, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "x", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "y", + "type": "", + "pos": { + "x": 12, + "y": 238 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "y", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "The top of the mountain", + "type": "text", + "pos": { + "x": -12, + "y": -29 + }, + "width": 162, + "height": 21, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "The top of the mountain", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 162, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "Joe", + "type": "person", + "pos": { + "x": -139, + "y": 125 + }, + "width": 131, + "height": 126, + "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": "Joe", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 31, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "Donald", + "type": "person", + "pos": { + "x": 146, + "y": 125 + }, + "width": 155, + "height": 126, + "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": "Donald", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 55, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "bottom", + "type": "text", + "pos": { + "x": -402, + "y": 384 + }, + "width": 943, + "height": 130, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# Cats, no less liquid than their shadows, offer no angles to the wind.\n\nIf we can't fix it, it ain't broke.\n\nDieters live life in the fasting lane.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 943, + "labelHeight": 130, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "i am top left", + "type": "text", + "pos": { + "x": -502, + "y": -29 + }, + "width": 80, + "height": 21, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "i am top left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "i am top right", + "type": "text", + "pos": { + "x": 560, + "y": -29 + }, + "width": 90, + "height": 21, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "i am top right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 90, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "i am bottom left", + "type": "text", + "pos": { + "x": -529, + "y": 384 + }, + "width": 107, + "height": 21, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "i am bottom left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 107, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "i am bottom right", + "type": "text", + "pos": { + "x": 560, + "y": 384 + }, + "width": 117, + "height": 21, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "i am bottom right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 117, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(x -> y)[0]", + "src": "x", + "srcArrow": "none", + "srcLabel": "", + "dst": "y", + "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": 69, + "y": 138 + }, + { + "x": 69, + "y": 238 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/constant_near_stress/elk/sketch.exp.svg b/e2etests/testdata/stable/constant_near_stress/elk/sketch.exp.svg new file mode 100644 index 000000000..a7943851b --- /dev/null +++ b/e2etests/testdata/stable/constant_near_stress/elk/sketch.exp.svg @@ -0,0 +1,798 @@ + +xyThe top of the mountainJoeDonald

Cats, no less liquid than their shadows, offer no angles to the wind.

+

If we can't fix it, it ain't broke.

+

Dieters live life in the fasting lane.

+
i am top lefti am top righti am bottom lefti am bottom right + + +
\ No newline at end of file diff --git a/e2etests/testdata/stable/constant_near_title/dagre/board.exp.json b/e2etests/testdata/stable/constant_near_title/dagre/board.exp.json new file mode 100644 index 000000000..09cde3d74 --- /dev/null +++ b/e2etests/testdata/stable/constant_near_title/dagre/board.exp.json @@ -0,0 +1,500 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "poll the people", + "type": "", + "pos": { + "x": 112, + "y": 0 + }, + "width": 210, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "poll the people", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 110, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "results", + "type": "", + "pos": { + "x": 241, + "y": 226 + }, + "width": 153, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "results", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 53, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "unfavorable", + "type": "", + "pos": { + "x": 0, + "y": 452 + }, + "width": 191, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "unfavorable", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 91, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "favorable", + "type": "", + "pos": { + "x": 251, + "y": 452 + }, + "width": 173, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "favorable", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 73, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "will of the people", + "type": "", + "pos": { + "x": 224, + "y": 678 + }, + "width": 227, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "will of the people", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 127, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "title", + "type": "text", + "pos": { + "x": 92, + "y": -70 + }, + "width": 266, + "height": 50, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# A winning strategy", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 266, + "labelHeight": 50, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(poll the people -> results)[0]", + "src": "poll the people", + "srcArrow": "none", + "srcLabel": "", + "dst": "results", + "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": 272.80973451327435, + "y": 126 + }, + { + "x": 308.5619469026549, + "y": 166 + }, + { + "x": 317.5, + "y": 186 + }, + { + "x": 317.5, + "y": 226 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(results -> unfavorable)[0]", + "src": "results", + "srcArrow": "none", + "srcLabel": "", + "dst": "unfavorable", + "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": 250.03982300884957, + "y": 352 + }, + { + "x": 207.2079646017699, + "y": 392 + }, + { + "x": 187.5, + "y": 412 + }, + { + "x": 151.5, + "y": 452 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(unfavorable -> poll the people)[0]", + "src": "unfavorable", + "srcArrow": "none", + "srcLabel": "", + "dst": "poll the people", + "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": 84.34955752212389, + "y": 452 + }, + { + "x": 77.26991150442478, + "y": 412 + }, + { + "x": 75.5, + "y": 379.4 + }, + { + "x": 75.5, + "y": 345.5 + }, + { + "x": 75.5, + "y": 311.6 + }, + { + "x": 87.9, + "y": 166 + }, + { + "x": 137.5, + "y": 126 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(results -> favorable)[0]", + "src": "results", + "srcArrow": "none", + "srcLabel": "", + "dst": "favorable", + "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": 328.6504424778761, + "y": 352 + }, + { + "x": 335.7300884955752, + "y": 392 + }, + { + "x": 337.5, + "y": 412 + }, + { + "x": 337.5, + "y": 452 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(favorable -> will of the people)[0]", + "src": "favorable", + "srcArrow": "none", + "srcLabel": "", + "dst": "will of the people", + "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": 337.5, + "y": 578 + }, + { + "x": 337.5, + "y": 618 + }, + { + "x": 337.5, + "y": 638 + }, + { + "x": 337.5, + "y": 678 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/constant_near_title/dagre/sketch.exp.svg b/e2etests/testdata/stable/constant_near_title/dagre/sketch.exp.svg new file mode 100644 index 000000000..de05ea4cf --- /dev/null +++ b/e2etests/testdata/stable/constant_near_title/dagre/sketch.exp.svg @@ -0,0 +1,796 @@ + +poll the peopleresultsunfavorablefavorablewill of the people

A winning strategy

+
+ + +
\ No newline at end of file diff --git a/e2etests/testdata/stable/constant_near_title/elk/board.exp.json b/e2etests/testdata/stable/constant_near_title/elk/board.exp.json new file mode 100644 index 000000000..e4ae5bee0 --- /dev/null +++ b/e2etests/testdata/stable/constant_near_title/elk/board.exp.json @@ -0,0 +1,459 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "poll the people", + "type": "", + "pos": { + "x": 81, + "y": 12 + }, + "width": 210, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "poll the people", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 110, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "results", + "type": "", + "pos": { + "x": 74, + "y": 238 + }, + "width": 153, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "results", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 53, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "unfavorable", + "type": "", + "pos": { + "x": 232, + "y": 464 + }, + "width": 191, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "unfavorable", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 91, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "favorable", + "type": "", + "pos": { + "x": 39, + "y": 464 + }, + "width": 173, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "favorable", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 73, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "will of the people", + "type": "", + "pos": { + "x": 12, + "y": 690 + }, + "width": 227, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "will of the people", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 127, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "title", + "type": "text", + "pos": { + "x": 84, + "y": -58 + }, + "width": 266, + "height": 50, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# A winning strategy", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 266, + "labelHeight": 50, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(poll the people -> results)[0]", + "src": "poll the people", + "srcArrow": "none", + "srcLabel": "", + "dst": "results", + "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": 151, + "y": 138 + }, + { + "x": 151, + "y": 238 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(results -> unfavorable)[0]", + "src": "results", + "srcArrow": "none", + "srcLabel": "", + "dst": "unfavorable", + "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": 176.5, + "y": 364 + }, + { + "x": 176.5, + "y": 414 + }, + { + "x": 295.6666666666667, + "y": 414 + }, + { + "x": 295.6666666666667, + "y": 464 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(unfavorable -> poll the people)[0]", + "src": "unfavorable", + "srcArrow": "none", + "srcLabel": "", + "dst": "poll the people", + "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": 359.3333333333333, + "y": 464 + }, + { + "x": 359.3333333333333, + "y": 188 + }, + { + "x": 221, + "y": 188 + }, + { + "x": 221, + "y": 138 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(results -> favorable)[0]", + "src": "results", + "srcArrow": "none", + "srcLabel": "", + "dst": "favorable", + "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": 125.5, + "y": 364 + }, + { + "x": 125.5, + "y": 464 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(favorable -> will of the people)[0]", + "src": "favorable", + "srcArrow": "none", + "srcLabel": "", + "dst": "will of the people", + "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": 125.5, + "y": 590 + }, + { + "x": 125.5, + "y": 690 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/constant_near_title/elk/sketch.exp.svg b/e2etests/testdata/stable/constant_near_title/elk/sketch.exp.svg new file mode 100644 index 000000000..bfc3fa64f --- /dev/null +++ b/e2etests/testdata/stable/constant_near_title/elk/sketch.exp.svg @@ -0,0 +1,796 @@ + +poll the peopleresultsunfavorablefavorablewill of the people

A winning strategy

+
+ + +
\ No newline at end of file diff --git a/e2etests/testdata/stable/container_edges/dagre/board.exp.json b/e2etests/testdata/stable/container_edges/dagre/board.exp.json index 85f844962..f44baa923 100644 --- a/e2etests/testdata/stable/container_edges/dagre/board.exp.json +++ b/e2etests/testdata/stable/container_edges/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", diff --git a/e2etests/testdata/stable/container_edges/dagre/sketch.exp.svg b/e2etests/testdata/stable/container_edges/dagre/sketch.exp.svg index 33432c0d9..8a5af78bc 100644 --- a/e2etests/testdata/stable/container_edges/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/container_edges/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="786" height="1530" viewBox="-100 -100 786 1530">agdfbhec agdfbhec + + +agdfbhec agdfbhec + + +abcdefghijklmnopq abcdefghijklmnopq + + +abcdefghijklmnopq abcdefghijklmnopq + + +finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot + + +finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot + + +bacde21345abcde bacde21345abcde + + +bacde21345abcde bacde21345abcde + + +alphabeta gamma - +alphabeta gamma + alphabeta gamma - +alphabeta gamma + 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 + + + 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 + + + +

Markdown: Syntax

+

Markdown: Syntax

  • Overview
      @@ -1031,7 +1032,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 +1032,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 + - + - - - + + + aabbccddllffwwyynniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 123456 - - - - - - - +aabbccddllffwwyynniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 123456 + + + + + + + abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv + + +abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv + + +mixed togethersugarsolution we get - +mixed togethersugarsolution we get + mixed togethersugarsolution we get - - +mixed togethersugarsolution we get + +
    +
    • Overview
      • Philosophy
      • @@ -786,7 +787,10 @@ width="579" height="752" viewBox="-100 -100 579 752">
          +
          • Overview
            • Philosophy
            • @@ -786,7 +787,10 @@ width="579" height="752" viewBox="-88 -88 579 752">
                +
                • Overview ok this is all measured
                  • Philosophy
                  • @@ -782,7 +783,10 @@ width="445" height="728" viewBox="-100 -100 445 728">
                      +
                      • Overview ok this is all measured
                        • Philosophy
                        • @@ -782,7 +783,10 @@ width="445" height="728" viewBox="-88 -88 445 728">
                            +
                            • Overview
                              • Philosophy
                              • @@ -807,7 +808,10 @@ width="547" height="1164" viewBox="-100 -100 547 1164">
                                  +
                                  • Overview
                                    • Philosophy
                                    • @@ -807,7 +808,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 +806,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 +806,10 @@ sit amet, consectetuer adipiscing elit.

                                    Another item in the same list.

                                -
                                ab x + + + + + + + + + + + +y + + + + + + + + + + + + +Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS! + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/links/elk/board.exp.json b/e2etests/testdata/stable/links/elk/board.exp.json new file mode 100644 index 000000000..69c78025b --- /dev/null +++ b/e2etests/testdata/stable/links/elk/board.exp.json @@ -0,0 +1,127 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "x", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 113, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "https://d2lang.com", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "x", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "y", + "type": "", + "pos": { + "x": 12, + "y": 238 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS!", + "link": "https://terrastruct.com", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "y", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(x -> y)[0]", + "src": "x", + "srcArrow": "none", + "srcLabel": "", + "dst": "y", + "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": 69, + "y": 138 + }, + { + "x": 69, + "y": 238 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/links/elk/sketch.exp.svg b/e2etests/testdata/stable/links/elk/sketch.exp.svg new file mode 100644 index 000000000..3a532dcdc --- /dev/null +++ b/e2etests/testdata/stable/links/elk/sketch.exp.svg @@ -0,0 +1,72 @@ + +x + + + + + + + + + + + +y + + + + + + + + + + + + +Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS! + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/lone_h1/dagre/board.exp.json b/e2etests/testdata/stable/lone_h1/dagre/board.exp.json index 654975ad8..ff793b416 100644 --- a/e2etests/testdata/stable/lone_h1/dagre/board.exp.json +++ b/e2etests/testdata/stable/lone_h1/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "md", @@ -14,8 +15,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -33,7 +34,7 @@ "language": "markdown", "color": "#0A0F25", "italic": false, - "bold": true, + "bold": false, "underline": false, "labelWidth": 266, "labelHeight": 50, diff --git a/e2etests/testdata/stable/lone_h1/dagre/sketch.exp.svg b/e2etests/testdata/stable/lone_h1/dagre/sketch.exp.svg index 160e7834f..ec323412e 100644 --- a/e2etests/testdata/stable/lone_h1/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/lone_h1/dagre/sketch.exp.svg @@ -277,6 +277,7 @@ width="466" height="702" viewBox="-100 -100 466 702">

                                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..1b9774b7f --- /dev/null +++ b/e2etests/testdata/stable/markdown_stroke_fill/elk/board.exp.json @@ -0,0 +1,165 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "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": false, + "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": false, + "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..4c3d805ef --- /dev/null +++ b/e2etests/testdata/stable/markdown_stroke_fill/elk/sketch.exp.svg @@ -0,0 +1,804 @@ + +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..5376f8370 100644 --- a/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json +++ b/e2etests/testdata/stable/md_2space_newline/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "markdown", @@ -54,8 +55,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -73,7 +74,7 @@ "language": "markdown", "color": "#0A0F25", "italic": false, - "bold": true, + "bold": false, "underline": false, "labelWidth": 459, "labelHeight": 48, 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..9e82a30fa 100644 --- a/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/md_2space_newline/dagre/sketch.exp.svg @@ -277,6 +277,7 @@ 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 + + +rootcontainerrootleftrightrootinnerrootinnerleftrightleftright to inner leftto inner rightto inner leftto inner rightto left container rootto right container root + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/overlapping_image_container_labels/elk/board.exp.json b/e2etests/testdata/stable/overlapping_image_container_labels/elk/board.exp.json new file mode 100644 index 000000000..4a0d2c40b --- /dev/null +++ b/e2etests/testdata/stable/overlapping_image_container_labels/elk/board.exp.json @@ -0,0 +1,929 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "root", + "type": "image", + "pos": { + "x": 279, + "y": 12 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 35, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "container", + "type": "", + "pos": { + "x": 12, + "y": 271 + }, + "width": 1322, + "height": 1044, + "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.root", + "type": "image", + "pos": { + "x": 279, + "y": 346 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 35, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "container.left", + "type": "", + "pos": { + "x": 87, + "y": 566 + }, + "width": 576, + "height": 674, + "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": "left", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 38, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "container.left.root", + "type": "image", + "pos": { + "x": 258, + "y": 641 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 35, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "container.left.inner", + "type": "", + "pos": { + "x": 162, + "y": 861 + }, + "width": 426, + "height": 304, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "inner", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 49, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "container.left.inner.left", + "type": "image", + "pos": { + "x": 237, + "y": 936 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 29, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 4 + }, + { + "id": "container.left.inner.right", + "type": "image", + "pos": { + "x": 385, + "y": 936 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 39, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 4 + }, + { + "id": "container.right", + "type": "", + "pos": { + "x": 683, + "y": 566 + }, + "width": 576, + "height": 674, + "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": "right", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 52, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "container.right.root", + "type": "image", + "pos": { + "x": 854, + "y": 641 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 35, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "container.right.inner", + "type": "", + "pos": { + "x": 758, + "y": 861 + }, + "width": 426, + "height": 304, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "inner", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 49, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "container.right.inner.left", + "type": "image", + "pos": { + "x": 833, + "y": 936 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 29, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 4 + }, + { + "id": "container.right.inner.right", + "type": "image", + "pos": { + "x": 981, + "y": 936 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/004-picture.svg", + "RawPath": "", + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 39, + "labelHeight": 26, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 4 + } + ], + "connections": [ + { + "id": "(root -> container.root)[0]", + "src": "root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.root", + "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": 343.6666666666667, + "y": 166 + }, + { + "x": 343.6666666666667, + "y": 346 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.left.(root -> inner.left)[0]", + "src": "container.left.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.left.inner.left", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to inner left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 76, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 301, + "y": 795 + }, + { + "x": 301, + "y": 936 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.left.(root -> inner.right)[0]", + "src": "container.left.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.left.inner.right", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to inner right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 86, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 343.6666666666667, + "y": 795 + }, + { + "x": 343.6666666666667, + "y": 805 + }, + { + "x": 393, + "y": 805 + }, + { + "x": 393, + "y": 846 + }, + { + "x": 449, + "y": 846 + }, + { + "x": 449, + "y": 936 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.right.(root -> inner.left)[0]", + "src": "container.right.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.right.inner.left", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to inner left", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 76, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 897, + "y": 795 + }, + { + "x": 897, + "y": 936 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.right.(root -> inner.right)[0]", + "src": "container.right.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.right.inner.right", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to inner right", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 86, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 939.6666666666667, + "y": 795 + }, + { + "x": 939.6666666666667, + "y": 805 + }, + { + "x": 989, + "y": 805 + }, + { + "x": 989, + "y": 846 + }, + { + "x": 1045, + "y": 846 + }, + { + "x": 1045, + "y": 936 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.(root -> left.root)[0]", + "src": "container.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.left.root", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to left container root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 136, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 322.33333333333337, + "y": 500 + }, + { + "x": 322.33333333333337, + "y": 641 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "container.(root -> right.root)[0]", + "src": "container.root", + "srcArrow": "none", + "srcLabel": "", + "dst": "container.right.root", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to right container root", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 146, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 365, + "y": 500 + }, + { + "x": 365, + "y": 510 + }, + { + "x": 918.3333333333334, + "y": 510 + }, + { + "x": 918.3333333333334, + "y": 641 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/overlapping_image_container_labels/elk/sketch.exp.svg b/e2etests/testdata/stable/overlapping_image_container_labels/elk/sketch.exp.svg new file mode 100644 index 000000000..9505c6dec --- /dev/null +++ b/e2etests/testdata/stable/overlapping_image_container_labels/elk/sketch.exp.svg @@ -0,0 +1,50 @@ + +rootcontainerrootleftrightrootinnerrootinnerleftrightleftright to inner leftto inner rightto inner leftto inner rightto left container rootto right container root + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/p/dagre/board.exp.json b/e2etests/testdata/stable/p/dagre/board.exp.json index f50e058e8..a4742e0b8 100644 --- a/e2etests/testdata/stable/p/dagre/board.exp.json +++ b/e2etests/testdata/stable/p/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "md", @@ -14,8 +15,8 @@ "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0D32B2", + "fill": "transparent", + "stroke": "#0A0F25", "shadow": false, "3d": false, "multiple": false, @@ -33,7 +34,7 @@ "language": "markdown", "color": "#0A0F25", "italic": false, - "bold": true, + "bold": false, "underline": false, "labelWidth": 1857, "labelHeight": 24, diff --git a/e2etests/testdata/stable/p/dagre/sketch.exp.svg b/e2etests/testdata/stable/p/dagre/sketch.exp.svg index fab012eab..6aba7026c 100644 --- a/e2etests/testdata/stable/p/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/p/dagre/sketch.exp.svg @@ -277,6 +277,7 @@ width="2057" height="676" viewBox="-100 -100 2057 676">

                                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 xyz hello - +xyz hello + xyz hello - - +xyz hello + + an actor with a really long label that will break everythinganactorwithareallylonglabelthatwillbreakeverythingsimplea short onefar awaywhat if there were no labels between this actor and the previous one shortlong label for testing purposes and it must be really, really longshortthis should span many actors lifelines so we know how it will look like when redering a long label over many actorslong label for testing purposes and it must be really, really long - +an actor with a really long label that will break everythinganactorwithareallylonglabelthatwillbreakeverythingsimplea short onefar awaywhat if there were no labels between this actor and the previous one shortlong label for testing purposes and it must be really, really longshortthis should span many actors lifelines so we know how it will look like when redering a long label over many actorslong label for testing purposes and it must be really, really long + diff --git a/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/board.exp.json index 2341f4127..085e70d8e 100644 --- a/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", diff --git a/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/sketch.exp.svg index b0be8b3b5..f0302fd8e 100644 --- a/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_actor_distance/elk/sketch.exp.svg @@ -18,8 +18,8 @@ width="2692" height="1396" viewBox="-76 -26 2692 1396">an actor with a really long label that will break everythinganactorwithareallylonglabelthatwillbreakeverythingsimplea short onefar awaywhat if there were no labels between this actor and the previous one shortlong label for testing purposes and it must be really, really longshortthis should span many actors lifelines so we know how it will look like when redering a long label over many actorslong label for testing purposes and it must be really, really long - +an actor with a really long label that will break everythinganactorwithareallylonglabelthatwillbreakeverythingsimplea short onefar awaywhat if there were no labels between this actor and the previous one shortlong label for testing purposes and it must be really, really longshortthis should span many actors lifelines so we know how it will look like when redering a long label over many actorslong label for testing purposes and it must be really, really long + diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json index 9f037376a..42c5c962e 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", @@ -85,17 +86,17 @@ "id": "c", "type": "class", "pos": { - "x": 486, + "x": 481, "y": 74 }, - "width": 241, + "width": 302, "height": 184, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, "fill": "#EDF0FD", - "stroke": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -126,17 +127,20 @@ "italic": false, "bold": false, "underline": false, - "labelWidth": 71, + "labelWidth": 104, "labelHeight": 36, "labelPosition": "INSIDE_MIDDLE_CENTER", "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" }, { "id": "d", "type": "cloud", "pos": { - "x": 777, + "x": 833, "y": 132 }, "width": 174, @@ -176,7 +180,7 @@ "id": "e", "type": "code", "pos": { - "x": 1016, + "x": 1072, "y": 188 }, "width": 196, @@ -216,7 +220,7 @@ "id": "f", "type": "cylinder", "pos": { - "x": 1289, + "x": 1345, "y": 132 }, "width": 150, @@ -256,7 +260,7 @@ "id": "g", "type": "diamond", "pos": { - "x": 1539, + "x": 1595, "y": 132 }, "width": 150, @@ -296,7 +300,7 @@ "id": "h", "type": "document", "pos": { - "x": 1789, + "x": 1845, "y": 132 }, "width": 150, @@ -336,7 +340,7 @@ "id": "i", "type": "hexagon", "pos": { - "x": 2025, + "x": 2082, "y": 132 }, "width": 177, @@ -376,7 +380,7 @@ "id": "j", "type": "image", "pos": { - "x": 2289, + "x": 2345, "y": 109 }, "width": 150, @@ -427,7 +431,7 @@ "id": "k", "type": "oval", "pos": { - "x": 2539, + "x": 2595, "y": 132 }, "width": 150, @@ -467,7 +471,7 @@ "id": "l", "type": "package", "pos": { - "x": 2789, + "x": 2845, "y": 132 }, "width": 150, @@ -507,7 +511,7 @@ "id": "m", "type": "page", "pos": { - "x": 3027, + "x": 3084, "y": 132 }, "width": 173, @@ -547,7 +551,7 @@ "id": "n", "type": "parallelogram", "pos": { - "x": 3275, + "x": 3332, "y": 116 }, "width": 177, @@ -587,7 +591,7 @@ "id": "o", "type": "person", "pos": { - "x": 3538, + "x": 3595, "y": 79 }, "width": 151, @@ -627,7 +631,7 @@ "id": "p", "type": "queue", "pos": { - "x": 3784, + "x": 3841, "y": 132 }, "width": 159, @@ -667,7 +671,7 @@ "id": "q", "type": "rectangle", "pos": { - "x": 4032, + "x": 4089, "y": 95 }, "width": 163, @@ -707,7 +711,7 @@ "id": "r", "type": "step", "pos": { - "x": 4260, + "x": 4317, "y": 132 }, "width": 207, @@ -747,7 +751,7 @@ "id": "s", "type": "stored_data", "pos": { - "x": 4539, + "x": 4595, "y": 132 }, "width": 150, @@ -787,17 +791,17 @@ "id": "t", "type": "sql_table", "pos": { - "x": 4759, + "x": 4840, "y": 150 }, - "width": 210, + "width": 161, "height": 108, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, "fill": "#EDF0FD", - "stroke": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -810,14 +814,58 @@ "methods": null, "columns": [ { - "name": "id", - "type": "int", + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "name", - "type": "varchar", + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "varchar", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 64, + "labelHeight": 26 + }, "constraint": "", "reference": "" } @@ -834,7 +882,10 @@ "labelHeight": 36, "labelPosition": "INSIDE_MIDDLE_CENTER", "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" } ], "connections": [ @@ -907,7 +958,7 @@ "y": 518 }, { - "x": 606.5, + "x": 632, "y": 518 } ], @@ -942,11 +993,11 @@ "labelPercentage": 0, "route": [ { - "x": 606.5, + "x": 632, "y": 648 }, { - "x": 864, + "x": 920, "y": 648 } ], @@ -981,11 +1032,11 @@ "labelPercentage": 0, "route": [ { - "x": 864, + "x": 920, "y": 778 }, { - "x": 1114, + "x": 1170, "y": 778 } ], @@ -1020,11 +1071,11 @@ "labelPercentage": 0, "route": [ { - "x": 1114, + "x": 1170, "y": 908 }, { - "x": 1364, + "x": 1420, "y": 908 } ], @@ -1059,11 +1110,11 @@ "labelPercentage": 0, "route": [ { - "x": 1364, + "x": 1420, "y": 1038 }, { - "x": 1614, + "x": 1670, "y": 1038 } ], @@ -1098,11 +1149,11 @@ "labelPercentage": 0, "route": [ { - "x": 1614, + "x": 1670, "y": 1168 }, { - "x": 1864, + "x": 1920, "y": 1168 } ], @@ -1137,11 +1188,11 @@ "labelPercentage": 0, "route": [ { - "x": 1864, + "x": 1920, "y": 1298 }, { - "x": 2113.5, + "x": 2170.5, "y": 1298 } ], @@ -1176,11 +1227,11 @@ "labelPercentage": 0, "route": [ { - "x": 2113.5, + "x": 2170.5, "y": 1428 }, { - "x": 2364, + "x": 2420, "y": 1428 } ], @@ -1215,11 +1266,11 @@ "labelPercentage": 0, "route": [ { - "x": 2364, + "x": 2420, "y": 1558 }, { - "x": 2614, + "x": 2670, "y": 1558 } ], @@ -1254,11 +1305,11 @@ "labelPercentage": 0, "route": [ { - "x": 2614, + "x": 2670, "y": 1688 }, { - "x": 2864, + "x": 2920, "y": 1688 } ], @@ -1293,11 +1344,11 @@ "labelPercentage": 0, "route": [ { - "x": 2864, + "x": 2920, "y": 1818 }, { - "x": 3113.5, + "x": 3170.5, "y": 1818 } ], @@ -1332,11 +1383,11 @@ "labelPercentage": 0, "route": [ { - "x": 3113.5, + "x": 3170.5, "y": 1948 }, { - "x": 3363.5, + "x": 3420.5, "y": 1948 } ], @@ -1371,11 +1422,11 @@ "labelPercentage": 0, "route": [ { - "x": 3363.5, + "x": 3420.5, "y": 2078 }, { - "x": 3613.5, + "x": 3670.5, "y": 2078 } ], @@ -1410,11 +1461,11 @@ "labelPercentage": 0, "route": [ { - "x": 3613.5, + "x": 3670.5, "y": 2208 }, { - "x": 3863.5, + "x": 3920.5, "y": 2208 } ], @@ -1449,11 +1500,11 @@ "labelPercentage": 0, "route": [ { - "x": 3863.5, + "x": 3920.5, "y": 2338 }, { - "x": 4113.5, + "x": 4170.5, "y": 2338 } ], @@ -1488,11 +1539,11 @@ "labelPercentage": 0, "route": [ { - "x": 4113.5, + "x": 4170.5, "y": 2468 }, { - "x": 4363.5, + "x": 4420.5, "y": 2468 } ], @@ -1527,11 +1578,11 @@ "labelPercentage": 0, "route": [ { - "x": 4363.5, + "x": 4420.5, "y": 2598 }, { - "x": 4614, + "x": 4670, "y": 2598 } ], @@ -1566,11 +1617,11 @@ "labelPercentage": 0, "route": [ { - "x": 4614, + "x": 4670, "y": 2728 }, { - "x": 4864, + "x": 4920.5, "y": 2728 } ], @@ -1683,11 +1734,11 @@ "labelPercentage": 0, "route": [ { - "x": 606.5, + "x": 632, "y": 258 }, { - "x": 606.5, + "x": 632, "y": 2858 } ], @@ -1722,11 +1773,11 @@ "labelPercentage": 0, "route": [ { - "x": 864, + "x": 920, "y": 258 }, { - "x": 864, + "x": 920, "y": 2858 } ], @@ -1761,11 +1812,11 @@ "labelPercentage": 0, "route": [ { - "x": 1114, + "x": 1170, "y": 258 }, { - "x": 1114, + "x": 1170, "y": 2858 } ], @@ -1800,11 +1851,11 @@ "labelPercentage": 0, "route": [ { - "x": 1364, + "x": 1420, "y": 258 }, { - "x": 1364, + "x": 1420, "y": 2858 } ], @@ -1839,11 +1890,11 @@ "labelPercentage": 0, "route": [ { - "x": 1614, + "x": 1670, "y": 258 }, { - "x": 1614, + "x": 1670, "y": 2858 } ], @@ -1878,11 +1929,11 @@ "labelPercentage": 0, "route": [ { - "x": 1864, + "x": 1920, "y": 258 }, { - "x": 1864, + "x": 1920, "y": 2858 } ], @@ -1917,11 +1968,11 @@ "labelPercentage": 0, "route": [ { - "x": 2113.5, + "x": 2170.5, "y": 258 }, { - "x": 2113.5, + "x": 2170.5, "y": 2858 } ], @@ -1956,11 +2007,11 @@ "labelPercentage": 0, "route": [ { - "x": 2364, + "x": 2420, "y": 263 }, { - "x": 2364, + "x": 2420, "y": 2858 } ], @@ -1995,11 +2046,11 @@ "labelPercentage": 0, "route": [ { - "x": 2614, + "x": 2670, "y": 258 }, { - "x": 2614, + "x": 2670, "y": 2858 } ], @@ -2034,11 +2085,11 @@ "labelPercentage": 0, "route": [ { - "x": 2864, + "x": 2920, "y": 258 }, { - "x": 2864, + "x": 2920, "y": 2858 } ], @@ -2073,11 +2124,11 @@ "labelPercentage": 0, "route": [ { - "x": 3113.5, + "x": 3170.5, "y": 258 }, { - "x": 3113.5, + "x": 3170.5, "y": 2858 } ], @@ -2112,11 +2163,11 @@ "labelPercentage": 0, "route": [ { - "x": 3363.5, + "x": 3420.5, "y": 258 }, { - "x": 3363.5, + "x": 3420.5, "y": 2858 } ], @@ -2151,11 +2202,11 @@ "labelPercentage": 0, "route": [ { - "x": 3613.5, + "x": 3670.5, "y": 263 }, { - "x": 3613.5, + "x": 3670.5, "y": 2858 } ], @@ -2190,11 +2241,11 @@ "labelPercentage": 0, "route": [ { - "x": 3863.5, + "x": 3920.5, "y": 258 }, { - "x": 3863.5, + "x": 3920.5, "y": 2858 } ], @@ -2229,11 +2280,11 @@ "labelPercentage": 0, "route": [ { - "x": 4113.5, + "x": 4170.5, "y": 258 }, { - "x": 4113.5, + "x": 4170.5, "y": 2858 } ], @@ -2268,11 +2319,11 @@ "labelPercentage": 0, "route": [ { - "x": 4363.5, + "x": 4420.5, "y": 258 }, { - "x": 4363.5, + "x": 4420.5, "y": 2858 } ], @@ -2307,11 +2358,11 @@ "labelPercentage": 0, "route": [ { - "x": 4614, + "x": 4670, "y": 258 }, { - "x": 4614, + "x": 4670, "y": 2858 } ], @@ -2346,11 +2397,11 @@ "labelPercentage": 0, "route": [ { - "x": 4864, + "x": 4920.5, "y": 258 }, { - "x": 4864, + "x": 4920.5, "y": 2858 } ], diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg index 4823fd471..2b86d5324 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ a labelblabelsa class+ +public() bool +void- +private() int +voidcloudyyyy:= 5 := a + 7 -fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid -int -name -varchar - result := callThisFunction(obj, 5) midthis sideother side - +fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid +int +name +varchar + result := callThisFunction(obj, 5) midthis sideother side + - + a labelblabelsa class+ -public() bool -void- -private() int -voidcloudyyyy:= 5 +a labelblabelsa class+ +public() bool +void- +private() int +voidcloudyyyy:= 5 := a + 7 -fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid -int -name -varchar - result := callThisFunction(obj, 5) midthis sideother side - +fmt.Printf("%d", b)cyldiadocssix cornersa random iconoverpackdocs pagetoohard o saysinglepersona queuea squarea step at a timedatausersid +int +name +varchar + result := callThisFunction(obj, 5) midthis sideother side + - + alicebob what does it mean to be well-adjustedThe ability to play bridge or golf as if they were games - +alicebob what does it mean to be well-adjustedThe ability to play bridge or golf as if they were games + alicebob what does it mean to be well-adjustedThe ability to play bridge or golf as if they were games - +alicebob what does it mean to be well-adjustedThe ability to play bridge or golf as if they were games + abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note - - - - - - +abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note + + + + + + diff --git a/e2etests/testdata/stable/sequence_diagram_groups/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_groups/elk/board.exp.json index 4535a99dd..776b8ddfb 100644 --- a/e2etests/testdata/stable/sequence_diagram_groups/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_groups/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", @@ -197,6 +198,7 @@ "underline": false, "labelWidth": 30, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 1 }, @@ -236,6 +238,7 @@ "underline": false, "labelWidth": 57, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 1 }, @@ -275,6 +278,7 @@ "underline": false, "labelWidth": 78, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 2 }, @@ -314,6 +318,7 @@ "underline": false, "labelWidth": 58, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 1 }, @@ -393,6 +398,7 @@ "underline": false, "labelWidth": 38, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 1 }, diff --git a/e2etests/testdata/stable/sequence_diagram_groups/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_groups/elk/sketch.exp.svg index cac917156..d3ed45a1a 100644 --- a/e2etests/testdata/stable/sequence_diagram_groups/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_groups/elk/sketch.exp.svg @@ -18,13 +18,13 @@ width="1147" height="2268" viewBox="-76 -26 1147 2268">abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note - - - - - - +abcdggggroup 1group bchoonested guy lalaeyokayokaywhat would arnold saythis note + + + + + + diff --git a/e2etests/testdata/stable/sequence_diagram_long_note/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_long_note/dagre/board.exp.json index 8890d73c9..3c36498bb 100644 --- a/e2etests/testdata/stable/sequence_diagram_long_note/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_long_note/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "b", diff --git a/e2etests/testdata/stable/sequence_diagram_long_note/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_long_note/dagre/sketch.exp.svg index e19ca892f..8cc670b3d 100644 --- a/e2etests/testdata/stable/sequence_diagram_long_note/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_long_note/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="850" height="1162" viewBox="-263 -26 850 1162">ba a note here to remember that padding must consider notes toojustalongnotehereba a note here to remember that padding must consider notes toojustalongnotehereabjust an actorthis is a message groupaltand this is a nested message groupcase 1case 2case 3case 4what about more nestingcrazy townwhoa a notea note here to remember that padding must consider notes toojustalongnotehere - - - - - - - - - - - +abjust an actorthis is a message groupaltand this is a nested message groupcase 1case 2case 3case 4what about more nestingcrazy townwhoa a notea note here to remember that padding must consider notes toojustalongnotehere + + + + + + + + + + + abjust an actorthis is a message groupaltand this is a nested message groupcase 1case 2case 3case 4what about more nestingcrazy townwhoa a notea note here to remember that padding must consider notes toojustalongnotehere - - - - - - - - - - - +abjust an actorthis is a message groupaltand this is a nested message groupcase 1case 2case 3case 4what about more nestingcrazy townwhoa a notea note here to remember that padding must consider notes toojustalongnotehere + + + + + + + + + + + scoreritemResponseitemessayRubricconceptitemOutcome scoreritemResponseitemessayRubricconceptitemOutcome abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier - +abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier + abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier - +abcd okayexplanationanother explanationSome one who believes imaginary things appear right before your i's.The earth is like a tiny grain of sand, only much, much heavier + How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place - - +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place + + diff --git a/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json index 0f9eb51ae..3945edf3f 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "How this is rendered", @@ -477,6 +478,7 @@ "underline": false, "labelWidth": 185, "labelHeight": 26, + "labelPosition": "INSIDE_TOP_LEFT", "zIndex": 3, "level": 2 }, diff --git a/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg index 0d904f1d9..5f4dd359a 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg @@ -18,9 +18,9 @@ width="2447" height="2536" viewBox="-88 -88 2447 2536">How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place - - +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutonly if root is not sequence 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVGmeasurements also take place + + diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json index 4c8d9cd09..54d51adf1 100644 --- a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg index b0d237070..f947663cd 100644 --- a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg @@ -18,8 +18,8 @@ width="696" height="1366" viewBox="-76 -26 696 1366">ab a self edge herebetween actorsto descendantto deeper descendantto parentactor - +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json index 4c8d9cd09..54d51adf1 100644 --- a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg index b0d237070..f947663cd 100644 --- a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg @@ -18,8 +18,8 @@ width="696" height="1366" viewBox="-76 -26 696 1366">ab a self edge herebetween actorsto descendantto deeper descendantto parentactor - +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + diff --git a/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json index 5daa335fc..fb0887203 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "alice", diff --git a/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg index 608019d3a..d0e6006a2 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg @@ -18,8 +18,8 @@ width="1598" height="1868" viewBox="-76 -26 1598 1868">AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response - +AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response + diff --git a/e2etests/testdata/stable/sequence_diagram_simple/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_simple/elk/board.exp.json index 5daa335fc..fb0887203 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_simple/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "alice", diff --git a/e2etests/testdata/stable/sequence_diagram_simple/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_simple/elk/sketch.exp.svg index 608019d3a..d0e6006a2 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_simple/elk/sketch.exp.svg @@ -18,8 +18,8 @@ width="1598" height="1868" viewBox="-76 -26 1598 1868">AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response - +AlicelinebreakerBobdbqueueanoddservicewithanameinmultiple lines Authentication Requestmake request for something that is quite far away and requires a really long label to take all the space between the objectsvalidate credentialsAuthentication ResponseAnother authentication Requestdo it later storedAnother authentication Response + diff --git a/e2etests/testdata/stable/sequence_diagram_span/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_span/dagre/board.exp.json index af401f123..37a77eab3 100644 --- a/e2etests/testdata/stable/sequence_diagram_span/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_span/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "scorer", diff --git a/e2etests/testdata/stable/sequence_diagram_span/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_span/dagre/sketch.exp.svg index 470d35fc6..aa3321ec2 100644 --- a/e2etests/testdata/stable/sequence_diagram_span/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_span/dagre/sketch.exp.svg @@ -18,8 +18,8 @@ width="1624" height="2146" viewBox="-76 -26 1624 2146">scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - +scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + diff --git a/e2etests/testdata/stable/sequence_diagram_span/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_span/elk/board.exp.json index af401f123..37a77eab3 100644 --- a/e2etests/testdata/stable/sequence_diagram_span/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_span/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "scorer", diff --git a/e2etests/testdata/stable/sequence_diagram_span/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_span/elk/sketch.exp.svg index 470d35fc6..aa3321ec2 100644 --- a/e2etests/testdata/stable/sequence_diagram_span/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_span/elk/sketch.exp.svg @@ -18,8 +18,8 @@ width="1624" height="2146" viewBox="-76 -26 1624 2146">scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) - +scoreritemResponseitemessayRubricconceptitemOutcome getItem() itemgetRubric()rubricapplyTo(essayResp)match(essayResponse)scorenewgetNormalMinimum()getNormalMaximum()setScore(score)setFeedback(missingConcepts) + diff --git a/e2etests/testdata/stable/sequence_diagrams/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagrams/dagre/board.exp.json index fb7550f69..5844c5a26 100644 --- a/e2etests/testdata/stable/sequence_diagrams/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagrams/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a_shape", diff --git a/e2etests/testdata/stable/sequence_diagrams/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagrams/dagre/sketch.exp.svg index 1a8465908..db69a01ce 100644 --- a/e2etests/testdata/stable/sequence_diagrams/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagrams/dagre/sketch.exp.svg @@ -18,8 +18,8 @@ width="3402" height="4269" viewBox="-100 -100 3402 4269">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/sequence_diagrams/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagrams/elk/board.exp.json index 3ed298e02..1f4879ef4 100644 --- a/e2etests/testdata/stable/sequence_diagrams/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagrams/elk/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a_shape", @@ -46,7 +47,7 @@ "type": "sequence_diagram", "pos": { "x": 12, - "y": 364 + "y": 363 }, "width": 1472, "height": 2080, @@ -165,7 +166,7 @@ "id": "finally", "type": "queue", "pos": { - "x": 1504, + "x": 1503, "y": 2626 }, "width": 1623, @@ -205,7 +206,7 @@ "id": "finally.sequence", "type": "sequence_diagram", "pos": { - "x": 1579, + "x": 1578, "y": 2701 }, "width": 1473, @@ -245,7 +246,7 @@ "id": "finally.sequence.scorer", "type": "", "pos": { - "x": 1603, + "x": 1602, "y": 2806 }, "width": 150, @@ -285,7 +286,7 @@ "id": "finally.sequence.concept", "type": "", "pos": { - "x": 1848, + "x": 1847, "y": 2806 }, "width": 160, @@ -325,7 +326,7 @@ "id": "finally.sequence.essayRubric", "type": "", "pos": { - "x": 2085, + "x": 2084, "y": 2806 }, "width": 186, @@ -365,7 +366,7 @@ "id": "finally.sequence.item", "type": "", "pos": { - "x": 2353, + "x": 2352, "y": 2806 }, "width": 150, @@ -405,7 +406,7 @@ "id": "finally.sequence.itemOutcome", "type": "", "pos": { - "x": 2580, + "x": 2579, "y": 2806 }, "width": 197, @@ -445,7 +446,7 @@ "id": "finally.sequence.itemResponse", "type": "", "pos": { - "x": 2828, + "x": 2827, "y": 2806 }, "width": 200, @@ -486,7 +487,7 @@ "type": "", "pos": { "x": 36, - "y": 474 + "y": 473 }, "width": 150, "height": 126, @@ -526,7 +527,7 @@ "type": "rectangle", "pos": { "x": 105, - "y": 714 + "y": 713 }, "width": 12, "height": 1592, @@ -565,7 +566,7 @@ "type": "", "pos": { "x": 261, - "y": 474 + "y": 473 }, "width": 200, "height": 126, @@ -605,7 +606,7 @@ "type": "rectangle", "pos": { "x": 355, - "y": 714 + "y": 713 }, "width": 12, "height": 162, @@ -644,7 +645,7 @@ "type": "", "pos": { "x": 536, - "y": 474 + "y": 473 }, "width": 150, "height": 126, @@ -684,7 +685,7 @@ "type": "rectangle", "pos": { "x": 605, - "y": 974 + "y": 973 }, "width": 12, "height": 162, @@ -723,7 +724,7 @@ "type": "", "pos": { "x": 768, - "y": 474 + "y": 473 }, "width": 186, "height": 126, @@ -763,7 +764,7 @@ "type": "rectangle", "pos": { "x": 855, - "y": 1234 + "y": 1233 }, "width": 12, "height": 422, @@ -802,7 +803,7 @@ "type": "rectangle", "pos": { "x": 851, - "y": 1364 + "y": 1363 }, "width": 20, "height": 162, @@ -841,7 +842,7 @@ "type": "", "pos": { "x": 1031, - "y": 474 + "y": 473 }, "width": 160, "height": 126, @@ -881,7 +882,7 @@ "type": "rectangle", "pos": { "x": 1105, - "y": 1494 + "y": 1493 }, "width": 12, "height": 80, @@ -920,7 +921,7 @@ "type": "", "pos": { "x": 1263, - "y": 474 + "y": 473 }, "width": 197, "height": 126, @@ -960,7 +961,7 @@ "type": "rectangle", "pos": { "x": 1355, - "y": 1754 + "y": 1753 }, "width": 12, "height": 80, @@ -999,7 +1000,7 @@ "type": "rectangle", "pos": { "x": 605, - "y": 1884 + "y": 1883 }, "width": 12, "height": 80, @@ -1038,7 +1039,7 @@ "type": "rectangle", "pos": { "x": 605, - "y": 2014 + "y": 2013 }, "width": 12, "height": 80, @@ -1077,7 +1078,7 @@ "type": "rectangle", "pos": { "x": 1355, - "y": 2144 + "y": 2143 }, "width": 12, "height": 80, @@ -1116,7 +1117,7 @@ "type": "rectangle", "pos": { "x": 1355, - "y": 2274 + "y": 2273 }, "width": 12, "height": 80, @@ -1863,7 +1864,7 @@ "id": "finally.sequence.itemResponse.a", "type": "rectangle", "pos": { - "x": 2922, + "x": 2921, "y": 3046 }, "width": 12, @@ -1902,7 +1903,7 @@ "id": "finally.sequence.item.a", "type": "rectangle", "pos": { - "x": 2422, + "x": 2421, "y": 3030 }, "width": 12, @@ -1941,7 +1942,7 @@ "id": "finally.sequence.item.a.b", "type": "rectangle", "pos": { - "x": 2418, + "x": 2417, "y": 3046 }, "width": 20, @@ -1980,7 +1981,7 @@ "id": "finally.sequence.essayRubric.a", "type": "rectangle", "pos": { - "x": 2172, + "x": 2171, "y": 3144 }, "width": 12, @@ -2019,7 +2020,7 @@ "id": "finally.sequence.essayRubric.a.b", "type": "rectangle", "pos": { - "x": 2168, + "x": 2167, "y": 3160 }, "width": 20, @@ -2058,7 +2059,7 @@ "id": "finally.sequence.essayRubric.a.b.c", "type": "rectangle", "pos": { - "x": 2164, + "x": 2163, "y": 3176 }, "width": 28, @@ -2097,7 +2098,7 @@ "id": "finally.sequence.concept.a", "type": "rectangle", "pos": { - "x": 1922, + "x": 1921, "y": 3258 }, "width": 12, @@ -2136,7 +2137,7 @@ "id": "finally.sequence.concept.a.b", "type": "rectangle", "pos": { - "x": 1918, + "x": 1917, "y": 3274 }, "width": 20, @@ -2175,7 +2176,7 @@ "id": "finally.sequence.concept.a.b.c", "type": "rectangle", "pos": { - "x": 1914, + "x": 1913, "y": 3290 }, "width": 28, @@ -2214,7 +2215,7 @@ "id": "finally.sequence.concept.a.b.c.d", "type": "rectangle", "pos": { - "x": 1910, + "x": 1909, "y": 3306 }, "width": 36, @@ -2448,7 +2449,7 @@ "id": "finally.sequence.scorer.abc", "type": "rectangle", "pos": { - "x": 1672, + "x": 1671, "y": 3696 }, "width": 12, @@ -2487,7 +2488,7 @@ "id": "finally.sequence.itemResponse.c", "type": "rectangle", "pos": { - "x": 2922, + "x": 2921, "y": 3956 }, "width": 12, @@ -2551,11 +2552,11 @@ "route": [ { "x": 117, - "y": 730 + "y": 729.5 }, { "x": 355, - "y": 730 + "y": 729.5 } ], "animated": false, @@ -2590,11 +2591,11 @@ "route": [ { "x": 117, - "y": 860 + "y": 859.5 }, { "x": 355, - "y": 860 + "y": 859.5 } ], "animated": false, @@ -2629,11 +2630,11 @@ "route": [ { "x": 117, - "y": 990 + "y": 989.5 }, { "x": 605, - "y": 990 + "y": 989.5 } ], "animated": false, @@ -2668,11 +2669,11 @@ "route": [ { "x": 117, - "y": 1120 + "y": 1119.5 }, { "x": 605, - "y": 1120 + "y": 1119.5 } ], "animated": false, @@ -2707,11 +2708,11 @@ "route": [ { "x": 117, - "y": 1250 + "y": 1249.5 }, { "x": 855, - "y": 1250 + "y": 1249.5 } ], "animated": false, @@ -2746,11 +2747,11 @@ "route": [ { "x": 361, - "y": 1380 + "y": 1379.5 }, { "x": 851, - "y": 1380 + "y": 1379.5 } ], "animated": false, @@ -2785,11 +2786,11 @@ "route": [ { "x": 871, - "y": 1510 + "y": 1509.5 }, { "x": 1105, - "y": 1510 + "y": 1509.5 } ], "animated": false, @@ -2824,11 +2825,11 @@ "route": [ { "x": 111, - "y": 1640 + "y": 1639.5 }, { "x": 855, - "y": 1640 + "y": 1639.5 } ], "animated": false, @@ -2863,11 +2864,11 @@ "route": [ { "x": 117, - "y": 1770 + "y": 1769.5 }, { "x": 1355.5, - "y": 1770 + "y": 1769.5 } ], "animated": false, @@ -2902,11 +2903,11 @@ "route": [ { "x": 117, - "y": 1900 + "y": 1899.5 }, { "x": 605, - "y": 1900 + "y": 1899.5 } ], "animated": false, @@ -2941,11 +2942,11 @@ "route": [ { "x": 117, - "y": 2030 + "y": 2029.5 }, { "x": 605, - "y": 2030 + "y": 2029.5 } ], "animated": false, @@ -2980,11 +2981,11 @@ "route": [ { "x": 117, - "y": 2160 + "y": 2159.5 }, { "x": 1355.5, - "y": 2160 + "y": 2159.5 } ], "animated": false, @@ -3019,11 +3020,11 @@ "route": [ { "x": 117, - "y": 2290 + "y": 2289.5 }, { "x": 1355.5, - "y": 2290 + "y": 2289.5 } ], "animated": false, @@ -3565,7 +3566,7 @@ "route": [ { "x": 748, - "y": 176 + "y": 165 }, { "x": 748, @@ -3729,7 +3730,7 @@ "route": [ { "x": 830, - "y": 176 + "y": 165 }, { "x": 830, @@ -3748,7 +3749,7 @@ "y": 2571 }, { - "x": 2325, + "x": 2326, "y": 2626 } ], @@ -3783,11 +3784,11 @@ "labelPercentage": 0, "route": [ { - "x": 2922, + "x": 2921.5, "y": 3062 }, { - "x": 2438, + "x": 2437.5, "y": 3062 } ], @@ -3822,11 +3823,11 @@ "labelPercentage": 0, "route": [ { - "x": 2418, + "x": 2417.5, "y": 3192 }, { - "x": 2192, + "x": 2191.5, "y": 3192 } ], @@ -3861,11 +3862,11 @@ "labelPercentage": 0, "route": [ { - "x": 2164, + "x": 2163.5, "y": 3322 }, { - "x": 1946, + "x": 1945.5, "y": 3322 } ], @@ -3900,11 +3901,11 @@ "labelPercentage": 0, "route": [ { - "x": 2422, + "x": 2421.5, "y": 3452 }, { - "x": 2188, + "x": 2187.5, "y": 3452 } ], @@ -3939,11 +3940,11 @@ "labelPercentage": 0, "route": [ { - "x": 1946, + "x": 1945.5, "y": 3582 }, { - "x": 2656.5, + "x": 2656, "y": 3582 } ], @@ -3978,11 +3979,11 @@ "labelPercentage": 0, "route": [ { - "x": 1684, + "x": 1683.5, "y": 3712 }, { - "x": 2422, + "x": 2421.5, "y": 3712 } ], @@ -4017,11 +4018,11 @@ "labelPercentage": 0, "route": [ { - "x": 2656.5, + "x": 2656, "y": 3842 }, { - "x": 1678, + "x": 1677.5, "y": 3842 } ], @@ -4056,11 +4057,11 @@ "labelPercentage": 0, "route": [ { - "x": 1678, + "x": 1677.5, "y": 3972 }, { - "x": 2922, + "x": 2921.5, "y": 3972 } ], @@ -4096,11 +4097,11 @@ "route": [ { "x": 111, - "y": 600 + "y": 599.5 }, { "x": 111, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4135,11 +4136,11 @@ "route": [ { "x": 361, - "y": 600 + "y": 599.5 }, { "x": 361, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4174,11 +4175,11 @@ "route": [ { "x": 611, - "y": 600 + "y": 599.5 }, { "x": 611, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4213,11 +4214,11 @@ "route": [ { "x": 861, - "y": 600 + "y": 599.5 }, { "x": 861, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4252,11 +4253,11 @@ "route": [ { "x": 1111, - "y": 600 + "y": 599.5 }, { "x": 1111, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4291,11 +4292,11 @@ "route": [ { "x": 1361.5, - "y": 600 + "y": 599.5 }, { "x": 1361.5, - "y": 2420 + "y": 2419.5 } ], "animated": false, @@ -4563,11 +4564,11 @@ "labelPercentage": 0, "route": [ { - "x": 1678, + "x": 1677.5, "y": 2932 }, { - "x": 1678, + "x": 1677.5, "y": 4102 } ], @@ -4602,11 +4603,11 @@ "labelPercentage": 0, "route": [ { - "x": 1928, + "x": 1927.5, "y": 2932 }, { - "x": 1928, + "x": 1927.5, "y": 4102 } ], @@ -4641,11 +4642,11 @@ "labelPercentage": 0, "route": [ { - "x": 2178, + "x": 2177.5, "y": 2932 }, { - "x": 2178, + "x": 2177.5, "y": 4102 } ], @@ -4680,11 +4681,11 @@ "labelPercentage": 0, "route": [ { - "x": 2428, + "x": 2427.5, "y": 2932 }, { - "x": 2428, + "x": 2427.5, "y": 4102 } ], @@ -4719,11 +4720,11 @@ "labelPercentage": 0, "route": [ { - "x": 2678.5, + "x": 2678, "y": 2932 }, { - "x": 2678.5, + "x": 2678, "y": 4102 } ], @@ -4758,11 +4759,11 @@ "labelPercentage": 0, "route": [ { - "x": 2928, + "x": 2927.5, "y": 2932 }, { - "x": 2928, + "x": 2927.5, "y": 4102 } ], diff --git a/e2etests/testdata/stable/sequence_diagrams/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagrams/elk/sketch.exp.svg index b043d6fdd..7d5445201 100644 --- a/e2etests/testdata/stable/sequence_diagrams/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagrams/elk/sketch.exp.svg @@ -18,20 +18,20 @@ width="3324" height="4389" viewBox="-88 -88 3324 4389">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/board.exp.json b/e2etests/testdata/stable/sql_tables/dagre/board.exp.json index b6e0dfc1d..0fd37e978 100644 --- a/e2etests/testdata/stable/sql_tables/dagre/board.exp.json +++ b/e2etests/testdata/stable/sql_tables/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "users", @@ -8,14 +9,14 @@ "x": 0, "y": 0 }, - "width": 259, + "width": 268, "height": 216, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0A0F25", + "fill": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -28,33 +29,143 @@ "methods": null, "columns": [ { - "name": "id", - "type": "int", + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "orders.user_id" }, { - "name": "name", - "type": "string", + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "email", - "type": "string", + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "password", - "type": "string", + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "last_login", - "type": "datetime", - "constraint": "", + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "primary_key", "reference": "" } ], @@ -69,23 +180,26 @@ "labelWidth": 61, "labelHeight": 36, "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" }, { "id": "products", "type": "sql_table", "pos": { - "x": 319, + "x": 328, "y": 18 }, - "width": 290, + "width": 164, "height": 180, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0A0F25", + "fill": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -98,26 +212,114 @@ "methods": null, "columns": [ { - "name": "id", - "type": "int", + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "orders.product_id" }, { - "name": "price", - "type": "decimal", + "name": { + "label": "price", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 42, + "labelHeight": 26 + }, + "type": { + "label": "decimal", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 67, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "sku", - "type": "string", + "name": { + "label": "sku", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 29, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "name", - "type": "string", + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" } @@ -133,23 +335,26 @@ "labelWidth": 99, "labelHeight": 36, "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" }, { "id": "orders", "type": "sql_table", "pos": { - "x": 357, + "x": 328, "y": 316 }, - "width": 215, + "width": 164, "height": 144, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0A0F25", + "fill": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -162,20 +367,86 @@ "methods": null, "columns": [ { - "name": "id", - "type": "int", + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "user_id", - "type": "int", + "name": { + "label": "user_id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 61, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "product_id", - "type": "int", + "name": { + "label": "product_id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 91, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "" } @@ -191,23 +462,26 @@ "labelWidth": 74, "labelHeight": 36, "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" }, { "id": "shipments", "type": "sql_table", "pos": { - "x": 669, + "x": 552, "y": 18 }, - "width": 293, + "width": 244, "height": 180, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, - "fill": "#FFFFFF", - "stroke": "#0A0F25", + "fill": "#0A0F25", + "stroke": "#FFFFFF", "shadow": false, "3d": false, "multiple": false, @@ -220,26 +494,114 @@ "methods": null, "columns": [ { - "name": "id", - "type": "int", + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "order_id", - "type": "int", + "name": { + "label": "order_id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 71, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, "constraint": "", "reference": "orders.id" }, { - "name": "tracking_number", - "type": "string", + "name": { + "label": "tracking_number", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 146, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" }, { - "name": "status", - "type": "string", + "name": { + "label": "status", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 51, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, "constraint": "", "reference": "" } @@ -255,7 +617,10 @@ "labelWidth": 116, "labelHeight": 36, "zIndex": 0, - "level": 1 + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" } ], "connections": [ @@ -285,20 +650,20 @@ "labelPercentage": 0, "route": [ { - "x": 129.5, + "x": 134, "y": 216 }, { - "x": 129.5, + "x": 134, "y": 256 }, { - "x": 174.9, - "y": 282.55844544095663 + "x": 172.8, + "y": 283.2 }, { - "x": 356.5, - "y": 348.7922272047833 + "x": 328, + "y": 352 } ], "isCurve": true, @@ -333,19 +698,19 @@ "labelPercentage": 0, "route": [ { - "x": 464, + "x": 410, "y": 198 }, { - "x": 464, + "x": 410, "y": 252.4 }, { - "x": 464, + "x": 410, "y": 276 }, { - "x": 464, + "x": 410, "y": 316 } ], @@ -381,20 +746,20 @@ "labelPercentage": 0, "route": [ { - "x": 815.5, + "x": 674, "y": 198 }, { - "x": 815.5, + "x": 674, "y": 252.4 }, { - "x": 766.7, - "y": 283 + "x": 637.6, + "y": 282.8 }, { - "x": 571.5, - "y": 351 + "x": 492, + "y": 350 } ], "isCurve": true, diff --git a/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg b/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg index a953a362a..8b78b0f66 100644 --- a/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sql_tables/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ \ 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..551a775bb 100644 --- a/e2etests/testdata/stable/stylish/elk/board.exp.json +++ b/e2etests/testdata/stable/stylish/elk/board.exp.json @@ -1,11 +1,12 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "x", "type": "", "pos": { - "x": 13, + "x": 12, "y": 12 }, "width": 113, @@ -46,7 +47,7 @@ "type": "", "pos": { "x": 12, - "y": 238 + "y": 359 }, "width": 114, "height": 126, @@ -95,7 +96,8 @@ "strokeDash": 5, "strokeWidth": 2, "stroke": "green", - "label": "", + "fill": "lavender", + "label": "in style", "fontSize": 16, "fontFamily": "DEFAULT", "language": "", @@ -103,9 +105,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 +116,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..a3bbf4301 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/text_font_sizes/dagre/board.exp.json b/e2etests/testdata/stable/text_font_sizes/dagre/board.exp.json new file mode 100644 index 000000000..fdb475c2b --- /dev/null +++ b/e2etests/testdata/stable/text_font_sizes/dagre/board.exp.json @@ -0,0 +1,221 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "bear", + "type": "text", + "pos": { + "x": 143, + "y": 141 + }, + "width": 44, + "height": 28, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "bear", + "fontSize": 22, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 44, + "labelHeight": 28, + "zIndex": 0, + "level": 1 + }, + { + "id": "mama bear", + "type": "text", + "pos": { + "x": 0, + "y": 3 + }, + "width": 135, + "height": 36, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "mama bear", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 135, + "labelHeight": 36, + "zIndex": 0, + "level": 1 + }, + { + "id": "papa bear", + "type": "text", + "pos": { + "x": 195, + "y": 0 + }, + "width": 134, + "height": 41, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "papa bear", + "fontSize": 32, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": true, + "labelWidth": 134, + "labelHeight": 41, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(mama bear -> bear)[0]", + "src": "mama bear", + "srcArrow": "none", + "srcLabel": "", + "dst": "bear", + "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": 67.5, + "y": 39.5 + }, + { + "x": 67.5, + "y": 80.7 + }, + { + "x": 82.7, + "y": 101 + }, + { + "x": 143.5, + "y": 141 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(papa bear -> bear)[0]", + "src": "papa bear", + "srcArrow": "none", + "srcLabel": "", + "dst": "bear", + "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": 262, + "y": 41 + }, + { + "x": 262, + "y": 81 + }, + { + "x": 246.8, + "y": 101 + }, + { + "x": 186, + "y": 141 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/text_font_sizes/dagre/sketch.exp.svg b/e2etests/testdata/stable/text_font_sizes/dagre/sketch.exp.svg new file mode 100644 index 000000000..5c31b0803 --- /dev/null +++ b/e2etests/testdata/stable/text_font_sizes/dagre/sketch.exp.svg @@ -0,0 +1,805 @@ + +bearmama bearpapa bear + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/text_font_sizes/elk/board.exp.json b/e2etests/testdata/stable/text_font_sizes/elk/board.exp.json new file mode 100644 index 000000000..ae4e7c84e --- /dev/null +++ b/e2etests/testdata/stable/text_font_sizes/elk/board.exp.json @@ -0,0 +1,211 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "bear", + "type": "text", + "pos": { + "x": 64, + "y": 153 + }, + "width": 44, + "height": 28, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "bear", + "fontSize": 22, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 44, + "labelHeight": 28, + "zIndex": 0, + "level": 1 + }, + { + "id": "mama bear", + "type": "text", + "pos": { + "x": 12, + "y": 17 + }, + "width": 135, + "height": 36, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "mama bear", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 135, + "labelHeight": 36, + "zIndex": 0, + "level": 1 + }, + { + "id": "papa bear", + "type": "text", + "pos": { + "x": 167, + "y": 12 + }, + "width": 134, + "height": 41, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "papa bear", + "fontSize": 32, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": true, + "labelWidth": 134, + "labelHeight": 41, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(mama bear -> bear)[0]", + "src": "mama bear", + "srcArrow": "none", + "srcLabel": "", + "dst": "bear", + "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": 79.5, + "y": 53 + }, + { + "x": 79.5, + "y": 153 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(papa bear -> bear)[0]", + "src": "papa bear", + "srcArrow": "none", + "srcLabel": "", + "dst": "bear", + "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": 234, + "y": 53 + }, + { + "x": 234, + "y": 103 + }, + { + "x": 94.16666666666666, + "y": 103 + }, + { + "x": 94.16666666666666, + "y": 153 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/text_font_sizes/elk/sketch.exp.svg b/e2etests/testdata/stable/text_font_sizes/elk/sketch.exp.svg new file mode 100644 index 000000000..506609593 --- /dev/null +++ b/e2etests/testdata/stable/text_font_sizes/elk/sketch.exp.svg @@ -0,0 +1,805 @@ + +bearmama bearpapa bear + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/tooltips/dagre/board.exp.json b/e2etests/testdata/stable/tooltips/dagre/board.exp.json new file mode 100644 index 000000000..79b8db45b --- /dev/null +++ b/e2etests/testdata/stable/tooltips/dagre/board.exp.json @@ -0,0 +1,136 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "x", + "type": "", + "pos": { + "x": 1, + "y": 0 + }, + "width": 113, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "Total abstinence is easier than perfect moderation", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "x", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "y", + "type": "", + "pos": { + "x": 0, + "y": 226 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS!", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "y", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(x -> y)[0]", + "src": "x", + "srcArrow": "none", + "srcLabel": "", + "dst": "y", + "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": 57, + "y": 126 + }, + { + "x": 57, + "y": 166 + }, + { + "x": 57, + "y": 186 + }, + { + "x": 57, + "y": 226 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/tooltips/dagre/sketch.exp.svg b/e2etests/testdata/stable/tooltips/dagre/sketch.exp.svg new file mode 100644 index 000000000..083bb23f6 --- /dev/null +++ b/e2etests/testdata/stable/tooltips/dagre/sketch.exp.svg @@ -0,0 +1,61 @@ + +x + + + + + + + + + + + + +Total abstinence is easier than perfect moderationy + + + + + + + + + + + + +Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS! + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/tooltips/elk/board.exp.json b/e2etests/testdata/stable/tooltips/elk/board.exp.json new file mode 100644 index 000000000..24850e39e --- /dev/null +++ b/e2etests/testdata/stable/tooltips/elk/board.exp.json @@ -0,0 +1,127 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "x", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 113, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "Total abstinence is easier than perfect moderation", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "x", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "y", + "type": "", + "pos": { + "x": 12, + "y": 238 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS!", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "y", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(x -> y)[0]", + "src": "x", + "srcArrow": "none", + "srcLabel": "", + "dst": "y", + "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": 69, + "y": 138 + }, + { + "x": 69, + "y": 238 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/tooltips/elk/sketch.exp.svg b/e2etests/testdata/stable/tooltips/elk/sketch.exp.svg new file mode 100644 index 000000000..c14d91d31 --- /dev/null +++ b/e2etests/testdata/stable/tooltips/elk/sketch.exp.svg @@ -0,0 +1,61 @@ + +x + + + + + + + + + + + + +Total abstinence is easier than perfect moderationy + + + + + + + + + + + + +Gee, I feel kind of LIGHT in the head now, +knowing I can't make my satellite dish PAYMENTS! + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/transparent_3d/dagre/board.exp.json b/e2etests/testdata/stable/transparent_3d/dagre/board.exp.json index c19a78f8f..7927d10ea 100644 --- a/e2etests/testdata/stable/transparent_3d/dagre/board.exp.json +++ b/e2etests/testdata/stable/transparent_3d/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "cube", diff --git a/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg b/e2etests/testdata/stable/transparent_3d/dagre/sketch.exp.svg index ab997d9a1..2a8e698d3 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 -cube- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/unnamed_only_height/elk/board.exp.json b/e2etests/testdata/stable/unnamed_only_height/elk/board.exp.json new file mode 100644 index 000000000..1505d6b80 --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_height/elk/board.exp.json @@ -0,0 +1,538 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 12, + "y": 12 + }, + "width": 422, + "height": 512, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 119, + "y": 624 + }, + "width": 208, + "height": 512, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 59, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 125, + "y": 1236 + }, + "width": 196, + "height": 512, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + }, + { + "id": "package", + "type": "package", + "pos": { + "x": 173, + "y": 1848 + }, + "width": 100, + "height": 512, + "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": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "no height", + "type": "", + "pos": { + "x": 173, + "y": 2460 + }, + "width": 100, + "height": 100, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(class -> users)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 223, + "y": 524 + }, + { + "x": 223, + "y": 624 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(users -> code)[0]", + "src": "users", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 223, + "y": 1136 + }, + { + "x": 223, + "y": 1236 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(code -> package)[0]", + "src": "code", + "srcArrow": "none", + "srcLabel": "", + "dst": "package", + "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": 223, + "y": 1748 + }, + { + "x": 223, + "y": 1848 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(package -> no height)[0]", + "src": "package", + "srcArrow": "none", + "srcLabel": "", + "dst": "no height", + "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": 223, + "y": 2360 + }, + { + "x": 223, + "y": 2460 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/unnamed_only_height/elk/sketch.exp.svg b/e2etests/testdata/stable/unnamed_only_height/elk/sketch.exp.svg new file mode 100644 index 000000000..b9c8d3109 --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_height/elk/sketch.exp.svg @@ -0,0 +1,62 @@ + +- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/unnamed_only_width/dagre/board.exp.json b/e2etests/testdata/stable/unnamed_only_width/dagre/board.exp.json new file mode 100644 index 000000000..01bac4015 --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_width/dagre/board.exp.json @@ -0,0 +1,574 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 0, + "y": 0 + }, + "width": 512, + "height": 368, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 0, + "y": 468 + }, + "width": 512, + "height": 186, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 59, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 0, + "y": 754 + }, + "width": 512, + "height": 70, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + }, + { + "id": "package", + "type": "package", + "pos": { + "x": 0, + "y": 924 + }, + "width": 512, + "height": 100, + "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": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "no width", + "type": "", + "pos": { + "x": 206, + "y": 1124 + }, + "width": 100, + "height": 100, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(class -> users)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 256, + "y": 368 + }, + { + "x": 256, + "y": 408 + }, + { + "x": 256, + "y": 428 + }, + { + "x": 256, + "y": 468 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(users -> code)[0]", + "src": "users", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 256, + "y": 654 + }, + { + "x": 256, + "y": 694 + }, + { + "x": 256, + "y": 714 + }, + { + "x": 256, + "y": 754 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(code -> package)[0]", + "src": "code", + "srcArrow": "none", + "srcLabel": "", + "dst": "package", + "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": 256, + "y": 824 + }, + { + "x": 256, + "y": 864 + }, + { + "x": 256, + "y": 890.8 + }, + { + "x": 256, + "y": 958 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(package -> no width)[0]", + "src": "package", + "srcArrow": "none", + "srcLabel": "", + "dst": "no width", + "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": 256, + "y": 1024 + }, + { + "x": 256, + "y": 1064 + }, + { + "x": 256, + "y": 1084 + }, + { + "x": 256, + "y": 1124 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/unnamed_only_width/dagre/sketch.exp.svg b/e2etests/testdata/stable/unnamed_only_width/dagre/sketch.exp.svg new file mode 100644 index 000000000..86ddafb1b --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_width/dagre/sketch.exp.svg @@ -0,0 +1,62 @@ + +- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/unnamed_only_width/elk/board.exp.json b/e2etests/testdata/stable/unnamed_only_width/elk/board.exp.json new file mode 100644 index 000000000..6db26d5e7 --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_width/elk/board.exp.json @@ -0,0 +1,538 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "class", + "type": "class", + "pos": { + "x": 12, + "y": 12 + }, + "width": 512, + "height": 368, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 12, + "y": 480 + }, + "width": 512, + "height": 186, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 59, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 12, + "y": 766 + }, + "width": 512, + "height": 70, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + }, + { + "id": "package", + "type": "package", + "pos": { + "x": 12, + "y": 936 + }, + "width": 512, + "height": 100, + "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": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "no width", + "type": "", + "pos": { + "x": 218, + "y": 1136 + }, + "width": 100, + "height": 100, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(class -> users)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 268, + "y": 380 + }, + { + "x": 268, + "y": 480 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(users -> code)[0]", + "src": "users", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 268, + "y": 666 + }, + { + "x": 268, + "y": 766 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(code -> package)[0]", + "src": "code", + "srcArrow": "none", + "srcLabel": "", + "dst": "package", + "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": 268, + "y": 836 + }, + { + "x": 268, + "y": 970 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(package -> no width)[0]", + "src": "package", + "srcArrow": "none", + "srcLabel": "", + "dst": "no width", + "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": 268, + "y": 1036 + }, + { + "x": 268, + "y": 1136 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/unnamed_only_width/elk/sketch.exp.svg b/e2etests/testdata/stable/unnamed_only_width/elk/sketch.exp.svg new file mode 100644 index 000000000..b61d80937 --- /dev/null +++ b/e2etests/testdata/stable/unnamed_only_width/elk/sketch.exp.svg @@ -0,0 +1,62 @@ + +- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidid +int +name +string +email +string +password +string +last_login +datetime +:= 5 +:= a + 7 +fmt.Printf("%d", b) + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/us_map/dagre/board.exp.json b/e2etests/testdata/stable/us_map/dagre/board.exp.json index 6deece116..8cb41e19c 100644 --- a/e2etests/testdata/stable/us_map/dagre/board.exp.json +++ b/e2etests/testdata/stable/us_map/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "AK", diff --git a/e2etests/testdata/stable/us_map/dagre/sketch.exp.svg b/e2etests/testdata/stable/us_map/dagre/sketch.exp.svg index e2fa2bdca..1a0207fcd 100644 --- a/e2etests/testdata/stable/us_map/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/us_map/dagre/sketch.exp.svg @@ -18,7 +18,10 @@ width="3322" height="2812" viewBox="-100 -100 3322 2812">AKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTND + + +AKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTND + + +containerfirstsecond 1->2c->2 - +containerfirstsecond 1->2c->2 + containerfirstsecond 1->2c->2 - - - +containerfirstsecond 1->2c->2 + + + ninety ninesixty fourthirty twosixteeneightninety ninesixty fourthirty twosixteeneight + + +ninety ninesixty fourthirty twosixteeneightninety ninesixty fourthirty twosixteeneight + + +eightsixteenthirty twosixty fourninety nine twelvetwenty fourforty eighteighty one - - - - - +eightsixteenthirty twosixty fourninety nine twelvetwenty fourforty eighteighty one + + + + + eightsixteenthirty twosixty fourninety nine twelvetwenty fourforty eighteighty one - - - - - +eightsixteenthirty twosixty fourninety nine twelvetwenty fourforty eighteighty one + + + + + bacthis is a message groupand this is a nested message groupwhat about more nestingyoyo - - - - - - +bacthis is a message groupand this is a nested message groupwhat about more nestingyoyo + + + + + + bacthis is a message groupand this is a nested message groupwhat about more nestingyoyo - - - - - - +bacthis is a message groupand this is a nested message groupwhat about more nestingyoyo + + + + + + containerscloudtall cylinderusersid +int +name +string +email +string +password +string +last_login +datetime +class- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidmarkdown text expanded to 800x400:= 5 +:= a + 7 +fmt.Printf("%d", b):= 5 +:= a + 7 +fmt.Printf("%d", b)containercircle containerdiamond containeroval containerhexagon containerdiamondcirclehexagonoval + + + \ No newline at end of file diff --git a/e2etests/testdata/todo/shape_set_width_height/elk/board.exp.json b/e2etests/testdata/todo/shape_set_width_height/elk/board.exp.json new file mode 100644 index 000000000..61577b1dd --- /dev/null +++ b/e2etests/testdata/todo/shape_set_width_height/elk/board.exp.json @@ -0,0 +1,1097 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "containers", + "type": "", + "pos": { + "x": 12, + "y": 12 + }, + "width": 1322, + "height": 428, + "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": "containers", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 128, + "labelHeight": 41, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "containers.circle container", + "type": "oval", + "pos": { + "x": 87, + "y": 119 + }, + "width": 278, + "height": 214, + "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": "circle container", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 161, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "containers.circle container.diamond", + "type": "diamond", + "pos": { + "x": 162, + "y": 194 + }, + "width": 128, + "height": 64, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#CFD2DD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "diamond", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 68, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "containers.diamond container", + "type": "diamond", + "pos": { + "x": 385, + "y": 87 + }, + "width": 278, + "height": 278, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#CFD2DD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "diamond container", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 197, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "containers.diamond container.circle", + "type": "oval", + "pos": { + "x": 460, + "y": 162 + }, + "width": 128, + "height": 128, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "circle", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 44, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "containers.oval container", + "type": "oval", + "pos": { + "x": 683, + "y": 119 + }, + "width": 278, + "height": 214, + "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": "oval container", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 149, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "containers.oval container.hexagon", + "type": "hexagon", + "pos": { + "x": 758, + "y": 194 + }, + "width": 128, + "height": 64, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#DEE1EB", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "hexagon", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 65, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "containers.hexagon container", + "type": "hexagon", + "pos": { + "x": 981, + "y": 119 + }, + "width": 278, + "height": 214, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#DEE1EB", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "hexagon container", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 193, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "containers.hexagon container.oval", + "type": "oval", + "pos": { + "x": 1056, + "y": 194 + }, + "width": 128, + "height": 64, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "oval", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 36, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "cloud", + "type": "cloud", + "pos": { + "x": 1354, + "y": 184 + }, + "width": 512, + "height": 256, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "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": "cloud", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 45, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "tall cylinder", + "type": "cylinder", + "pos": { + "x": 1482, + "y": 1440 + }, + "width": 256, + "height": 512, + "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": "tall cylinder", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 91, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "users", + "type": "sql_table", + "pos": { + "x": 1210, + "y": 2052 + }, + "width": 800, + "height": 400, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "id", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 15, + "labelHeight": 26 + }, + "type": { + "label": "int", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 23, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "name", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "email", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 47, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "password", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 80, + "labelHeight": 26 + }, + "type": { + "label": "string", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + }, + { + "name": { + "label": "last_login", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 81, + "labelHeight": 26 + }, + "type": { + "label": "datetime", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 26 + }, + "constraint": "", + "reference": "" + } + ], + "label": "users", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 61, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "class", + "type": "class", + "pos": { + "x": 1210, + "y": 740 + }, + "width": 800, + "height": 400, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#0A0F25", + "stroke": "#FFFFFF", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "class", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 75, + "labelHeight": 36, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "#0D32B2", + "secondaryAccentColor": "#4A6FF3", + "neutralAccentColor": "#676C7E" + }, + { + "id": "text", + "type": "", + "pos": { + "x": 2030, + "y": 540 + }, + "width": 400, + "height": 800, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "markdown text expanded to 800x400", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 266, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "code", + "type": "code", + "pos": { + "x": 2030, + "y": 1546 + }, + "width": 400, + "height": 300, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + }, + { + "id": "small code", + "type": "code", + "pos": { + "x": 2132, + "y": 2052 + }, + "width": 196, + "height": 70, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 5\nb := a + 7\nfmt.Printf(\"%d\", b)", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 196, + "labelHeight": 70, + "zIndex": 0, + "level": 1 + }, + { + "id": "container", + "type": "", + "pos": { + "x": 2143, + "y": 314 + }, + "width": 174, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "container", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 74, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(cloud -> class)[0]", + "src": "cloud", + "srcArrow": "none", + "srcLabel": "", + "dst": "class", + "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": 1610, + "y": 440 + }, + { + "x": 1610, + "y": 740 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(class -> tall cylinder)[0]", + "src": "class", + "srcArrow": "none", + "srcLabel": "", + "dst": "tall cylinder", + "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": 1610, + "y": 1140 + }, + { + "x": 1610, + "y": 1440 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(tall cylinder -> users)[0]", + "src": "tall cylinder", + "srcArrow": "none", + "srcLabel": "", + "dst": "users", + "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": 1610, + "y": 1952 + }, + { + "x": 1610, + "y": 2052 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(container -> text)[0]", + "src": "container", + "srcArrow": "none", + "srcLabel": "", + "dst": "text", + "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": 2230, + "y": 440 + }, + { + "x": 2230, + "y": 540 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(text -> code)[0]", + "src": "text", + "srcArrow": "none", + "srcLabel": "", + "dst": "code", + "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": 2230, + "y": 1340 + }, + { + "x": 2230, + "y": 1546 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(code -> small code)[0]", + "src": "code", + "srcArrow": "none", + "srcLabel": "", + "dst": "small code", + "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": 2230, + "y": 1846 + }, + { + "x": 2230, + "y": 2052 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/todo/shape_set_width_height/elk/sketch.exp.svg b/e2etests/testdata/todo/shape_set_width_height/elk/sketch.exp.svg new file mode 100644 index 000000000..509a9bf1e --- /dev/null +++ b/e2etests/testdata/todo/shape_set_width_height/elk/sketch.exp.svg @@ -0,0 +1,71 @@ + +containerscloudtall cylinderusersid +int +name +string +email +string +password +string +last_login +datetime +class- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +voidmarkdown text expanded to 800x400:= 5 +:= a + 7 +fmt.Printf("%d", b):= 5 +:= a + 7 +fmt.Printf("%d", b)containercircle containerdiamond containeroval containerhexagon containerdiamondcirclehexagonoval + + + \ No newline at end of file diff --git a/e2etests/testdata/todo/tall_edge_label/dagre/board.exp.json b/e2etests/testdata/todo/tall_edge_label/dagre/board.exp.json index 4050c622f..ab7a9450a 100644 --- a/e2etests/testdata/todo/tall_edge_label/dagre/board.exp.json +++ b/e2etests/testdata/todo/tall_edge_label/dagre/board.exp.json @@ -1,5 +1,6 @@ { "name": "", + "fontFamily": "SourceSansPro", "shapes": [ { "id": "a", @@ -46,7 +47,7 @@ "type": "", "pos": { "x": 0, - "y": 226 + "y": 299 }, "width": 113, "height": 126, @@ -114,15 +115,15 @@ }, { "x": 56.5, - "y": 166 + "y": 195.2 }, { "x": 56.5, - "y": 186 + "y": 229.9 }, { "x": 56.5, - "y": 226 + "y": 299.5 } ], "isCurve": true, diff --git a/e2etests/testdata/todo/tall_edge_label/dagre/sketch.exp.svg b/e2etests/testdata/todo/tall_edge_label/dagre/sketch.exp.svg index b4e1b6d2d..e61e1a050 100644 --- a/e2etests/testdata/todo/tall_edge_label/dagre/sketch.exp.svg +++ b/e2etests/testdata/todo/tall_edge_label/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ ab Thereoncewasaverytalledgelabel + + ab Thereoncewasaverytalledgelabel - +ab Thereoncewasaverytalledgelabel +