diff --git a/d2renderers/d2latex/latex_test.go b/d2renderers/d2latex/latex_test.go index 2082bc73d..736631422 100644 --- a/d2renderers/d2latex/latex_test.go +++ b/d2renderers/d2latex/latex_test.go @@ -6,12 +6,18 @@ import ( ) func TestSVG(t *testing.T) { - svg, err := SVG("$$a + b = c$$") - if err != nil { - t.Fatal(err) + txts := []string{ + "a + b = c", + "\\\\frac{1}{2}", } - var xmlParsed interface{} - if err := xml.Unmarshal([]byte(svg), &xmlParsed); err != nil { - t.Fatalf("invalid SVG: %v", err) + for _, txt := range txts { + svg, err := SVG(txt) + if err != nil { + t.Fatal(err) + } + var xmlParsed interface{} + if err := xml.Unmarshal([]byte(svg), &xmlParsed); err != nil { + t.Fatalf("invalid SVG: %v", err) + } } } diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 1349183fa..3e9374098 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -18,8 +18,10 @@ import ( "github.com/alecthomas/chroma/formatters" "github.com/alecthomas/chroma/lexers" "github.com/alecthomas/chroma/styles" + "github.com/davecgh/go-spew/spew" "oss.terrastruct.com/d2/d2renderers/d2fonts" + "oss.terrastruct.com/d2/d2renderers/d2latex" "oss.terrastruct.com/d2/d2renderers/textmeasure" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/color" @@ -660,46 +662,60 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error { switch targetShape.Type { case d2target.ShapeCode: - lexer := lexers.Get(targetShape.Language) - if lexer == nil { - return fmt.Errorf("code snippet lexer for %s not found", targetShape.Language) - } - style := styles.Get("github") - if style == nil { - return errors.New(`code snippet style "github" not found`) - } - formatter := formatters.Get("svg") - if formatter == nil { - return errors.New(`code snippet formatter "svg" not found`) - } - iterator, err := lexer.Tokenise(nil, targetShape.Label) - if err != nil { - return err - } - - svgStyles := styleToSVG(style) - containerStyle := fmt.Sprintf(`stroke: %s;fill:%s`, targetShape.Stroke, style.Get(chroma.Background).Background.String()) - - fmt.Fprintf(writer, ``, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity) - fmt.Fprintf(writer, ``, - targetShape.Width, targetShape.Height, containerStyle) - // Padding - fmt.Fprintf(writer, ``) - - for index, tokens := range chroma.SplitTokensIntoLines(iterator.Tokens()) { - // TODO mono font looks better with 1.2 em (use px equivalent), but textmeasure needs to account for it. Not obvious how that should be done - fmt.Fprintf(writer, "", 1*float64(index+1)) - for _, token := range tokens { - text := svgEscaper.Replace(token.String()) - attr := styleAttr(svgStyles, token.Type) - if attr != "" { - text = fmt.Sprintf("%s", attr, text) - } - fmt.Fprint(writer, text) + if targetShape.Language == "latex" { + spew.Dump(targetShape.Label) + render, err := d2latex.SVG(targetShape.Label) + if err != nil { + spew.Dump(err) + return err } - fmt.Fprint(writer, "") + fmt.Fprintf(writer, ``, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity) + // render = strings.Replace(render, "svg", "g", -1) + spew.Dump(render) + fmt.Fprintf(writer, render) + fmt.Fprintf(writer, "") + } else { + lexer := lexers.Get(targetShape.Language) + if lexer == nil { + return fmt.Errorf("code snippet lexer for %s not found", targetShape.Language) + } + style := styles.Get("github") + if style == nil { + return errors.New(`code snippet style "github" not found`) + } + formatter := formatters.Get("svg") + if formatter == nil { + return errors.New(`code snippet formatter "svg" not found`) + } + iterator, err := lexer.Tokenise(nil, targetShape.Label) + if err != nil { + return err + } + + svgStyles := styleToSVG(style) + containerStyle := fmt.Sprintf(`stroke: %s;fill:%s`, targetShape.Stroke, style.Get(chroma.Background).Background.String()) + + fmt.Fprintf(writer, ``, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity) + fmt.Fprintf(writer, ``, + targetShape.Width, targetShape.Height, containerStyle) + // Padding + fmt.Fprintf(writer, ``) + + for index, tokens := range chroma.SplitTokensIntoLines(iterator.Tokens()) { + // TODO mono font looks better with 1.2 em (use px equivalent), but textmeasure needs to account for it. Not obvious how that should be done + fmt.Fprintf(writer, "", 1*float64(index+1)) + for _, token := range tokens { + text := svgEscaper.Replace(token.String()) + attr := styleAttr(svgStyles, token.Type) + if attr != "" { + text = fmt.Sprintf("%s", attr, text) + } + fmt.Fprint(writer, text) + } + fmt.Fprint(writer, "") + } + fmt.Fprintf(writer, "") } - fmt.Fprintf(writer, "") case d2target.ShapeText: render, err := textmeasure.RenderMarkdown(targetShape.Label) if err != nil { diff --git a/e2etests/testdata/todo/latex/dagre/board.exp.json b/e2etests/testdata/todo/latex/dagre/board.exp.json index 824b3e667..96c82cce5 100644 --- a/e2etests/testdata/todo/latex/dagre/board.exp.json +++ b/e2etests/testdata/todo/latex/dagre/board.exp.json @@ -2,20 +2,94 @@ "name": "", "shapes": [ { - "id": "hi", - "type": "text", + "id": "a", + "type": "code", "pos": { "x": 0, - "y": 0 + "y": 44 }, - "width": 167, - "height": 24, + "width": 1022, + "height": 38, "level": 1, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "\\\\frac{\\\\alpha g^2}{\\\\omega^5} e^{[ -0.74\\\\bigl\\\\{\\\\frac{\\\\omega U_\\\\omega 19.5}{g}\\\\bigr\\\\}^{\\\\!-4}\\\\,]}", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "latex", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 1022, + "labelHeight": 38 + }, + { + "id": "b", + "type": "code", + "pos": { + "x": 1082, + "y": 44 + }, + "width": 93, + "height": 38, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "e = mc^2", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "latex", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 93, + "labelHeight": 38 + }, + { + "id": "c", + "type": "", + "pos": { + "x": 1022, + "y": 226 + }, + "width": 214, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -27,17 +101,283 @@ "fields": null, "methods": null, "columns": null, - "label": "Inline math $\\frac{1}{2}$", + "label": "mixed together", "fontSize": 16, "fontFamily": "DEFAULT", - "language": "markdown", + "language": "", "color": "#0A0F25", "italic": false, "bold": true, "underline": false, - "labelWidth": 167, - "labelHeight": 24 + "labelWidth": 114, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" + }, + { + "id": "sugar", + "type": "", + "pos": { + "x": 1235, + "y": 0 + }, + "width": 146, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "sugar", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" + }, + { + "id": "solution", + "type": "", + "pos": { + "x": 1047, + "y": 452 + }, + "width": 164, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "solution", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 64, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" } ], - "connections": [] + "connections": [ + { + "id": "(a -> c)[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 511, + "y": 82 + }, + { + "x": 511, + "y": 157.2 + }, + { + "x": 613.1, + "y": 194.6838866396761 + }, + { + "x": 1021.5, + "y": 269.41943319838055 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 1128.5, + "y": 82 + }, + { + "x": 1128.5, + "y": 157.2 + }, + { + "x": 1128.5, + "y": 186 + }, + { + "x": 1128.5, + "y": 226 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(sugar -> c)[0]", + "src": "sugar", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 1308, + "y": 126 + }, + { + "x": 1308, + "y": 166 + }, + { + "x": 1292.2, + "y": 186 + }, + { + "x": 1229, + "y": 226 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(c -> solution)[0]", + "src": "c", + "srcArrow": "none", + "srcLabel": "", + "dst": "solution", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "we get", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 44, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1128.5, + "y": 352 + }, + { + "x": 1128.5, + "y": 392 + }, + { + "x": 1128.5, + "y": 412 + }, + { + "x": 1128.5, + "y": 452 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null + } + ] } diff --git a/e2etests/testdata/todo/latex/dagre/sketch.exp.svg b/e2etests/testdata/todo/latex/dagre/sketch.exp.svg index 7efe84bd8..c086185bf 100644 --- a/e2etests/testdata/todo/latex/dagre/sketch.exp.svg +++ b/e2etests/testdata/todo/latex/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/e2etests/testdata/todo/latex/elk/board.exp.json b/e2etests/testdata/todo/latex/elk/board.exp.json index 945c00919..33b8af3a0 100644 --- a/e2etests/testdata/todo/latex/elk/board.exp.json +++ b/e2etests/testdata/todo/latex/elk/board.exp.json @@ -2,20 +2,94 @@ "name": "", "shapes": [ { - "id": "hi", - "type": "text", + "id": "a", + "type": "code", "pos": { "x": 12, - "y": 12 + "y": 216 }, - "width": 167, - "height": 24, + "width": 1022, + "height": 38, "level": 1, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, "borderRadius": 0, "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "\\\\frac{\\\\alpha g^2}{\\\\omega^5} e^{[ -0.74\\\\bigl\\\\{\\\\frac{\\\\omega U_\\\\omega 19.5}{g}\\\\bigr\\\\}^{\\\\!-4}\\\\,]}", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "latex", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 1022, + "labelHeight": 38 + }, + { + "id": "b", + "type": "code", + "pos": { + "x": 941, + "y": 158 + }, + "width": 93, + "height": 38, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "e = mc^2", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "latex", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 93, + "labelHeight": 38 + }, + { + "id": "c", + "type": "", + "pos": { + "x": 1134, + "y": 114 + }, + "width": 214, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", "stroke": "#0D32B2", "shadow": false, "3d": false, @@ -27,17 +101,263 @@ "fields": null, "methods": null, "columns": null, - "label": "Inline math $\\frac{1}{2}$", + "label": "mixed together", "fontSize": 16, "fontFamily": "DEFAULT", - "language": "markdown", + "language": "", "color": "#0A0F25", "italic": false, "bold": true, "underline": false, - "labelWidth": 167, - "labelHeight": 24 + "labelWidth": 114, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" + }, + { + "id": "sugar", + "type": "", + "pos": { + "x": 888, + "y": 12 + }, + "width": 146, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "sugar", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" + }, + { + "id": "solution", + "type": "", + "pos": { + "x": 1592, + "y": 114 + }, + "width": 164, + "height": 126, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "solution", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 64, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER" } ], - "connections": [] + "connections": [ + { + "id": "(a -> c)[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 1034, + "y": 235 + }, + { + "x": 1084, + "y": 235 + }, + { + "x": 1084, + "y": 208.5 + }, + { + "x": 1134, + "y": 208.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 1034, + "y": 177 + }, + { + "x": 1134, + "y": 177 + } + ], + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(sugar -> c)[0]", + "src": "sugar", + "srcArrow": "none", + "srcLabel": "", + "dst": "c", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 1034, + "y": 75 + }, + { + "x": 1084, + "y": 75 + }, + { + "x": 1084, + "y": 145.5 + }, + { + "x": 1134, + "y": 145.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null + }, + { + "id": "(c -> solution)[0]", + "src": "c", + "srcArrow": "none", + "srcLabel": "", + "dst": "solution", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "we get", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 44, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 1348, + "y": 177 + }, + { + "x": 1592, + "y": 177 + } + ], + "animated": false, + "tooltip": "", + "icon": null + } + ] } diff --git a/e2etests/testdata/todo/latex/elk/sketch.exp.svg b/e2etests/testdata/todo/latex/elk/sketch.exp.svg index 7c84186b0..ef72f515f 100644 --- a/e2etests/testdata/todo/latex/elk/sketch.exp.svg +++ b/e2etests/testdata/todo/latex/elk/sketch.exp.svg @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/e2etests/todo_test.go b/e2etests/todo_test.go index 793c6ca58..a7ecebb82 100644 --- a/e2etests/todo_test.go +++ b/e2etests/todo_test.go @@ -17,9 +17,20 @@ container -> container.second: c->2 }, { name: "latex", - script: `hi: |md -Inline math $\frac{1}{2}$ + script: `a: |latex +\\frac{\\alpha g^2}{\\omega^5} e^{[ -0.74\\bigl\\{\\frac{\\omega U_\\omega 19.5}{g}\\bigr\\}^{\\!-4}\\,]} | + +b: |latex +e = mc^2 +| + +a -> c +b -> c +sugar -> c +c: mixed together + +c -> solution: we get `, }, }