Merge branch 'master' into near-keys-for-container
14
README.md
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
34
ci/release/changelogs/v0.3.0.md
Normal 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).
|
||||

|
||||
- 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)
|
||||
|
|
@ -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 .
|
||||
|
|
|
|||
167
d2cli/main.go
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
23
d2renderers/d2fonts/d2fonts_test.go
Normal 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)
|
||||
}
|
||||
BIN
d2renderers/d2fonts/testdata/d2fonts/cut.exp.txt
vendored
Normal file
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 334 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 741 KiB After Width: | Height: | Size: 493 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 355 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 180 KiB |
|
|
@ -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++
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 676 KiB |
115
d2renderers/d2svg/appendix/testdata/internal-links/sketch.exp.svg
vendored
Normal file
|
After Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 661 KiB |
|
|
@ -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
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 81 KiB |
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ var LightCatalog = []d2themes.Theme{
|
|||
ButteredToast,
|
||||
Terminal,
|
||||
TerminalGrayscale,
|
||||
Origami,
|
||||
}
|
||||
|
||||
var DarkCatalog = []d2themes.Theme{
|
||||
|
|
|
|||
40
d2themes/d2themescatalog/origami.go
Normal 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",
|
||||
}
|
||||
BIN
e2etests-cli/RockSalt-Regular.ttf
Normal 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()
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 669 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 9.3 KiB |
102
e2etests-cli/testdata/TestCLI_E2E/with-font.exp.svg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -2526,6 +2526,7 @@ scenarios: {
|
|||
}
|
||||
}`,
|
||||
},
|
||||
loadFromFile(t, "arrowhead_scaling"),
|
||||
}
|
||||
|
||||
runa(t, tcs)
|
||||
|
|
|
|||
340
e2etests/testdata/files/arrowhead_scaling.d2
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 19 KiB |
|
|
@ -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 |