Merge branch 'master' into near-keys-for-container

This commit is contained in:
donglixiaoche 2023-03-31 21:18:46 +08:00
commit 9089253872
No known key found for this signature in database
GPG key ID: 3190E65EBAD6D6E2
446 changed files with 26122 additions and 1340 deletions

View file

@ -262,12 +262,12 @@ this selected list of featured projects using D2.
- Official app of the Netherlands for coronavirus entry passes.
- [Block
Protocol](https://github.com/blockprotocol/blockprotocol/blob/db4cf8d422b881e52113aa52467d53115270e2b3/libs/%40blockprotocol/type-system/crate/assets/overview.d2)
- The Block Protocol is an open standard for building and using data-driven blocks.
- [Dagger](https://github.com/dagger/dagger/tree/main/cmd/dagger-graph) - A programmable
CI/CD engine that runs your pipelines in containers
- The Block Protocol is an open standard for building and using data-driven blocks (1.2k
stars).
- [Dagger](https://github.com/dagger/dagger/tree/main/cmd/dagger-graph)
- A programmable CI/CD engine that runs your pipelines in containers (8k stars).
- [Ivy
Wallet](https://github.com/Ivy-Apps/ivy-wallet/blob/8062624bfa65175ec143cdc4038de27a84d38b57/assets/calc_algo.d2)
- Ivy Wallet is an open-source money manager app for Android.
- [Shed
Skin](https://github.com/shedskin/shedskin/blob/c7929e5fe0290d734ffb7e34e4cfc2cf731c7f98/docs/assets/diagrams/shedskin.d2)
- Python to C++ compiler
- Open-source money manager app for Android (1.1k stars).
- [LocalStack](https://docs.localstack.cloud/references/network-troubleshooting/)
- Cloud service emulator (46k stars)

View file

@ -1,8 +1,9 @@
#### Features 🚀
- `--animate-interval` can be passed as a flag to animate multi-board diagrams. See [docs](https://d2lang.com/todo). [#1088](https://github.com/terrastruct/d2/pull/1088)
- `paper` is available as a `fill-pattern` option [#1070](https://github.com/terrastruct/d2/pull/1070)
- Multi-board SVG outputs with internal links go to their output paths [#1116](https://github.com/terrastruct/d2/pull/1116)
#### Improvements 🧹
#### Bugfixes ⛑️
- Fix a bug in ID parsing [#322](https://github.com/terrastruct/d2/issues/322)

View file

@ -0,0 +1,34 @@
D2 0.3 is here!
## Major updates:
- SVG sizes are ~**5%** of what they were in D2 0.2.
- The disproportionately largest contributor to this size was the font. Instead of encoding the entire font, D2 now only bundles only the used part of it (e.g. if you don't use the letter "b", the font encoding for "b" won't be included).
- The first practical applications of multi-board compositions are here: animations. Composition is among D2's most powerful features, and these first applications are just the tip of the iceberg. Stay tuned for more in upcoming 0.3.x releases. See [docs](https://d2lang.com/tour/composition).
![animated](https://user-images.githubusercontent.com/3120367/228722320-65a42558-55b5-40f0-8616-53510b57202f.svg)
- Customizable fonts. You can pass in whatever you want to use through the command line.
<img width="300" alt="Screen Shot 2023-03-29 at 8 27 45 PM" src="https://user-images.githubusercontent.com/3120367/228721122-577c8d28-5fbf-473e-924c-35f6f1e98fa1.png">
### Other
- New "Origami" theme
<img width="550" alt="Screen Shot 2023-03-29 at 7 59 31 PM" src="https://user-images.githubusercontent.com/3120367/228721029-2136e162-e303-4b87-9da3-d8e6ad02af92.png">
#### Features 🚀
- Flags to set a custom font are supported. See [docs](https://d2lang.com/tour/fonts). [#1108](https://github.com/terrastruct/d2/pull/1108)
- `--animate-interval` can be passed as a flag to animate multi-board diagrams. See [docs](https://d2lang.com/tour/composition). [#1088](https://github.com/terrastruct/d2/pull/1088)
- New `fill-pattern`: `paper` [#1070](https://github.com/terrastruct/d2/pull/1070)
- Fonts are subsetted to only include what's necessary [#1089](https://github.com/terrastruct/d2/pull/1089)
- New theme: Origami [#1110](https://github.com/terrastruct/d2/pull/1110)
#### Improvements 🧹
- Prevent `tooltip` being set to a URL when `link` is already set (for security) [#1091](https://github.com/terrastruct/d2/pull/1091)
- Scale arrowhead sizes appropriately to `stroke-width`. [#1101](https://github.com/terrastruct/d2/pull/1101)
#### Bugfixes ⛑️
- Prevents an object's `near` from targeting another object with `near` set to a constant [#1100](https://github.com/terrastruct/d2/pull/1100)
- Fixes inaccurate bold edge label padding [#1108](https://github.com/terrastruct/d2/pull/1108)
- Prevents Latex blocks from being uppercased in special themes [#1111](https://github.com/terrastruct/d2/pull/1111)

View file

@ -80,6 +80,15 @@ Renders the diagram to look like it was sketched by hand
.It Fl -center Ar flag
Center the SVG in the containing viewbox, such as your browser screen
.Ns .
.It Fl -font-regular
Path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used
.Ns .
.It Fl -font-italic
Path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used
.Ns .
.It Fl -font-bold
Path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used
.Ns .
.It Fl -pad Ar 100
Pixels padded around the rendered diagram
.Ns .

View file

@ -95,6 +95,10 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return err
}
fontRegularFlag := ms.Opts.String("D2_FONT_REGULAR", "font-regular", "", "", "path to .ttf file to use for the regular font. If none provided, Source Sans Pro Regular is used.")
fontItalicFlag := ms.Opts.String("D2_FONT_ITALIC", "font-italic", "", "", "path to .ttf file to use for the italic font. If none provided, Source Sans Pro Regular-Italic is used.")
fontBoldFlag := ms.Opts.String("D2_FONT_BOLD", "font-bold", "", "", "path to .ttf file to use for the bold font. If none provided, Source Sans Pro Bold is used.")
ps, err := d2plugin.ListPlugins(ctx)
if err != nil {
return err
@ -114,6 +118,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return nil
}
fontFamily, err := loadFonts(ms, *fontRegularFlag, *fontItalicFlag, *fontBoldFlag)
if err != nil {
return xmain.UsageErrorf("failed to load specified fonts: %v", err)
}
if len(ms.Opts.Flags.Args()) > 0 {
switch ms.Opts.Flags.Arg(0) {
case "init-playwright":
@ -265,6 +274,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
bundle: *bundleFlag,
forceAppendix: *forceAppendixFlag,
pw: pw,
fontFamily: fontFamily,
})
if err != nil {
return err
@ -275,7 +285,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
ctx, cancel := context.WithTimeout(ctx, time.Minute*2)
defer cancel()
_, written, err := compile(ctx, ms, plugin, renderOpts, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
_, written, err := compile(ctx, ms, plugin, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, *bundleFlag, *forceAppendixFlag, pw.Page)
if err != nil {
if written {
return fmt.Errorf("failed to fully compile (partial render written): %w", err)
@ -285,7 +295,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
return nil
}
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
start := time.Now()
input, err := ms.ReadPath(inputPath)
if err != nil {
@ -299,9 +309,10 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
layout := plugin.Layout
opts := &d2lib.CompileOptions{
Layout: layout,
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
Layout: layout,
Ruler: ruler,
ThemeID: renderOpts.ThemeID,
FontFamily: fontFamily,
}
if renderOpts.Sketch {
opts.FontFamily = go2.Pointer(d2fonts.HandDrawn)
@ -347,6 +358,15 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
return pdf, true, nil
} else {
compileDur := time.Since(start)
if animateInterval <= 0 {
// Rename all the "root.layers.x" to the paths that the boards get output to
linkToOutput, err := resolveLinks("root", outputPath, diagram)
if err != nil {
return nil, false, err
}
relink(diagram, linkToOutput)
}
boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil {
return nil, false, err
@ -371,6 +391,99 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende
}
}
func resolveLinks(currDiagramPath, outputPath string, diagram *d2target.Diagram) (linkToOutput map[string]string, err error) {
if diagram.Name != "" {
ext := filepath.Ext(outputPath)
outputPath = strings.TrimSuffix(outputPath, ext)
outputPath = filepath.Join(outputPath, diagram.Name)
outputPath += ext
}
boardOutputPath := outputPath
if len(diagram.Layers) > 0 || len(diagram.Scenarios) > 0 || len(diagram.Steps) > 0 {
ext := filepath.Ext(boardOutputPath)
boardOutputPath = strings.TrimSuffix(boardOutputPath, ext)
boardOutputPath = filepath.Join(boardOutputPath, "index")
boardOutputPath += ext
}
layersOutputPath := outputPath
if len(diagram.Scenarios) > 0 || len(diagram.Steps) > 0 {
ext := filepath.Ext(layersOutputPath)
layersOutputPath = strings.TrimSuffix(layersOutputPath, ext)
layersOutputPath = filepath.Join(layersOutputPath, "layers")
layersOutputPath += ext
}
scenariosOutputPath := outputPath
if len(diagram.Layers) > 0 || len(diagram.Steps) > 0 {
ext := filepath.Ext(scenariosOutputPath)
scenariosOutputPath = strings.TrimSuffix(scenariosOutputPath, ext)
scenariosOutputPath = filepath.Join(scenariosOutputPath, "scenarios")
scenariosOutputPath += ext
}
stepsOutputPath := outputPath
if len(diagram.Layers) > 0 || len(diagram.Scenarios) > 0 {
ext := filepath.Ext(stepsOutputPath)
stepsOutputPath = strings.TrimSuffix(stepsOutputPath, ext)
stepsOutputPath = filepath.Join(stepsOutputPath, "steps")
stepsOutputPath += ext
}
linkToOutput = map[string]string{currDiagramPath: boardOutputPath}
for _, dl := range diagram.Layers {
m, err := resolveLinks(strings.Join([]string{currDiagramPath, "layers", dl.Name}, "."), layersOutputPath, dl)
if err != nil {
return nil, err
}
for k, v := range m {
linkToOutput[k] = v
}
}
for _, dl := range diagram.Scenarios {
m, err := resolveLinks(strings.Join([]string{currDiagramPath, "scenarios", dl.Name}, "."), scenariosOutputPath, dl)
if err != nil {
return nil, err
}
for k, v := range m {
linkToOutput[k] = v
}
}
for _, dl := range diagram.Steps {
m, err := resolveLinks(strings.Join([]string{currDiagramPath, "steps", dl.Name}, "."), stepsOutputPath, dl)
if err != nil {
return nil, err
}
for k, v := range m {
linkToOutput[k] = v
}
}
return linkToOutput, nil
}
func relink(d *d2target.Diagram, linkToOutput map[string]string) {
for i, shape := range d.Shapes {
if shape.Link != "" {
for k, v := range linkToOutput {
if shape.Link == k {
d.Shapes[i].Link = v
break
}
}
}
}
for _, board := range d.Layers {
relink(board, linkToOutput)
}
for _, board := range d.Scenarios {
relink(board, linkToOutput)
}
for _, board := range d.Steps {
relink(board, linkToOutput)
}
}
func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) {
if diagram.Name != "" {
ext := filepath.Ext(outputPath)
@ -659,3 +772,47 @@ func initPlaywright() error {
}
return pw.Cleanup()
}
func loadFont(ms *xmain.State, path string) ([]byte, error) {
if filepath.Ext(path) != ".ttf" {
return nil, fmt.Errorf("expected .ttf file but %s has extension %s", path, filepath.Ext(path))
}
ttf, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read font at %s: %v", path, err)
}
ms.Log.Info.Printf("font %s loaded", filepath.Base(path))
return ttf, nil
}
func loadFonts(ms *xmain.State, pathToRegular, pathToItalic, pathToBold string) (*d2fonts.FontFamily, error) {
if pathToRegular == "" && pathToItalic == "" && pathToBold == "" {
return nil, nil
}
var regularTTF []byte
var italicTTF []byte
var boldTTF []byte
var err error
if pathToRegular != "" {
regularTTF, err = loadFont(ms, pathToRegular)
if err != nil {
return nil, err
}
}
if pathToItalic != "" {
italicTTF, err = loadFont(ms, pathToItalic)
if err != nil {
return nil, err
}
}
if pathToBold != "" {
boldTTF, err = loadFont(ms, pathToBold)
if err != nil {
return nil, err
}
}
return d2fonts.AddFontFamily("custom", regularTTF, italicTTF, boldTTF)
}

View file

@ -26,6 +26,7 @@ import (
"oss.terrastruct.com/util-go/xmain"
"oss.terrastruct.com/d2/d2plugin"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/lib/png"
)
@ -51,6 +52,7 @@ type watcherOpts struct {
bundle bool
forceAppendix bool
pw png.Playwright
fontFamily *d2fonts.FontFamily
}
type watcher struct {
@ -358,7 +360,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
w.pw = newPW
}
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page)
svg, _, err := compile(ctx, w.ms, w.layoutPlugin, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, w.bundle, w.forceAppendix, w.pw.Page)
errs := ""
if err != nil {
if len(svg) > 0 {

View file

@ -363,6 +363,13 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
attrs.Constraint.Value = scalar.ScalarString()
attrs.Constraint.MapKey = f.LastPrimaryKey()
}
if attrs.Link != nil && attrs.Tooltip != nil {
_, err := url.ParseRequestURI(attrs.Tooltip.Value)
if err == nil {
c.errorf(scalar, "Tooltip cannot be set to URL when link is also set (for security)")
}
}
}
func (c *compiler) compileStyle(attrs *d2graph.Attributes, m *d2ir.Map) {
@ -719,6 +726,13 @@ func (c *compiler) validateNear(g *d2graph.Graph) {
c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an object within sequence diagrams")
continue
}
if nearObj.Attributes.NearKey != nil {
_, nearObjNearIsConst := d2graph.NearConstants[d2graph.Key(nearObj.Attributes.NearKey)[0]]
if nearObjNearIsConst {
c.errorf(obj.Attributes.NearKey, "near keys cannot be set to an object with a constant near key")
continue
}
}
} else if isConst {
is := false
for _, e := range g.Edges {

View file

@ -1457,6 +1457,40 @@ x -> y: {
}
},
},
{
name: "url_tooltip",
text: `x: {tooltip: https://google.com}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
if g.Objects[0].Attributes.Tooltip.Value != "https://google.com" {
t.Fatal(g.Objects[0].Attributes.Tooltip.Value)
}
},
},
{
name: "no_url_link_and_url_tooltip_concurrently",
text: `x: {link: https://not-google.com; tooltip: https://google.com}`,
expErr: `d2/testdata/d2compiler/TestCompile/no_url_link_and_url_tooltip_concurrently.d2:1:44: Tooltip cannot be set to URL when link is also set (for security)`,
},
{
name: "url_link_and_not_url_tooltip_concurrently",
text: `x: {link: https://google.com; tooltip: hello world}`,
assertions: func(t *testing.T, g *d2graph.Graph) {
if len(g.Objects) != 1 {
t.Fatal(g.Objects)
}
if g.Objects[0].Attributes.Link.Value != "https://google.com" {
t.Fatal(g.Objects[0].Attributes.Link.Value)
}
if g.Objects[0].Attributes.Tooltip.Value != "hello world" {
t.Fatal(g.Objects[0].Attributes.Tooltip.Value)
}
},
},
{
name: "nil_scope_obj_regression",
@ -2223,6 +2257,19 @@ layers: {
}`,
expErr: `d2/testdata/d2compiler/TestCompile/link-board-underscore-not-found.d2:7:9: invalid underscore usage`,
},
{
name: "near_near_const",
text: `
title: Title {
near: top-center
}
obj {
near: title
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/near_near_const.d2:7:8: near keys cannot be set to an object with a constant near key`,
},
}
for _, tc := range testCases {

View file

@ -62,6 +62,8 @@ func applyTheme(shape *d2target.Shape, obj *d2graph.Object, theme *d2themes.Them
if len(obj.ChildrenArray) > 0 {
shape.FillPattern = "dots"
}
} else if theme.SpecialRules.AllPaper {
shape.FillPattern = "paper"
}
if theme.SpecialRules.Mono {
shape.FontFamily = "mono"

View file

@ -1043,10 +1043,14 @@ func (e *Edge) Text() *d2target.MText {
if e.Attributes.Style.FontSize != nil {
fontSize, _ = strconv.Atoi(e.Attributes.Style.FontSize.Value)
}
isBold := false
if e.Attributes.Style.Bold != nil {
isBold, _ = strconv.ParseBool(e.Attributes.Style.Bold.Value)
}
return &d2target.MText{
Text: e.Attributes.Label.Value,
FontSize: fontSize,
IsBold: false,
IsBold: isBold,
IsItalic: true,
Dimensions: e.LabelDimensions,
@ -1312,7 +1316,9 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) {
obj.Attributes.Label.Value = strings.ToUpper(obj.Attributes.Label.Value)
if obj.Attributes.Language != "latex" {
obj.Attributes.Label.Value = strings.ToUpper(obj.Attributes.Label.Value)
}
}
labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily)
@ -1462,7 +1468,9 @@ func (g *Graph) Texts() []*d2target.MText {
if obj.Attributes.Label.Value != "" {
text := obj.Text()
if capsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) {
text.Text = strings.ToUpper(text.Text)
if obj.Attributes.Language != "latex" {
text.Text = strings.ToUpper(text.Text)
}
}
texts = appendTextDedup(texts, text)
}

View file

@ -1063,6 +1063,15 @@ func (p *parser) parseUnquotedString(inKey bool) (s *d2ast.UnquotedString) {
if eof {
return s
}
switch r2 {
case '\n', ';', '#', '{', '}', '[', ']':
p.rewind()
p.peek()
p.commit()
sb.WriteRune(r)
rawb.WriteRune(r)
return s
}
if r2 == '-' || r2 == '>' || r2 == '*' {
p.rewind()
return s

View file

@ -74,7 +74,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
return nil, err
}
d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily)
d2svg.EmbedFonts(buf, diagramHash, svgsStr, rootDiagram.FontFamily, rootDiagram.GetNestedCorpus())
themeStylesheet, err := d2svg.ThemeCSS(diagramHash, renderOpts.ThemeID, renderOpts.DarkThemeID)
if err != nil {

View file

@ -6,7 +6,13 @@ package d2fonts
import (
"embed"
"encoding/base64"
"fmt"
"strings"
"github.com/jung-kurt/gofpdf"
fontlib "oss.terrastruct.com/d2/lib/font"
)
type FontFamily string
@ -26,6 +32,29 @@ func (f FontFamily) Font(size int, style FontStyle) Font {
}
}
func (f Font) GetEncodedSubset(corpus string) string {
var uniqueChars string
uniqueMap := make(map[rune]bool)
for _, char := range corpus {
if _, exists := uniqueMap[char]; !exists {
uniqueMap[char] = true
uniqueChars = uniqueChars + string(char)
}
}
fontBuf := make([]byte, len(FontFaces[f]))
copy(fontBuf, FontFaces[f])
fontBuf = gofpdf.UTF8CutFont(fontBuf, uniqueChars)
fontBuf, err := fontlib.Sfnt2Woff(fontBuf)
if err != nil {
// If subset fails, return full encoding
return FontEncodings[f]
}
return fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(fontBuf))
}
const (
FONT_SIZE_XS = 13
FONT_SIZE_S = 14
@ -216,3 +245,78 @@ var D2_FONT_TO_FAMILY = map[string]FontFamily{
"default": SourceSansPro,
"mono": SourceCodePro,
}
func AddFontStyle(font Font, style FontStyle, ttf []byte) error {
FontFaces[font] = ttf
woff, err := fontlib.Sfnt2Woff(ttf)
if err != nil {
return fmt.Errorf("failed to encode ttf to woff: %v", err)
}
encodedWoff := fmt.Sprintf("data:application/font-woff;base64,%v", base64.StdEncoding.EncodeToString(woff))
FontEncodings[font] = encodedWoff
return nil
}
func AddFontFamily(name string, regularTTF, italicTTF, boldTTF []byte) (*FontFamily, error) {
customFontFamily := FontFamily(name)
regularFont := Font{
Family: customFontFamily,
Style: FONT_STYLE_REGULAR,
}
if regularTTF != nil {
err := AddFontStyle(regularFont, FONT_STYLE_REGULAR, regularTTF)
if err != nil {
return nil, err
}
} else {
fallbackFont := Font{
Family: SourceSansPro,
Style: FONT_STYLE_REGULAR,
}
FontFaces[regularFont] = FontFaces[fallbackFont]
FontEncodings[regularFont] = FontEncodings[fallbackFont]
}
italicFont := Font{
Family: customFontFamily,
Style: FONT_STYLE_ITALIC,
}
if italicTTF != nil {
err := AddFontStyle(italicFont, FONT_STYLE_ITALIC, italicTTF)
if err != nil {
return nil, err
}
} else {
fallbackFont := Font{
Family: SourceSansPro,
Style: FONT_STYLE_ITALIC,
}
FontFaces[italicFont] = FontFaces[fallbackFont]
FontEncodings[italicFont] = FontEncodings[fallbackFont]
}
boldFont := Font{
Family: customFontFamily,
Style: FONT_STYLE_BOLD,
}
if boldTTF != nil {
err := AddFontStyle(boldFont, FONT_STYLE_BOLD, boldTTF)
if err != nil {
return nil, err
}
} else {
fallbackFont := Font{
Family: SourceSansPro,
Style: FONT_STYLE_BOLD,
}
FontFaces[boldFont] = FontFaces[fallbackFont]
FontEncodings[boldFont] = FontEncodings[fallbackFont]
}
FontFamilies = append(FontFamilies, customFontFamily)
return &customFontFamily, nil
}

View file

@ -0,0 +1,23 @@
package d2fonts
import (
"path/filepath"
"testing"
"github.com/jung-kurt/gofpdf"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff"
)
func TestCutFont(t *testing.T) {
f := Font{
Family: SourceCodePro,
Style: FONT_STYLE_BOLD,
}
fontBuf := make([]byte, len(FontFaces[f]))
copy(fontBuf, FontFaces[f])
fontBuf = gofpdf.UTF8CutFont(fontBuf, " 1")
err := diff.Testdata(filepath.Join("testdata", "d2fonts", "cut"), ".txt", fontBuf)
assert.Success(t, err)
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 127 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 115 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 160 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 325 KiB

After

Width:  |  Height:  |  Size: 150 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 161 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 165 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 89 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 741 KiB

After

Width:  |  Height:  |  Size: 493 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 74 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 420 KiB

After

Width:  |  Height:  |  Size: 180 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 420 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View file

@ -11,6 +11,7 @@ import (
"strings"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2svg"
"oss.terrastruct.com/d2/d2target"
@ -162,6 +163,31 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by
return []byte(svg)
}
// transformInternalLink turns
// "root.layers.x.layers.y"
// into
// "root > x > y"
func transformInternalLink(link string) string {
if link == "" || !strings.HasPrefix(link, "root") {
return link
}
mk, err := d2parser.ParseMapKey(link)
if err != nil {
return ""
}
key := d2graph.Key(mk.Key)
if len(key) > 1 {
for i := 1; i < len(key); i += 2 {
key[i] = ">"
}
}
return strings.Join(key, " ")
}
func generateAppendix(diagram *d2target.Diagram, ruler *textmeasure.Ruler, svg string) (string, int, int) {
tl, br := diagram.BoundingBox()
@ -171,7 +197,7 @@ func generateAppendix(diagram *d2target.Diagram, ruler *textmeasure.Ruler, svg s
i := 1
for _, s := range diagram.Shapes {
for _, txt := range []string{s.Tooltip, s.Link} {
for _, txt := range []string{s.Tooltip, transformInternalLink(s.Link)} {
if txt != "" {
line, w, h := generateLine(i, br.Y+(PAD_TOP*2)+totalHeight, txt, ruler)
i++

View file

@ -90,6 +90,23 @@ x -> y
script: `x: { link: https://d2lang.com }
y: { link: https://fosny.eu; tooltip: Gee, I feel kind of LIGHT in the head now,\nknowing I can't make my satellite dish PAYMENTS! }
x -> y
`,
},
{
name: "internal-links",
script: `x: { link: layers.x }
layers: {
x: {
gooo
home.link: _
next.link: steps.next
steps: {
next: {
hi
}
}
}
}
`,
},
{

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 676 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 657 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 978 KiB

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 978 KiB

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 978 KiB

After

Width:  |  Height:  |  Size: 661 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 978 KiB

After

Width:  |  Height:  |  Size: 661 KiB

View file

@ -77,6 +77,7 @@ type RenderOpts struct {
Center bool
ThemeID int64
DarkThemeID *int64
Font string
// disables the fit to screen behavior and ensures the exported svg has the exact dimensions
SetDimensions bool
@ -109,34 +110,46 @@ func arrowheadMarkerID(isTarget bool, connection d2target.Connection) string {
}
func arrowheadDimensions(arrowhead d2target.Arrowhead, strokeWidth float64) (width, height float64) {
var widthMultiplier float64
var heightMultiplier float64
var baseWidth, baseHeight float64
var widthMultiplier, heightMultiplier float64
switch arrowhead {
case d2target.ArrowArrowhead:
widthMultiplier = 5
heightMultiplier = 5
baseWidth = 4
baseHeight = 4
widthMultiplier = 4
heightMultiplier = 4
case d2target.TriangleArrowhead:
widthMultiplier = 5
heightMultiplier = 6
baseWidth = 4
baseHeight = 4
widthMultiplier = 3
heightMultiplier = 4
case d2target.LineArrowhead:
widthMultiplier = 5
heightMultiplier = 8
case d2target.FilledDiamondArrowhead:
widthMultiplier = 11
heightMultiplier = 7
baseWidth = 11
baseHeight = 7
widthMultiplier = 5.5
heightMultiplier = 3.5
case d2target.DiamondArrowhead:
widthMultiplier = 11
heightMultiplier = 9
baseWidth = 11
baseHeight = 9
widthMultiplier = 5.5
heightMultiplier = 4.5
case d2target.FilledCircleArrowhead, d2target.CircleArrowhead:
widthMultiplier = 12
heightMultiplier = 12
baseWidth = 8
baseHeight = 8
widthMultiplier = 5
heightMultiplier = 5
case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
widthMultiplier = 9
heightMultiplier = 9
baseWidth = 9
baseHeight = 9
widthMultiplier = 4.5
heightMultiplier = 4.5
}
clippedStrokeWidth := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, strokeWidth)
return clippedStrokeWidth * widthMultiplier, clippedStrokeWidth * heightMultiplier
return baseWidth + clippedStrokeWidth*widthMultiplier, baseHeight + clippedStrokeWidth*heightMultiplier
}
func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) string {
@ -1386,7 +1399,7 @@ func RenderText(text string, x, height float64) string {
return strings.Join(rendered, "")
}
func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fonts.FontFamily) {
func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fonts.FontFamily, corpus string) {
fmt.Fprint(buf, `<style type="text/css"><![CDATA[`)
appendOnTrigger(
@ -1408,7 +1421,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR)],
fontFamily.Font(0, d2fonts.FONT_STYLE_REGULAR).GetEncodedSubset(corpus),
),
)
@ -1470,7 +1483,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD)],
fontFamily.Font(0, d2fonts.FONT_STYLE_BOLD).GetEncodedSubset(corpus),
),
)
@ -1493,7 +1506,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC)],
fontFamily.Font(0, d2fonts.FONT_STYLE_ITALIC).GetEncodedSubset(corpus),
),
)
@ -1518,7 +1531,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_REGULAR)],
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_REGULAR).GetEncodedSubset(corpus),
),
)
@ -1539,7 +1552,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_BOLD)],
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_BOLD).GetEncodedSubset(corpus),
),
)
@ -1560,7 +1573,7 @@ func EmbedFonts(buf *bytes.Buffer, diagramHash, source string, fontFamily *d2fon
diagramHash,
diagramHash,
diagramHash,
d2fonts.FontEncodings[d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_ITALIC)],
d2fonts.SourceCodePro.Font(0, d2fonts.FONT_STYLE_ITALIC).GetEncodedSubset(corpus),
),
)
@ -1726,7 +1739,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
// generate style elements that will be appended to the SVG tag
upperBuf := &bytes.Buffer{}
if opts.MasterID == "" {
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
EmbedFonts(upperBuf, diagramHash, buf.String(), diagram.FontFamily, diagram.GetCorpus()) // EmbedFonts *must* run before `d2sketch.DefineFillPatterns`, but after all elements are appended to `buf`
themeStylesheet, err := ThemeCSS(diagramHash, themeID, darkThemeID)
if err != nil {
return nil, err

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -237,6 +237,62 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
return Point{x1, y1}, Point{x2, y2}
}
func (diagram Diagram) GetNestedCorpus() string {
corpus := diagram.GetCorpus()
for _, d := range diagram.Layers {
corpus += d.GetNestedCorpus()
}
for _, d := range diagram.Scenarios {
corpus += d.GetNestedCorpus()
}
for _, d := range diagram.Steps {
corpus += d.GetNestedCorpus()
}
return corpus
}
func (diagram Diagram) GetCorpus() string {
var corpus string
appendixCount := 0
for _, s := range diagram.Shapes {
corpus += s.Label
if s.Tooltip != "" {
corpus += s.Tooltip
appendixCount++
corpus += fmt.Sprint(appendixCount)
}
if s.Link != "" {
corpus += s.Link
appendixCount++
corpus += fmt.Sprint(appendixCount)
}
if s.Type == ShapeClass {
for _, cf := range s.Fields {
corpus += cf.Text(0).Text + cf.VisibilityToken()
}
for _, cm := range s.Methods {
corpus += cm.Text(0).Text + cm.VisibilityToken()
}
}
if s.Type == ShapeSQLTable {
for _, c := range s.Columns {
for _, t := range c.Texts(0) {
corpus += t.Text
}
corpus += c.ConstraintAbbr()
}
}
}
for _, c := range diagram.Connections {
corpus += c.Label
corpus += c.SrcLabel
corpus += c.DstLabel
}
return corpus
}
func NewDiagram() *Diagram {
return &Diagram{
Root: Shape{

View file

@ -18,6 +18,8 @@ type SpecialRules struct {
OuterContainerDoubleBorder bool `json:"outerContainerDoubleBorder"`
ContainerDots bool `json:"containerDots"`
CapsLock bool `json:"capsLock"`
AllPaper bool `json:"allPaper"`
}
func (t *Theme) IsDark() bool {

View file

@ -24,6 +24,7 @@ var LightCatalog = []d2themes.Theme{
ButteredToast,
Terminal,
TerminalGrayscale,
Origami,
}
var DarkCatalog = []d2themes.Theme{

View file

@ -0,0 +1,40 @@
package d2themescatalog
import "oss.terrastruct.com/d2/d2themes"
var Origami = d2themes.Theme{
ID: 302,
Name: "Origami",
Colors: d2themes.ColorPalette{
Neutrals: OrigamiNeutral,
B1: "#170206",
B2: "#A62543",
B3: "#E07088",
B4: "#F3E0D2",
B5: "#FAF1E6",
B6: "#FFFBF8",
AA2: "#0A4EA6",
AA4: "#3182CD",
AA5: "#68A8E4",
AB4: "#E07088",
AB5: "#F19CAE",
},
SpecialRules: d2themes.SpecialRules{
NoCornerRadius: true,
OuterContainerDoubleBorder: true,
AllPaper: true,
},
}
var OrigamiNeutral = d2themes.Neutral{
N1: "#170206",
N2: "#6F0019",
N3: "#FFFFFF",
N4: "#E07088",
N5: "#D2B098",
N6: "#FFFFFF",
N7: "#FFFFFF",
}

Binary file not shown.

View file

@ -21,6 +21,7 @@ func TestCLI_E2E(t *testing.T) {
tca := []struct {
name string
skipCI bool
skip bool
run func(t *testing.T, ctx context.Context, dir string, env *xos.Env)
}{
{
@ -82,6 +83,48 @@ steps: {
assert.Testdata(t, ".svg", svg)
},
},
{
name: "linked-path",
// TODO tempdir is random, resulting in different test results each time with the links
skip: true,
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "linked.d2", `cat: how does the cat go? {
link: layers.cat
}
layers: {
cat: {
home: {
link: _
}
the cat -> meow: goes
scenarios: {
big cat: {
the cat -> roar: goes
}
}
}
}
`)
err := runTestMain(t, ctx, dir, env, "linked.d2")
assert.Success(t, err)
assert.TestdataDir(t, filepath.Join(dir, "linked"))
},
},
{
name: "with-font",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
writeFile(t, dir, "font.d2", `a: Why do computers get sick often?
b: Because their Windows are always open!
a -> b: italic font
`)
err := runTestMain(t, ctx, dir, env, "--font-bold=./RockSalt-Regular.ttf", "font.d2")
assert.Success(t, err)
svg := readFile(t, dir, "font.svg")
assert.Testdata(t, ".svg", svg)
},
},
{
name: "incompatible-animation",
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
@ -228,6 +271,9 @@ layers: {
if tc.skipCI && os.Getenv("CI") != "" {
t.SkipNow()
}
if tc.skip {
t.SkipNow()
}
ctx, cancel := context.WithTimeout(ctx, time.Minute*5)
defer cancel()

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 669 KiB

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 329 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 329 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -646,6 +646,16 @@ l3c2.c -> l4.c3.c`,
name: "link_with_ampersand",
script: `a.link: https://calendar.google.com/calendar/u/0/r?tab=mc&pli=1`,
},
{
name: "bold_edge_label",
script: `
direction: right
x -> y: sync
y -> z: sync {
style.bold: true
}
`,
},
}
runa(t, tcs)

View file

@ -2526,6 +2526,7 @@ scenarios: {
}
}`,
},
loadFromFile(t, "arrowhead_scaling"),
}
runa(t, tcs)

View file

@ -0,0 +1,340 @@
default: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
}
}
line: {
start: ""
end: ""
start.1 -- end.1: 1 {
style.stroke-width: 1
}
start.2 -- end.2: 2 {
style.stroke-width: 2
}
start.4 -- end.4: 4 {
style.stroke-width: 4
}
start.8 -- end.8: 8 {
style.stroke-width: 8
}
start.15 -- end.15: 15 {
style.stroke-width: 15
}
}
arrow: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: arrow
target-arrowhead.shape: arrow
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: arrow
target-arrowhead.shape: arrow
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: arrow
target-arrowhead.shape: arrow
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: arrow
target-arrowhead.shape: arrow
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: arrow
target-arrowhead.shape: arrow
}
}
diamond: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
}
}
filled diamond: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: diamond
target-arrowhead.shape: diamond
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
}
circle: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: circle
target-arrowhead.shape: circle
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: circle
target-arrowhead.shape: circle
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: circle
target-arrowhead.shape: circle
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: circle
target-arrowhead.shape: circle
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: circle
target-arrowhead.shape: circle
}
}
filled circle: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: circle
target-arrowhead.shape: circle
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: circle
target-arrowhead.shape: circle
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: circle
target-arrowhead.shape: circle
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: circle
target-arrowhead.shape: circle
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: circle
target-arrowhead.shape: circle
source-arrowhead.style.filled: true
target-arrowhead.style.filled: true
}
}
cf one: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: cf-one
target-arrowhead.shape: cf-one
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: cf-one
target-arrowhead.shape: cf-one
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: cf-one
target-arrowhead.shape: cf-one
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: cf-one
target-arrowhead.shape: cf-one
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: cf-one
target-arrowhead.shape: cf-one
}
}
cf one required: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: cf-one-required
target-arrowhead.shape: cf-one-required
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: cf-one-required
target-arrowhead.shape: cf-one-required
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: cf-one-required
target-arrowhead.shape: cf-one-required
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: cf-one-required
target-arrowhead.shape: cf-one-required
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: cf-one-required
target-arrowhead.shape: cf-one-required
}
}
cf many: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: cf-many
target-arrowhead.shape: cf-many
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: cf-many
target-arrowhead.shape: cf-many
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: cf-many
target-arrowhead.shape: cf-many
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: cf-many
target-arrowhead.shape: cf-many
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: cf-many
target-arrowhead.shape: cf-many
}
}
cf many required: {
start: ""
end: ""
start.1 <-> end.1: 1 {
style.stroke-width: 1
source-arrowhead.shape: cf-many-required
target-arrowhead.shape: cf-many-required
}
start.2 <-> end.2: 2 {
style.stroke-width: 2
source-arrowhead.shape: cf-many-required
target-arrowhead.shape: cf-many-required
}
start.4 <-> end.4: 4 {
style.stroke-width: 4
source-arrowhead.shape: cf-many-required
target-arrowhead.shape: cf-many-required
}
start.8 <-> end.8: 8 {
style.stroke-width: 8
source-arrowhead.shape: cf-many-required
target-arrowhead.shape: cf-many-required
}
start.15 <-> end.15: 15 {
style.stroke-width: 15
source-arrowhead.shape: cf-many-required
target-arrowhead.shape: cf-many-required
}
}

View file

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

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 341 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

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

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6 KiB

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