diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 0541fb231..2a6e579d6 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ #### Features ๐ +- Icons can be added for special objects (sql_table, class, code, markdown, latex). [#1774](https://github.com/terrastruct/d2/pull/1774) + #### Improvements ๐งน #### Bugfixes โ๏ธ diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 2d709f81f..8b01bc964 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -1506,18 +1506,22 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler // give shapes with icons extra padding to fit their label if obj.Icon != nil { - labelHeight := float64(labelDims.Height + INNER_LABEL_PADDING) - // Evenly pad enough to fit label above icon - if desiredWidth == 0 { - paddingX += labelHeight - } - if desiredHeight == 0 { - paddingY += labelHeight + switch shapeType { + case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE, shape.TEXT_TYPE: + default: + labelHeight := float64(labelDims.Height + INNER_LABEL_PADDING) + // Evenly pad enough to fit label above icon + if desiredWidth == 0 { + paddingX += labelHeight + } + if desiredHeight == 0 { + paddingY += labelHeight + } } } if desiredWidth == 0 { switch shapeType { - case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE, shape.IMAGE_TYPE: + case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE: default: if obj.Link != nil { paddingX += 32 diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 230fba11c..55e00e6fb 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -554,6 +554,8 @@ func positionLabelsIcons(obj *d2graph.Object) { obj.LabelPosition = go2.Pointer(label.OutsideTopRight.String()) return } + } else if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" { + obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String()) } else { obj.IconPosition = go2.Pointer(label.InsideMiddleCenter.String()) } diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 664f73dab..47dee829f 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -374,6 +374,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err if hasTop || hasBottom { padding := parsePadding(elkNodes[obj].LayoutOptions.Padding) if hasTop { + // TODO I think this fails to account for a potential inner label of container padding.top = go2.Max(padding.top, d2target.MAX_ICON_SIZE+2*label.PADDING) } if hasBottom { @@ -1207,6 +1208,8 @@ func positionLabelsIcons(obj *d2graph.Object) { obj.LabelPosition = go2.Pointer(label.InsideTopRight.String()) return } + } else if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" { + obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String()) } else { obj.IconPosition = go2.Pointer(label.InsideMiddleCenter.String()) } diff --git a/d2renderers/d2svg/class.go b/d2renderers/d2svg/class.go index ff9fb73d7..6f5c124bb 100644 --- a/d2renderers/d2svg/class.go +++ b/d2renderers/d2svg/class.go @@ -2,6 +2,7 @@ package d2svg import ( "fmt" + "html" "io" "oss.terrastruct.com/d2/d2target" @@ -141,4 +142,19 @@ func drawClass(writer io.Writer, diagramHash string, targetShape d2target.Shape) ) rowBox.TopLeft.Y += rowHeight } + + if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage { + iconPosition := label.FromString(targetShape.IconPosition) + iconSize := d2target.GetIconSize(box, targetShape.IconPosition) + + tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize)) + + fmt.Fprintf(writer, ``, + html.EscapeString(targetShape.Icon.String()), + tl.X, + tl.Y, + iconSize, + iconSize, + ) + } } diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go index 7192ed70a..0925bcf6b 100644 --- a/d2renderers/d2svg/table.go +++ b/d2renderers/d2svg/table.go @@ -2,6 +2,7 @@ package d2svg import ( "fmt" + "html" "io" "oss.terrastruct.com/d2/d2target" @@ -163,4 +164,19 @@ func drawTable(writer io.Writer, diagramHash string, targetShape d2target.Shape) lineEl.Style = "stroke-width:2" fmt.Fprint(writer, lineEl.Render()) } + + if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage { + iconPosition := label.FromString(targetShape.IconPosition) + iconSize := d2target.GetIconSize(box, targetShape.IconPosition) + + tl := iconPosition.GetPointOnBox(box, label.PADDING, float64(iconSize), float64(iconSize)) + + fmt.Fprintf(writer, ``, + html.EscapeString(targetShape.Icon.String()), + tl.X, + tl.Y, + iconSize, + iconSize, + ) + } } diff --git a/e2etests-cli/main_test.go b/e2etests-cli/main_test.go index 098a82865..8a4d074f2 100644 --- a/e2etests-cli/main_test.go +++ b/e2etests-cli/main_test.go @@ -999,7 +999,7 @@ func waitLogs(ctx context.Context, buf *bytes.Buffer, pattern *regexp.Regexp) (s ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() var match string - for i := 0; i < 100 && match == ""; i++ { + for i := 0; i < 1000 && match == ""; i++ { select { case <-ticker.C: out := buf.String() diff --git a/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf b/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf index e0dd241f0..975a67423 100644 Binary files a/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf and b/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf differ diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 2bf8e401a..8fe0d1afd 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -10,6 +10,7 @@ import ( "testing" "cdr.dev/slog" + "golang.org/x/tools/txtar" trequire "github.com/stretchr/testify/require" @@ -42,6 +43,7 @@ func TestE2E(t *testing.T) { t.Run("unicode", testUnicode) t.Run("root", testRoot) t.Run("themes", testThemes) + t.Run("txtar", testTxtar) } func testSanity(t *testing.T) { @@ -75,6 +77,19 @@ a -> c runa(t, tcs) } +func testTxtar(t *testing.T) { + var tcs []testCase + archive, err := txtar.ParseFile("./testdata/txtar.txt") + assert.Success(t, err) + for _, f := range archive.Files { + tcs = append(tcs, testCase{ + name: f.Name, + script: string(f.Data), + }) + } + runa(t, tcs) +} + type testCase struct { name string // if the test is just testing a render/style thing, no need to exercise both engines diff --git a/e2etests/testdata/txtar.txt b/e2etests/testdata/txtar.txt new file mode 100644 index 000000000..9bf9b56db --- /dev/null +++ b/e2etests/testdata/txtar.txt @@ -0,0 +1,49 @@ +-- sql-icon -- +without: { + tableEx: { + shape: sql_table + a: b + } + classEx: { + shape: class + a: b + } + codeEx: |go + a := 1 + | + mdEx: |md + # This is for all ill-treated fellows + + You will live a long, healthy, happy life and make bags of money. + | +} + +with: { + tableEx: { + shape: sql_table + a: b + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg + } + classEx: { + shape: class + a: b + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg + } + codeEx: |go + a := 1 + | { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg + } + mdEx: |md + # This is for all ill-treated fellows + + You will live a long, healthy, happy life and make bags of money. + | { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg + } +} + +without.tableEx -> with.tableEx +without.classEx -> with.classEx +without.codeEx -> with.codeEx +without.mdEx -> with.mdEx diff --git a/e2etests/testdata/txtar/sql-icon/dagre/board.exp.json b/e2etests/testdata/txtar/sql-icon/dagre/board.exp.json new file mode 100644 index 000000000..d82dbf6e9 --- /dev/null +++ b/e2etests/testdata/txtar/sql-icon/dagre/board.exp.json @@ -0,0 +1,817 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "without", + "type": "rectangle", + "pos": { + "x": 10, + "y": -4 + }, + "width": 1052, + "height": 198, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "without", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 93, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "without.tableEx", + "type": "sql_table", + "pos": { + "x": 40, + "y": 59 + }, + "width": 106, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "a", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 8, + "labelHeight": 26 + }, + "type": { + "label": "b", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 10, + "labelHeight": 26 + }, + "constraint": null, + "reference": "" + } + ], + "label": "tableEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 81, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "without.classEx", + "type": "class", + "pos": { + "x": 206, + "y": 26 + }, + "width": 204, + "height": 138, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "a", + "type": "b", + "visibility": "public" + } + ], + "methods": null, + "columns": null, + "label": "classEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "without.codeEx", + "type": "code", + "pos": { + "x": 470, + "y": 77 + }, + "width": 74, + "height": 37, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "zIndex": 0, + "level": 2 + }, + { + "id": "without.mdEx", + "type": "text", + "pos": { + "x": 604, + "y": 50 + }, + "width": 428, + "height": 91, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# This is for all ill-treated fellows\n\nYou will live a long, healthy, happy life and make bags of money.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 428, + "labelHeight": 91, + "zIndex": 0, + "level": 2 + }, + { + "id": "with", + "type": "rectangle", + "pos": { + "x": 10, + "y": 314 + }, + "width": 1052, + "height": 242, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "with", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 51, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "with.tableEx", + "type": "sql_table", + "pos": { + "x": 40, + "y": 421 + }, + "width": 106, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "a", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 8, + "labelHeight": 26 + }, + "type": { + "label": "b", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 10, + "labelHeight": 26 + }, + "constraint": null, + "reference": "" + } + ], + "label": "tableEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 81, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "with.classEx", + "type": "class", + "pos": { + "x": 206, + "y": 388 + }, + "width": 204, + "height": 138, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": [ + { + "name": "a", + "type": "b", + "visibility": "public" + } + ], + "methods": null, + "columns": null, + "label": "classEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "with.codeEx", + "type": "code", + "pos": { + "x": 470, + "y": 439 + }, + "width": 74, + "height": 37, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "zIndex": 0, + "level": 2 + }, + { + "id": "with.mdEx", + "type": "text", + "pos": { + "x": 604, + "y": 412 + }, + "width": 428, + "height": 91, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# This is for all ill-treated fellows\n\nYou will live a long, healthy, happy life and make bags of money.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 428, + "labelHeight": 91, + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "(without.tableEx -> with.tableEx)[0]", + "src": "without.tableEx", + "srcArrow": "none", + "dst": "with.tableEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 93, + "y": 131 + }, + { + "x": 93, + "y": 197.39999389648438 + }, + { + "x": 93, + "y": 224 + }, + { + "x": 93, + "y": 239 + }, + { + "x": 93, + "y": 254 + }, + { + "x": 93, + "y": 335.3999938964844 + }, + { + "x": 93, + "y": 421 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.classEx -> with.classEx)[0]", + "src": "without.classEx", + "srcArrow": "none", + "dst": "with.classEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 308, + "y": 164 + }, + { + "x": 308, + "y": 204 + }, + { + "x": 308, + "y": 224 + }, + { + "x": 308, + "y": 239 + }, + { + "x": 308, + "y": 254 + }, + { + "x": 308, + "y": 328.79998779296875 + }, + { + "x": 308, + "y": 388 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.codeEx -> with.codeEx)[0]", + "src": "without.codeEx", + "srcArrow": "none", + "dst": "with.codeEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 507, + "y": 114.5 + }, + { + "x": 507, + "y": 194.10000610351562 + }, + { + "x": 507, + "y": 224 + }, + { + "x": 507, + "y": 239 + }, + { + "x": 507, + "y": 254 + }, + { + "x": 507, + "y": 338.8999938964844 + }, + { + "x": 507, + "y": 438.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.mdEx -> with.mdEx)[0]", + "src": "without.mdEx", + "srcArrow": "none", + "dst": "with.mdEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 818, + "y": 141.5 + }, + { + "x": 818, + "y": 199.5 + }, + { + "x": 818, + "y": 224 + }, + { + "x": 818, + "y": 239 + }, + { + "x": 818, + "y": 254 + }, + { + "x": 818, + "y": 333.5 + }, + { + "x": 818, + "y": 411.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/sql-icon/dagre/sketch.exp.svg b/e2etests/testdata/txtar/sql-icon/dagre/sketch.exp.svg new file mode 100644 index 000000000..1a9d70c49 --- /dev/null +++ b/e2etests/testdata/txtar/sql-icon/dagre/sketch.exp.svg @@ -0,0 +1,858 @@ +withoutwithtableExabclassEx+aba := 1a := 1This is for all ill-treated fellows +You will live a long, healthy, happy life and make bags of money. +tableExabclassEx+aba := 1a := 1This is for all ill-treated fellows +You will live a long, healthy, happy life and make bags of money. + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/sql-icon/elk/board.exp.json b/e2etests/testdata/txtar/sql-icon/elk/board.exp.json new file mode 100644 index 000000000..140aded18 --- /dev/null +++ b/e2etests/testdata/txtar/sql-icon/elk/board.exp.json @@ -0,0 +1,733 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "without", + "type": "rectangle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 972, + "height": 238, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "without", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 93, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "without.tableEx", + "type": "sql_table", + "pos": { + "x": 62, + "y": 128 + }, + "width": 106, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "a", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 8, + "labelHeight": 26 + }, + "type": { + "label": "b", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 10, + "labelHeight": 26 + }, + "constraint": null, + "reference": "" + } + ], + "label": "tableEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 81, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "without.classEx", + "type": "class", + "pos": { + "x": 188, + "y": 62 + }, + "width": 204, + "height": 138, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": [ + { + "name": "a", + "type": "b", + "visibility": "public" + } + ], + "methods": null, + "columns": null, + "label": "classEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "without.codeEx", + "type": "code", + "pos": { + "x": 412, + "y": 163 + }, + "width": 74, + "height": 37, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "zIndex": 0, + "level": 2 + }, + { + "id": "without.mdEx", + "type": "text", + "pos": { + "x": 506, + "y": 109 + }, + "width": 428, + "height": 91, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# This is for all ill-treated fellows\n\nYou will live a long, healthy, happy life and make bags of money.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 428, + "labelHeight": 91, + "zIndex": 0, + "level": 2 + }, + { + "id": "with", + "type": "rectangle", + "pos": { + "x": 12, + "y": 330 + }, + "width": 972, + "height": 262, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "with", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 51, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "with.tableEx", + "type": "sql_table", + "pos": { + "x": 62, + "y": 404 + }, + "width": 106, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "a", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 8, + "labelHeight": 26 + }, + "type": { + "label": "b", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 10, + "labelHeight": 26 + }, + "constraint": null, + "reference": "" + } + ], + "label": "tableEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 81, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "with.classEx", + "type": "class", + "pos": { + "x": 188, + "y": 404 + }, + "width": 204, + "height": 138, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": [ + { + "name": "a", + "type": "b", + "visibility": "public" + } + ], + "methods": null, + "columns": null, + "label": "classEx", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "zIndex": 0, + "level": 2, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + }, + { + "id": "with.codeEx", + "type": "code", + "pos": { + "x": 412, + "y": 404 + }, + "width": 74, + "height": 37, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a := 1", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "golang", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "zIndex": 0, + "level": 2 + }, + { + "id": "with.mdEx", + "type": "text", + "pos": { + "x": 506, + "y": 404 + }, + "width": 428, + "height": 91, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "OUTSIDE_TOP_LEFT", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# This is for all ill-treated fellows\n\nYou will live a long, healthy, happy life and make bags of money.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 428, + "labelHeight": 91, + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "(without.tableEx -> with.tableEx)[0]", + "src": "without.tableEx", + "srcArrow": "none", + "dst": "with.tableEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 115, + "y": 200 + }, + { + "x": 115, + "y": 404 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.classEx -> with.classEx)[0]", + "src": "without.classEx", + "srcArrow": "none", + "dst": "with.classEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 290, + "y": 200 + }, + { + "x": 290, + "y": 404 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.codeEx -> with.codeEx)[0]", + "src": "without.codeEx", + "srcArrow": "none", + "dst": "with.codeEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 449, + "y": 200 + }, + { + "x": 449, + "y": 404 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(without.mdEx -> with.mdEx)[0]", + "src": "without.mdEx", + "srcArrow": "none", + "dst": "with.mdEx", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 720, + "y": 200 + }, + { + "x": 720, + "y": 404 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/sql-icon/elk/sketch.exp.svg b/e2etests/testdata/txtar/sql-icon/elk/sketch.exp.svg new file mode 100644 index 000000000..8a6a3288d --- /dev/null +++ b/e2etests/testdata/txtar/sql-icon/elk/sketch.exp.svg @@ -0,0 +1,858 @@ +withoutwithtableExabclassEx+aba := 1a := 1This is for all ill-treated fellows +You will live a long, healthy, happy life and make bags of money. +tableExabclassEx+aba := 1a := 1This is for all ill-treated fellows +You will live a long, healthy, happy life and make bags of money. + + + + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index 6dcd671a9..e27c6847d 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( golang.org/x/image v0.14.0 golang.org/x/net v0.19.0 golang.org/x/text v0.14.0 + golang.org/x/tools v0.16.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 gonum.org/v1/plot v0.14.0 nhooyr.io/websocket v1.8.10 diff --git a/go.sum b/go.sum index a9ee8da04..f39bc661f 100644 --- a/go.sum +++ b/go.sum @@ -266,6 +266,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
You will live a long, healthy, happy life and make bags of money.