Skip to content

Commit

Permalink
Merge pull request #2204 from alixander/sequence-markdown
Browse files Browse the repository at this point in the history
use special text as labels
  • Loading branch information
alixander authored Mar 2, 2025
2 parents dd4e6bc + c80a8e0 commit 9c0736b
Show file tree
Hide file tree
Showing 38 changed files with 6,578 additions and 10,756 deletions.
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Icons: connections can include icons [#12](https://github.com/terrastruct/d2/issues/12)
- Syntax: `suspend`/`unsuspend` to define models and instantiate them [#2394](https://github.com/terrastruct/d2/pull/2394)
- Globs: support for filtering edges based on properties of endpoint nodes (e.g., `&src.style.fill: blue`) [#2395](https://github.com/terrastruct/d2/pull/2395)
- Render: markdown, latex, and code can be used as object labels [#2204](https://github.com/terrastruct/d2/pull/2204/files)

#### Improvements 🧹

Expand Down
7 changes: 7 additions & 0 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,14 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
parent := obj
obj = obj.EnsureChild(([]d2ast.String{f.Name}))
if f.Primary() != nil {
var s string
if obj.OuterSequenceDiagram() != nil {
s = obj.Attributes.Shape.Value
}
c.compileLabel(&obj.Attributes, f)
if s != "" {
obj.Attributes.Shape.Value = s
}
}
if f.Map() != nil {
c.compileMap(obj, f.Map())
Expand Down
6 changes: 2 additions & 4 deletions d2exporter/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape {
shape.Pos = d2target.NewPoint(int(obj.TopLeft.X), int(obj.TopLeft.Y))
shape.Width = int(obj.Width)
shape.Height = int(obj.Height)
shape.Language = obj.Language

text := obj.Text()
shape.Bold = text.IsBold
Expand All @@ -166,10 +167,7 @@ func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape {
shape.Color = text.GetColor(shape.Italic)
applyStyles(shape, obj)

switch obj.Shape.Value {
case d2target.ShapeCode, d2target.ShapeText:
shape.Language = obj.Language
shape.Label = obj.Label.Value
switch strings.ToLower(obj.Shape.Value) {
case d2target.ShapeClass:
shape.Class = *obj.Class
// The label is the header for classes and tables, which is set in client to be 4 px larger than the object's set font size
Expand Down
12 changes: 4 additions & 8 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,14 +946,16 @@ func (obj *Object) GetLabelSize(mtexts []*d2target.MText, ruler *textmeasure.Rul

var dims *d2target.TextDimensions
switch shapeType {
case d2target.ShapeText:
case d2target.ShapeClass:
dims = GetTextDimensions(mtexts, ruler, obj.Text(), go2.Pointer(d2fonts.SourceCodePro))
default:
if obj.Language == "latex" {
width, height, err := d2latex.Measure(obj.Text().Text)
if err != nil {
return nil, err
}
dims = d2target.NewTextDimensions(width, height)
} else if obj.Language != "" {
} else if obj.Language != "" && shapeType != d2target.ShapeCode {
var err error
dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text(), fontFamily)
if err != nil {
Expand All @@ -962,12 +964,6 @@ func (obj *Object) GetLabelSize(mtexts []*d2target.MText, ruler *textmeasure.Rul
} else {
dims = GetTextDimensions(mtexts, ruler, obj.Text(), fontFamily)
}

case d2target.ShapeClass:
dims = GetTextDimensions(mtexts, ruler, obj.Text(), go2.Pointer(d2fonts.SourceCodePro))

default:
dims = GetTextDimensions(mtexts, ruler, obj.Text(), fontFamily)
}

if shapeType == d2target.ShapeSQLTable && obj.Label.Value == "" {
Expand Down
121 changes: 77 additions & 44 deletions d2renderers/d2svg/d2svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,82 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
fontClass += " text-underline"
}

if targetShape.Type == d2target.ShapeCode {
if targetShape.Language == "latex" {
render, err := d2latex.Render(targetShape.Label)
if err != nil {
return labelMask, err
}
gEl := d2themes.NewThemableElement("g", inlineTheme)

labelPosition := label.FromString(targetShape.LabelPosition)
if labelPosition == label.Unset {
labelPosition = label.InsideMiddleCenter
}
var box *geo.Box
if labelPosition.IsOutside() {
box = s.GetBox()
} else {
box = s.GetInnerBox()
}
labelTL := labelPosition.GetPointOnBox(box, label.PADDING,
float64(targetShape.LabelWidth),
float64(targetShape.LabelHeight),
)
gEl.SetTranslate(labelTL.X, labelTL.Y)

gEl.Color = targetShape.Stroke
gEl.Content = render
fmt.Fprint(writer, gEl.Render())
} else if targetShape.Language == "markdown" {
render, err := textmeasure.RenderMarkdown(targetShape.Label)
if err != nil {
return labelMask, err
}

labelPosition := label.FromString(targetShape.LabelPosition)
if labelPosition == label.Unset {
labelPosition = label.InsideMiddleCenter
}
var box *geo.Box
if labelPosition.IsOutside() {
box = s.GetBox()
} else {
box = s.GetInnerBox()
}
labelTL := labelPosition.GetPointOnBox(box, label.PADDING,
float64(targetShape.LabelWidth),
float64(targetShape.LabelHeight),
)

fmt.Fprintf(writer, `<g><foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" x="%f" y="%f" width="%d" height="%d">`,
labelTL.X, labelTL.Y, targetShape.LabelWidth, targetShape.LabelHeight,
)

// we need the self closing form in this svg/xhtml context
render = strings.ReplaceAll(render, "<hr>", "<hr />")

mdEl := d2themes.NewThemableElement("div", inlineTheme)
mdEl.ClassName = "md"
mdEl.Content = render

// We have to set with styles since within foreignObject, we're in html
// land and not SVG attributes
var styles []string
if targetShape.FontSize != textmeasure.MarkdownFontSize {
styles = append(styles, fmt.Sprintf("font-size:%vpx", targetShape.FontSize))
}
if targetShape.Fill != "" && targetShape.Fill != "transparent" {
styles = append(styles, fmt.Sprintf(`background-color:%s`, targetShape.Fill))
}
if !color.IsThemeColor(targetShape.Color) {
styles = append(styles, fmt.Sprintf(`color:%s`, targetShape.Color))
}

mdEl.Style = strings.Join(styles, ";")

fmt.Fprint(writer, mdEl.Render())
fmt.Fprint(writer, `</foreignObject></g>`)
} else if targetShape.Language != "" {
lexer := lexers.Get(targetShape.Language)
if lexer == nil {
lexer = lexers.Fallback
Expand Down Expand Up @@ -1478,48 +1553,6 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape
}
fmt.Fprint(writer, "</g></g>")
}
} else if targetShape.Type == d2target.ShapeText && targetShape.Language == "latex" {
render, err := d2latex.Render(targetShape.Label)
if err != nil {
return labelMask, err
}
gEl := d2themes.NewThemableElement("g", inlineTheme)
gEl.SetTranslate(float64(box.TopLeft.X), float64(box.TopLeft.Y))
gEl.Color = targetShape.Stroke
gEl.Content = render
fmt.Fprint(writer, gEl.Render())
} else if targetShape.Type == d2target.ShapeText && targetShape.Language != "" {
render, err := textmeasure.RenderMarkdown(targetShape.Label)
if err != nil {
return labelMask, err
}
fmt.Fprintf(writer, `<g><foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" x="%f" y="%f" width="%d" height="%d">`,
box.TopLeft.X, box.TopLeft.Y, targetShape.Width, targetShape.Height,
)
// we need the self closing form in this svg/xhtml context
render = strings.ReplaceAll(render, "<hr>", "<hr />")

mdEl := d2themes.NewThemableElement("div", inlineTheme)
mdEl.ClassName = "md"
mdEl.Content = render

// We have to set with styles since within foreignObject, we're in html
// land and not SVG attributes
var styles []string
if targetShape.FontSize != textmeasure.MarkdownFontSize {
styles = append(styles, fmt.Sprintf("font-size:%vpx", targetShape.FontSize))
}
if targetShape.Fill != "" && targetShape.Fill != "transparent" {
styles = append(styles, fmt.Sprintf(`background-color:%s`, targetShape.Fill))
}
if !color.IsThemeColor(targetShape.Color) {
styles = append(styles, fmt.Sprintf(`color:%s`, targetShape.Color))
}

mdEl.Style = strings.Join(styles, ";")

fmt.Fprint(writer, mdEl.Render())
fmt.Fprint(writer, `</foreignObject></g>`)
} else {
if targetShape.LabelFill != "" {
rectEl := d2themes.NewThemableElement("rect", inlineTheme)
Expand Down Expand Up @@ -2075,7 +2108,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {

hasMarkdown := false
for _, s := range diagram.Shapes {
if s.Label != "" && s.Type == d2target.ShapeText {
if s.Language == "markdown" {
hasMarkdown = true
break
}
Expand Down
Loading

0 comments on commit 9c0736b

Please sign in to comment.