package d2svg
import (
"fmt"
"io"
"github.com/pancsta/d2/d2target"
"github.com/pancsta/d2/d2themes"
"github.com/pancsta/d2/lib/geo"
"github.com/pancsta/d2/lib/label"
"github.com/pancsta/d2/lib/svg"
"github.com/d2lang/util-go/go2"
)
func clipPathForBorderRadius(diagramHash string , shape d2target .Shape ) string {
box := geo .NewBox (
geo .NewPoint (float64 (shape .Pos .X ), float64 (shape .Pos .Y )),
float64 (shape .Width ),
float64 (shape .Height ),
)
topX , topY := box .TopLeft .X +box .Width , box .TopLeft .Y
id := borderRadiusClipPathID (diagramHash , shape .ID )
out := svg .OpenElement ("clipPath" , svg .Attr ("id" , id .String ()))
out += fmt .Sprintf (`<path d="M %s %s L %s %s S %s %s %s %s ` , svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +float64 (shape .BorderRadius )), svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +float64 (shape .BorderRadius )), svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y ), svg .FormatFloat (box .TopLeft .X +float64 (shape .BorderRadius )), svg .FormatFloat (box .TopLeft .Y ))
out += fmt .Sprintf (`L %s %s L %s %s ` , svg .FormatFloat (box .TopLeft .X +box .Width -float64 (shape .BorderRadius )), svg .FormatFloat (box .TopLeft .Y ), svg .FormatFloat (topX -float64 (shape .BorderRadius )), svg .FormatFloat (topY ))
out += fmt .Sprintf (`S %s %s %s %s ` , svg .FormatFloat (topX ), svg .FormatFloat (topY ), svg .FormatFloat (topX ), svg .FormatFloat (topY +float64 (shape .BorderRadius )))
out += fmt .Sprintf (`L %s %s ` , svg .FormatFloat (topX ), svg .FormatFloat (topY +box .Height -float64 (shape .BorderRadius )))
if len (shape .Columns ) != 0 {
out += fmt .Sprintf (`L %s %s L %s %s` , svg .FormatFloat (topX ), svg .FormatFloat (topY +box .Height ), svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +box .Height ))
} else {
out += fmt .Sprintf (`S %s %s %s %s ` , svg .FormatFloat (topX ), svg .FormatFloat (topY +box .Height ), svg .FormatFloat (topX -float64 (shape .BorderRadius )), svg .FormatFloat (topY +box .Height ))
out += fmt .Sprintf (`L %s %s ` , svg .FormatFloat (box .TopLeft .X +float64 (shape .BorderRadius )), svg .FormatFloat (box .TopLeft .Y +box .Height ))
out += fmt .Sprintf (`S %s %s %s %s` , svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +box .Height ), svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +box .Height -float64 (shape .BorderRadius )))
out += fmt .Sprintf (`L %s %s` , svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y +float64 (shape .BorderRadius )))
}
out += fmt .Sprintf (`Z %s %s" ` , svg .FormatFloat (box .TopLeft .X ), svg .FormatFloat (box .TopLeft .Y ))
return out + `fill="none" /> </clipPath>`
}
func borderRadiusClipPathID(diagramHash , shapeID string ) svg .ID {
return svg .LiteralID (fmt .Sprintf ("%s-%s" , diagramHash , svg .SVGID (shapeID )))
}
func tableHeader(diagramHash string , shape d2target .Shape , box *geo .Box , text string , textWidth , textHeight , fontSize float64 , inlineTheme *d2themes .Theme ) string {
rectEl := d2themes .NewThemableElement ("rect" , inlineTheme )
rectEl .X , rectEl .Y = box .TopLeft .X , box .TopLeft .Y
rectEl .Width , rectEl .Height = box .Width , box .Height
rectEl .Fill = shape .Fill
rectEl .FillPattern = shape .FillPattern
rectEl .ClassName = "class_header"
if shape .BorderRadius != 0 {
rectEl .SetClipPathID (borderRadiusClipPathID (diagramHash , shape .ID ))
}
str := rectEl .Render ()
if text != "" {
tl := label .InsideMiddleLeft .GetPointOnBox (
box ,
float64 (d2target .HeaderPadding ),
float64 (shape .Width ),
textHeight ,
)
textEl := d2themes .NewThemableElement ("text" , inlineTheme )
textEl .X = tl .X
textEl .Y = tl .Y + textHeight *3 /4
textEl .Fill = shape .GetFontColor ()
textEl .ClassName = "text"
textEl .Style = fmt .Sprintf ("text-anchor:%s;font-size:%vpx" ,
"start" , 4 +fontSize ,
)
textEl .SetText (text )
str += textEl .Render ()
}
return str
}
func tableRow(shape d2target .Shape , box *geo .Box , nameText , typeText , constraintText string , fontSize , longestNameWidth , longestTypeWidth float64 , inlineTheme *d2themes .Theme ) string {
nameTL := label .InsideMiddleLeft .GetPointOnBox (
box ,
d2target .NamePadding ,
0 ,
fontSize ,
)
textEl := d2themes .NewThemableElement ("text" , inlineTheme )
textEl .X = nameTL .X
textEl .Y = nameTL .Y + fontSize *3 /4
textEl .Fill = shape .PrimaryAccentColor
textEl .ClassName = "text"
textEl .Style = fmt .Sprintf ("text-anchor:%s;font-size:%vpx" , "start" , fontSize )
textEl .SetText (nameText )
out := textEl .Render ()
textEl .X += longestNameWidth + d2target .TypePadding
textEl .Fill = shape .NeutralAccentColor
textEl .SetText (typeText )
out += textEl .Render ()
textEl .X = box .TopLeft .X + (box .Width - d2target .NamePadding )
textEl .Fill = shape .SecondaryAccentColor
textEl .Style = fmt .Sprintf ("text-anchor:%s;font-size:%vpx" , "end" , fontSize )
textEl .SetText (constraintText )
out += textEl .Render ()
return out
}
func drawTable(writer io .Writer , diagramHash string , targetShape d2target .Shape , inlineTheme *d2themes .Theme ) {
rectEl := d2themes .NewThemableElement ("rect" , inlineTheme )
rectEl .X = float64 (targetShape .Pos .X )
rectEl .Y = float64 (targetShape .Pos .Y )
rectEl .Width = float64 (targetShape .Width )
rectEl .Height = float64 (targetShape .Height )
rectEl .Fill , rectEl .Stroke = d2themes .ShapeTheme (targetShape )
rectEl .FillPattern = targetShape .FillPattern
rectEl .ClassName = "shape"
rectEl .Style = targetShape .CSSStyle ()
if targetShape .BorderRadius != 0 {
rectEl .Rx = float64 (targetShape .BorderRadius )
rectEl .Ry = float64 (targetShape .BorderRadius )
}
fmt .Fprint (writer , rectEl .Render ())
box := geo .NewBox (
geo .NewPoint (float64 (targetShape .Pos .X ), float64 (targetShape .Pos .Y )),
float64 (targetShape .Width ),
float64 (targetShape .Height ),
)
rowHeight := box .Height / float64 (1 +len (targetShape .SQLTable .Columns ))
headerBox := geo .NewBox (box .TopLeft , box .Width , rowHeight )
fmt .Fprint (writer ,
tableHeader (diagramHash , targetShape , headerBox , targetShape .Label ,
float64 (targetShape .LabelWidth ), float64 (targetShape .LabelHeight ), float64 (targetShape .FontSize ), inlineTheme ),
)
var longestNameWidth int
var longestTypeWidth int
for _ , f := range targetShape .Columns {
longestNameWidth = go2 .Max (longestNameWidth , f .Name .LabelWidth )
longestTypeWidth = go2 .Max (longestTypeWidth , f .Type .LabelWidth )
}
rowBox := geo .NewBox (box .TopLeft .Copy (), box .Width , rowHeight )
rowBox .TopLeft .Y += headerBox .Height
for idx , f := range targetShape .Columns {
fmt .Fprint (writer ,
tableRow (targetShape , rowBox , f .Name .Label , f .Type .Label , f .ConstraintAbbr (), float64 (targetShape .FontSize ), float64 (longestNameWidth ), float64 (longestTypeWidth ), inlineTheme ),
)
rowBox .TopLeft .Y += rowHeight
lineEl := d2themes .NewThemableElement ("line" , inlineTheme )
if idx == len (targetShape .Columns )-1 && targetShape .BorderRadius != 0 {
lineEl .X1 , lineEl .Y1 = rowBox .TopLeft .X +float64 (targetShape .BorderRadius ), rowBox .TopLeft .Y
lineEl .X2 , lineEl .Y2 = rowBox .TopLeft .X +rowBox .Width -float64 (targetShape .BorderRadius ), rowBox .TopLeft .Y
} else {
lineEl .X1 , lineEl .Y1 = rowBox .TopLeft .X , rowBox .TopLeft .Y
lineEl .X2 , lineEl .Y2 = rowBox .TopLeft .X +rowBox .Width , rowBox .TopLeft .Y
}
lineEl .Stroke = targetShape .Fill
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 .Fprint (writer , svg .EmptyElement ("image" ,
svg .Attr ("href" , targetShape .Icon .String ()),
svg .FloatAttr ("x" , tl .X ),
svg .FloatAttr ("y" , tl .Y ),
svg .IntAttr ("width" , iconSize ),
svg .IntAttr ("height" , iconSize ),
))
}
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .