set up shape specific inner bounding boxes for labels
This commit is contained in:
parent
9b0f942c05
commit
23097370e2
18 changed files with 427 additions and 127 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"oss.terrastruct.com/d2/d2target"
|
||||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/shape"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
)
|
||||
|
||||
|
|
@ -1117,14 +1118,14 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
continue
|
||||
}
|
||||
|
||||
shapeType := strings.ToLower(obj.Attributes.Shape.Value)
|
||||
dslShape := strings.ToLower(obj.Attributes.Shape.Value)
|
||||
|
||||
labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch shapeType {
|
||||
switch dslShape {
|
||||
case d2target.ShapeText, d2target.ShapeClass, d2target.ShapeSQLTable, d2target.ShapeCode:
|
||||
// no labels
|
||||
default:
|
||||
|
|
@ -1134,7 +1135,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
}
|
||||
}
|
||||
|
||||
if shapeType != d2target.ShapeText && obj.Attributes.Label.Value != "" {
|
||||
if dslShape != d2target.ShapeText && obj.Attributes.Label.Value != "" {
|
||||
labelDims.Width += INNER_LABEL_PADDING
|
||||
labelDims.Height += INNER_LABEL_PADDING
|
||||
}
|
||||
|
|
@ -1150,7 +1151,7 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
|
||||
paddingX, paddingY := obj.GetPadding()
|
||||
|
||||
switch shapeType {
|
||||
switch dslShape {
|
||||
case d2target.ShapeSquare, d2target.ShapeCircle:
|
||||
if desiredWidth != 0 || desiredHeight != 0 {
|
||||
paddingX = 0.
|
||||
|
|
@ -1169,6 +1170,12 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
|
|||
obj.Height += float64(paddingY)
|
||||
}
|
||||
}
|
||||
contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(defaultDims.Width), float64(defaultDims.Height))
|
||||
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape]
|
||||
s := shape.NewShape(shapeType, contentBox)
|
||||
newWidth, newHeight := s.GetDimensionsToFit(contentBox.Width, contentBox.Height, paddingX/2)
|
||||
obj.Width = newWidth
|
||||
obj.Height = newHeight
|
||||
}
|
||||
for _, edge := range g.Edges {
|
||||
endpointLabels := []string{}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const (
|
|||
appendixIconRadius = 16
|
||||
)
|
||||
|
||||
var multipleOffset = geo.NewVector(10, -10)
|
||||
var multipleOffset = geo.NewVector(d2target.MULTIPLE_OFFSET, -d2target.MULTIPLE_OFFSET)
|
||||
|
||||
//go:embed tooltip.svg
|
||||
var TooltipIcon string
|
||||
|
|
@ -734,7 +734,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
|
||||
var multipleTL *geo.Point
|
||||
if targetShape.Multiple {
|
||||
multipleTL = tl.AddVector(geo.NewVector(d2target.MULTIPLE_OFFSET, -d2target.MULTIPLE_OFFSET))
|
||||
multipleTL = tl.AddVector(multipleOffset)
|
||||
}
|
||||
|
||||
switch targetShape.Type {
|
||||
|
|
@ -744,13 +744,13 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
drawClass(writer, targetShape)
|
||||
}
|
||||
addAppendixItems(writer, targetShape)
|
||||
fmt.Fprintf(writer, `</g>`)
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, `</g>`)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
case d2target.ShapeSQLTable:
|
||||
if sketchRunner != nil {
|
||||
|
|
@ -758,13 +758,13 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
drawTable(writer, targetShape)
|
||||
}
|
||||
addAppendixItems(writer, targetShape)
|
||||
fmt.Fprintf(writer, `</g>`)
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, `</g>`)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
case d2target.ShapeOval:
|
||||
if targetShape.DoubleBorder {
|
||||
|
|
@ -776,7 +776,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprint(writer, renderDoubleOval(tl, width, height, style))
|
||||
}
|
||||
|
|
@ -789,12 +789,17 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprint(writer, renderOval(tl, width, height, style))
|
||||
}
|
||||
}
|
||||
|
||||
// debugging
|
||||
for _, pathData := range s.GetSVGPathData() {
|
||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||
}
|
||||
|
||||
case d2target.ShapeImage:
|
||||
fmt.Fprintf(writer, `<image href="%s" x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
html.EscapeString(targetShape.Icon.String()),
|
||||
|
|
@ -815,7 +820,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||
|
|
@ -832,7 +837,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
fmt.Fprintf(writer, `<rect x="%d" y="%d" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style)
|
||||
|
|
@ -855,7 +860,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(writer, out)
|
||||
fmt.Fprint(writer, out)
|
||||
} else {
|
||||
for _, pathData := range s.GetSVGPathData() {
|
||||
fmt.Fprintf(writer, `<path d="%s" style="%s"/>`, pathData, style)
|
||||
|
|
@ -864,7 +869,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
}
|
||||
|
||||
// Closes the class=shape
|
||||
fmt.Fprintf(writer, `</g>`)
|
||||
fmt.Fprint(writer, `</g>`)
|
||||
|
||||
if targetShape.Icon != nil && targetShape.Type != d2target.ShapeImage {
|
||||
iconPosition := label.Position(targetShape.IconPosition)
|
||||
|
|
@ -895,7 +900,11 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
} else {
|
||||
box = s.GetInnerBox()
|
||||
}
|
||||
labelTL := labelPosition.GetPointOnBox(box, label.PADDING, float64(targetShape.LabelWidth), float64(targetShape.LabelHeight))
|
||||
labelTL := labelPosition.GetPointOnBox(box, label.PADDING,
|
||||
float64(targetShape.LabelWidth),
|
||||
// TODO consider further
|
||||
float64(targetShape.LabelHeight-d2graph.INNER_LABEL_PADDING),
|
||||
)
|
||||
|
||||
fontClass := "text"
|
||||
if targetShape.Bold {
|
||||
|
|
@ -932,7 +941,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
fmt.Fprintf(writer, `<rect class="shape" width="%d" height="%d" style="%s" />`,
|
||||
targetShape.Width, targetShape.Height, containerStyle)
|
||||
// Padding
|
||||
fmt.Fprintf(writer, `<g transform="translate(6 6)">`)
|
||||
fmt.Fprint(writer, `<g transform="translate(6 6)">`)
|
||||
|
||||
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
|
||||
|
|
@ -947,7 +956,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
}
|
||||
fmt.Fprint(writer, "</text>")
|
||||
}
|
||||
fmt.Fprintf(writer, "</g></g>")
|
||||
fmt.Fprint(writer, "</g></g>")
|
||||
} else if targetShape.Type == d2target.ShapeText && targetShape.Language == "latex" {
|
||||
render, err := d2latex.Render(targetShape.Label)
|
||||
if err != nil {
|
||||
|
|
@ -955,7 +964,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
}
|
||||
fmt.Fprintf(writer, `<g transform="translate(%f %f)">`, box.TopLeft.X, box.TopLeft.Y)
|
||||
fmt.Fprint(writer, render)
|
||||
fmt.Fprintf(writer, "</g>")
|
||||
fmt.Fprint(writer, "</g>")
|
||||
} else if targetShape.Type == d2target.ShapeText && targetShape.Language != "" {
|
||||
render, err := textmeasure.RenderMarkdown(targetShape.Label)
|
||||
if err != nil {
|
||||
|
|
@ -1000,7 +1009,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape, sketchRunner *d2ske
|
|||
|
||||
addAppendixItems(writer, targetShape)
|
||||
|
||||
fmt.Fprintf(writer, closingTag)
|
||||
fmt.Fprint(writer, closingTag)
|
||||
return labelMask, nil
|
||||
}
|
||||
|
||||
|
|
@ -1230,7 +1239,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
fmt.Fprintf(buf, `<style type="text/css">%s</style>`, mdCSS)
|
||||
}
|
||||
if sketchRunner != nil {
|
||||
fmt.Fprintf(buf, d2sketch.DefineFillPattern())
|
||||
fmt.Fprint(buf, d2sketch.DefineFillPattern())
|
||||
}
|
||||
|
||||
// only define shadow filter if a shape uses it
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"math"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -90,6 +91,8 @@ func (s baseShape) GetInnerTopLeft(_, _, padding float64) geo.Point {
|
|||
return *geo.NewPoint(s.Box.TopLeft.X+padding, s.Box.TopLeft.Y+padding)
|
||||
}
|
||||
|
||||
// return the minimum shape dimensions needed to fit content (width x height)
|
||||
// in the shape's innerBox with padding
|
||||
func (s baseShape) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
return width + padding*2, height + padding*2
|
||||
}
|
||||
|
|
@ -209,3 +212,13 @@ func TraceToShapeBorder(shape Shape, rectBorderPoint, prevPoint *geo.Point) *geo
|
|||
|
||||
return geo.NewPoint(math.Round(closestPoint.X), math.Round(closestPoint.Y))
|
||||
}
|
||||
|
||||
func boxPath(box *geo.Box) *svg.SvgPathContext {
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(0, 0))
|
||||
pc.L(false, box.Width, 0)
|
||||
pc.L(false, box.Width, box.Height)
|
||||
pc.L(false, 0, box.Height)
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ type shapeCallout struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const (
|
||||
defaultTipWidth = 30.
|
||||
defaultTipHeight = 45.
|
||||
)
|
||||
|
||||
func NewCallout(box *geo.Box) Shape {
|
||||
return shapeCallout{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,25 +23,31 @@ func NewCallout(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeCallout) GetInnerBox() *geo.Box {
|
||||
height := s.Box.Height
|
||||
tipHeight := 45.0
|
||||
if height < tipHeight*2 {
|
||||
tipHeight = height / 2.0
|
||||
func getTipWidth(box *geo.Box) float64 {
|
||||
tipWidth := defaultTipWidth
|
||||
if box.Width < tipWidth*2 {
|
||||
tipWidth = box.Width / 2.0
|
||||
}
|
||||
height -= tipHeight
|
||||
return tipWidth
|
||||
}
|
||||
|
||||
func getTipHeight(box *geo.Box) float64 {
|
||||
tipHeight := defaultTipHeight
|
||||
if box.Height < tipHeight*2 {
|
||||
tipHeight = box.Height / 2.0
|
||||
}
|
||||
return tipHeight
|
||||
}
|
||||
|
||||
func (s shapeCallout) GetInnerBox() *geo.Box {
|
||||
tipHeight := getTipHeight(s.Box)
|
||||
height := s.Box.Height - tipHeight
|
||||
return geo.NewBox(s.Box.TopLeft.Copy(), s.Box.Width, height)
|
||||
}
|
||||
|
||||
func calloutPath(box *geo.Box) *svg.SvgPathContext {
|
||||
tipWidth := 30.0
|
||||
if box.Width < tipWidth*2 {
|
||||
tipWidth = box.Width / 2.0
|
||||
}
|
||||
tipHeight := 45.0
|
||||
if box.Height < tipHeight*2 {
|
||||
tipHeight = box.Height / 2.0
|
||||
}
|
||||
tipWidth := getTipWidth(box)
|
||||
tipHeight := getTipHeight(box)
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(0, 0))
|
||||
pc.V(true, box.Height-tipHeight)
|
||||
|
|
@ -57,5 +68,19 @@ func (s shapeCallout) Perimeter() []geo.Intersectable {
|
|||
func (s shapeCallout) GetSVGPathData() []string {
|
||||
return []string{
|
||||
calloutPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeCallout) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
// return the minimum shape dimensions needed to fit content (width x height)
|
||||
// in the shape's innerBox with padding
|
||||
baseHeight := height + padding*2
|
||||
if baseHeight < defaultTipHeight {
|
||||
baseHeight *= 2
|
||||
} else {
|
||||
baseHeight += defaultTipHeight
|
||||
}
|
||||
return width + padding*2, baseHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,23 @@ func NewCircle(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeCircle) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
height := s.Box.Height
|
||||
insideTL := s.GetInsidePlacement(width, height, 0)
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
width -= 2 * (insideTL.X - tl.X)
|
||||
height -= 2 * (insideTL.Y - tl.Y)
|
||||
return geo.NewBox(&insideTL, width, height)
|
||||
}
|
||||
|
||||
func (s shapeCircle) AspectRatio1() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s shapeCircle) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
radius := math.Ceil(math.Sqrt(math.Pow(width/2, 2)+math.Pow(height/2, 2))) + padding
|
||||
return radius * 2, radius * 2
|
||||
diameter := math.Ceil(math.Sqrt(2 * math.Pow(math.Max(width, height)+2*padding, 2)))
|
||||
return diameter, diameter
|
||||
}
|
||||
|
||||
func (s shapeCircle) GetInsidePlacement(width, height, padding float64) geo.Point {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,24 @@ func NewCloud(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeCloud) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
height := s.Box.Height
|
||||
insideTL := s.GetInsidePlacement(width, height, 0)
|
||||
aspectRatio := width / height
|
||||
if aspectRatio > CLOUD_WIDE_ASPECT_BOUNDARY {
|
||||
width *= CLOUD_WIDE_INNER_WIDTH
|
||||
height *= CLOUD_WIDE_INNER_HEIGHT
|
||||
} else if aspectRatio < CLOUD_TALL_ASPECT_BOUNDARY {
|
||||
width *= CLOUD_TALL_INNER_WIDTH
|
||||
height *= CLOUD_TALL_INNER_HEIGHT
|
||||
} else {
|
||||
width *= CLOUD_SQUARE_INNER_WIDTH
|
||||
height *= CLOUD_SQUARE_INNER_HEIGHT
|
||||
}
|
||||
return geo.NewBox(&insideTL, width, height)
|
||||
}
|
||||
|
||||
func (s shapeCloud) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
width += padding
|
||||
height += padding
|
||||
|
|
@ -96,5 +114,7 @@ func (s shapeCloud) Perimeter() []geo.Intersectable {
|
|||
func (s shapeCloud) GetSVGPathData() []string {
|
||||
return []string{
|
||||
cloudPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ type shapeCylinder struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const (
|
||||
defaultArcDepth = 24.
|
||||
)
|
||||
|
||||
func NewCylinder(box *geo.Box) Shape {
|
||||
return shapeCylinder{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,46 +22,47 @@ func NewCylinder(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func getArcHeight(box *geo.Box) float64 {
|
||||
arcHeight := defaultArcDepth
|
||||
// Note: box height should always be larger than 3*default
|
||||
// this just handles after collapsing into an oval
|
||||
if box.Height < arcHeight*2 {
|
||||
arcHeight = box.Height / 2.0
|
||||
}
|
||||
return arcHeight
|
||||
}
|
||||
|
||||
func (s shapeCylinder) GetInnerBox() *geo.Box {
|
||||
height := s.Box.Height
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
arcDepth := 24.0
|
||||
if height < arcDepth*2 {
|
||||
arcDepth = height / 2.0
|
||||
}
|
||||
height -= 3 * arcDepth
|
||||
tl.Y += 2 * arcDepth
|
||||
arc := getArcHeight(s.Box)
|
||||
height -= 3 * arc
|
||||
tl.Y += 2 * arc
|
||||
return geo.NewBox(tl, s.Box.Width, height)
|
||||
}
|
||||
|
||||
func cylinderOuterPath(box *geo.Box) *svg.SvgPathContext {
|
||||
arcDepth := 24.0
|
||||
if box.Height < arcDepth*2 {
|
||||
arcDepth = box.Height / 2
|
||||
}
|
||||
arcHeight := getArcHeight(box)
|
||||
multiplier := 0.45
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(0, arcDepth))
|
||||
pc.StartAt(pc.Absolute(0, arcHeight))
|
||||
pc.C(false, 0, 0, box.Width*multiplier, 0, box.Width/2, 0)
|
||||
pc.C(false, box.Width-box.Width*multiplier, 0, box.Width, 0, box.Width, arcDepth)
|
||||
pc.V(true, box.Height-arcDepth*2)
|
||||
pc.C(false, box.Width-box.Width*multiplier, 0, box.Width, 0, box.Width, arcHeight)
|
||||
pc.V(true, box.Height-arcHeight*2)
|
||||
pc.C(false, box.Width, box.Height, box.Width-box.Width*multiplier, box.Height, box.Width/2, box.Height)
|
||||
pc.C(false, box.Width*multiplier, box.Height, 0, box.Height, 0, box.Height-arcDepth)
|
||||
pc.V(true, -(box.Height - arcDepth*2))
|
||||
pc.C(false, box.Width*multiplier, box.Height, 0, box.Height, 0, box.Height-arcHeight)
|
||||
pc.V(true, -(box.Height - arcHeight*2))
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
||||
func cylinderInnerPath(box *geo.Box) *svg.SvgPathContext {
|
||||
arcDepth := 24.0
|
||||
if box.Height < arcDepth*2 {
|
||||
arcDepth = box.Height / 2
|
||||
}
|
||||
arcHeight := getArcHeight(box)
|
||||
multiplier := 0.45
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(0, arcDepth))
|
||||
pc.C(false, 0, arcDepth*2, box.Width*multiplier, arcDepth*2, box.Width/2, arcDepth*2)
|
||||
pc.C(false, box.Width-box.Width*multiplier, arcDepth*2, box.Width, arcDepth*2, box.Width, arcDepth)
|
||||
pc.StartAt(pc.Absolute(0, arcHeight))
|
||||
pc.C(false, 0, arcHeight*2, box.Width*multiplier, arcHeight*2, box.Width/2, arcHeight*2)
|
||||
pc.C(false, box.Width-box.Width*multiplier, arcHeight*2, box.Width, arcHeight*2, box.Width, arcHeight)
|
||||
return pc
|
||||
}
|
||||
|
||||
|
|
@ -69,5 +74,13 @@ func (s shapeCylinder) GetSVGPathData() []string {
|
|||
return []string{
|
||||
cylinderOuterPath(s.Box).PathData(),
|
||||
cylinderInnerPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeCylinder) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
// 2 arcs top, height + padding, 1 arc bottom
|
||||
totalHeight := height + padding*2 + 3*defaultArcDepth
|
||||
return width + padding*2, totalHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,17 @@ func NewDiamond(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeDiamond) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
height := s.Box.Height
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
tl.X += width / 4.
|
||||
tl.Y += height / 4.
|
||||
width /= 2.
|
||||
height /= 2.
|
||||
return geo.NewBox(tl, width, height)
|
||||
}
|
||||
|
||||
func diamondPath(box *geo.Box) *svg.SvgPathContext {
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, box.Width/77, box.Height/76.9)
|
||||
pc.StartAt(pc.Absolute(38.5, 76.9))
|
||||
|
|
@ -41,5 +52,13 @@ func (s shapeDiamond) Perimeter() []geo.Intersectable {
|
|||
func (s shapeDiamond) GetSVGPathData() []string {
|
||||
return []string{
|
||||
diamondPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeDiamond) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := 2 * (width + 2*padding)
|
||||
totalHeight := 2 * (height + 2*padding)
|
||||
return totalWidth, totalHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ type shapeDocument struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const (
|
||||
// the shape is taller than where the bottom of the path ends
|
||||
docPathHeight = 18.925
|
||||
docPathInnerBottom = 14
|
||||
docPathBottom = 16.3
|
||||
)
|
||||
|
||||
func NewDocument(box *geo.Box) Shape {
|
||||
return shapeDocument{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,15 +25,19 @@ func NewDocument(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeDocument) GetInnerBox() *geo.Box {
|
||||
height := s.Box.Height * docPathInnerBottom / docPathHeight
|
||||
return geo.NewBox(s.Box.TopLeft.Copy(), s.Box.Width, height)
|
||||
}
|
||||
|
||||
func documentPath(box *geo.Box) *svg.SvgPathContext {
|
||||
pathHeight := 18.925
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height)
|
||||
pc.StartAt(pc.Absolute(0, 16.3/pathHeight))
|
||||
pc.StartAt(pc.Absolute(0, docPathBottom/docPathHeight))
|
||||
pc.L(false, 0, 0)
|
||||
pc.L(false, 1, 0)
|
||||
pc.L(false, 1, 16.3/pathHeight)
|
||||
pc.C(false, 5/6.0, 12.8/pathHeight, 2/3.0, 12.8/pathHeight, 1/2.0, 16.3/pathHeight)
|
||||
pc.C(false, 1/3.0, 19.8/pathHeight, 1/6.0, 19.8/pathHeight, 0, 16.3/pathHeight)
|
||||
pc.L(false, 1, docPathBottom/docPathHeight)
|
||||
pc.C(false, 5/6.0, 12.8/docPathHeight, 2/3.0, 12.8/docPathHeight, 1/2.0, docPathBottom/docPathHeight)
|
||||
pc.C(false, 1/3.0, 19.8/docPathHeight, 1/6.0, 19.8/docPathHeight, 0, docPathBottom/docPathHeight)
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
|
@ -38,5 +49,12 @@ func (s shapeDocument) Perimeter() []geo.Intersectable {
|
|||
func (s shapeDocument) GetSVGPathData() []string {
|
||||
return []string{
|
||||
documentPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeDocument) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
baseHeight := (height + padding*2) * docPathHeight / docPathInnerBottom
|
||||
return width + padding*2, baseHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@ func NewHexagon(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeHexagon) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
tl.X += width / 4.
|
||||
width /= 2.
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func hexagonPath(box *geo.Box) *svg.SvgPathContext {
|
||||
halfYFactor := 43.6 / 87.3
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, box.Width, box.Height)
|
||||
|
|
@ -38,5 +46,12 @@ func (s shapeHexagon) Perimeter() []geo.Intersectable {
|
|||
func (s shapeHexagon) GetSVGPathData() []string {
|
||||
return []string{
|
||||
hexagonPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeHexagon) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := 2 * (width + 2*padding)
|
||||
return totalWidth, height + 2*padding
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@ func NewOval(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeOval) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
height := s.Box.Height
|
||||
insideTL := s.GetInsidePlacement(width, height, 0)
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
width -= 2 * (insideTL.X - tl.X)
|
||||
height -= 2 * (insideTL.Y - tl.Y)
|
||||
return geo.NewBox(&insideTL, width, height)
|
||||
}
|
||||
|
||||
func (s shapeOval) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
theta := math.Atan2(height, width)
|
||||
// add padding in direction of diagonal so there is padding distance between top left and border
|
||||
|
|
@ -53,3 +63,10 @@ func (s shapeOval) GetInsidePlacement(width, height, padding float64) geo.Point
|
|||
func (s shapeOval) Perimeter() []geo.Intersectable {
|
||||
return []geo.Intersectable{geo.NewEllipse(s.Box.Center(), s.Box.Width/2, s.Box.Height/2)}
|
||||
}
|
||||
|
||||
// debugging
|
||||
func (s shapeOval) GetSVGPathData() []string {
|
||||
return []string{
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ type shapePackage struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const (
|
||||
packageTopMinHeight = 34.
|
||||
packageTopMaxHeight = 55.
|
||||
packageTopMinWidth = 50.
|
||||
packageTopMaxWidth = 150.
|
||||
packageHorizontalScalar = 0.5
|
||||
packageVerticalScalar = 0.2
|
||||
)
|
||||
|
||||
func NewPackage(box *geo.Box) Shape {
|
||||
return shapePackage{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -20,22 +29,27 @@ func NewPackage(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func packagePath(box *geo.Box) *svg.SvgPathContext {
|
||||
const MIN_TOP_HEIGHT = 34
|
||||
const MAX_TOP_HEIGHT = 55
|
||||
const MIN_TOP_WIDTH = 50
|
||||
const MAX_TOP_WIDTH = 150
|
||||
func (s shapePackage) GetInnerBox() *geo.Box {
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
height := s.Box.Height
|
||||
|
||||
const horizontalScalar = 0.5
|
||||
topWidth := box.Width * horizontalScalar
|
||||
if box.Width >= 2*MIN_TOP_WIDTH {
|
||||
topWidth = math.Min(MAX_TOP_WIDTH, math.Max(MIN_TOP_WIDTH, topWidth))
|
||||
}
|
||||
const verticalScalar = 0.2
|
||||
topHeight := box.Height * verticalScalar
|
||||
if box.Height >= 2*MIN_TOP_HEIGHT {
|
||||
topHeight = math.Min(MAX_TOP_HEIGHT, math.Max(MIN_TOP_HEIGHT, topHeight))
|
||||
_, topHeight := getTopDimensions(s.Box)
|
||||
tl.Y += topHeight
|
||||
height -= topHeight
|
||||
return geo.NewBox(tl, s.Box.Width, height)
|
||||
}
|
||||
|
||||
func getTopDimensions(box *geo.Box) (width, height float64) {
|
||||
width = box.Width * packageHorizontalScalar
|
||||
if box.Width >= 2*packageTopMinWidth {
|
||||
width = math.Min(packageTopMaxWidth, math.Max(packageTopMinWidth, width))
|
||||
}
|
||||
height = math.Min(packageTopMaxHeight, box.Height*packageVerticalScalar)
|
||||
return width, height
|
||||
}
|
||||
|
||||
func packagePath(box *geo.Box) *svg.SvgPathContext {
|
||||
topWidth, topHeight := getTopDimensions(box)
|
||||
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(0, 0))
|
||||
|
|
@ -55,5 +69,18 @@ func (s shapePackage) Perimeter() []geo.Intersectable {
|
|||
func (s shapePackage) GetSVGPathData() []string {
|
||||
return []string{
|
||||
packagePath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapePackage) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
innerHeight := height + padding*2
|
||||
// We want to compute what the topHeight will be to add to inner height;
|
||||
// topHeight=(verticalScalar * totalHeight) and totalHeight=(topHeight + innerHeight)
|
||||
// so solving for topHeight we get: topHeight=innerHeight * (verticalScalar/(1-verticalScalar))
|
||||
topHeight := innerHeight * packageVerticalScalar / (1. - packageVerticalScalar)
|
||||
totalHeight := innerHeight + math.Min(topHeight, packageTopMaxHeight)
|
||||
|
||||
return width + padding*2, totalHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package shape
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/geo"
|
||||
"oss.terrastruct.com/d2/lib/svg"
|
||||
)
|
||||
|
|
@ -9,6 +11,12 @@ type shapePage struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: cleanup
|
||||
pageCornerWidth = 20.8164
|
||||
pageCornerHeight = 20.348
|
||||
)
|
||||
|
||||
func NewPage(box *geo.Box) Shape {
|
||||
return shapePage{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,49 +26,51 @@ func NewPage(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
const PAGE_WIDTH = 66.
|
||||
const PAGE_HEIGHT = 79.
|
||||
func (s shapePage) GetInnerBox() *geo.Box {
|
||||
// Note: for simplicity this assumes shape padding is greater than pageCornerSize
|
||||
width := s.Box.Width
|
||||
// consider right hand side occupied by corner for short pages
|
||||
if s.Box.Height < 3*pageCornerHeight {
|
||||
width -= pageCornerWidth
|
||||
}
|
||||
return geo.NewBox(s.Box.TopLeft.Copy(), width, s.Box.Height)
|
||||
}
|
||||
|
||||
func pageOuterPath(box *geo.Box) *svg.SvgPathContext {
|
||||
// TODO: cleanup
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.)
|
||||
baseX := box.Width - PAGE_WIDTH
|
||||
baseY := box.Height - PAGE_HEIGHT
|
||||
pc.StartAt(pc.Absolute(0.5, 0))
|
||||
pc.H(false, baseX+45.1836) // = width-(66+45.1836)
|
||||
pc.C(false, baseX+46.3544, 0.0, baseX+47.479, 0.456297, baseX+48.3189, 1.27202)
|
||||
pc.L(false, baseX+64.6353, 17.12)
|
||||
pc.C(false, baseX+65.5077, 17.9674, baseX+66., 19.1318, baseX+66., 20.348)
|
||||
// baseY is not needed above because the coordinates start at 0
|
||||
pc.V(false, baseY+78.5)
|
||||
pc.C(false, baseX+66.0, baseY+78.7761, baseX+65.7761, baseY+79.0, baseX+65.5, baseY+79.0)
|
||||
pc.H(false, box.Width-20.8164)
|
||||
pc.C(false, box.Width-19.6456, 0.0, box.Width-18.521, 0.456297, box.Width-17.6811, 1.27202)
|
||||
pc.L(false, box.Width-1.3647, 17.12)
|
||||
pc.C(false, box.Width-0.4923, 17.9674, box.Width, 19.1318, box.Width, 20.348)
|
||||
pc.V(false, box.Height-0.5)
|
||||
pc.C(false, box.Width, box.Height-0.2239, box.Width-0.2239, box.Height, box.Width-0.5, box.Height)
|
||||
|
||||
pc.H(false, .499999)
|
||||
pc.C(false, 0.223857, baseY+79.0, 0.0, baseY+78.7761, 0.0, baseY+78.5)
|
||||
pc.H(false, 0.499999)
|
||||
pc.C(false, 0.223857, box.Height, 0, box.Height-0.2239, 0, box.Height-0.5)
|
||||
pc.V(false, 0.499999)
|
||||
pc.C(false, 0.0, 0.223857, 0.223857, 0.0, 0.5, 0.0)
|
||||
pc.C(false, 0, 0.223857, 0.223857, 0, 0.5, 0)
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
||||
func pageInnerPath(box *geo.Box) *svg.SvgPathContext {
|
||||
baseX := box.Width - PAGE_WIDTH
|
||||
baseY := box.Height - PAGE_HEIGHT
|
||||
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1., 1.)
|
||||
pc.StartAt(pc.Absolute(baseX+64.91803, baseY+79.))
|
||||
pc.StartAt(pc.Absolute(box.Width-1.08197, box.Height))
|
||||
pc.H(false, 1.08196)
|
||||
pc.C(true, -0.64918, 0, -1.08196, -0.43287, -1.08196, -1.08219)
|
||||
pc.V(false, 1.08219)
|
||||
pc.C(true, 0, -0.64931, 0.43278, -1.08219, 1.08196, -1.08219)
|
||||
|
||||
pc.H(true, baseX+43.27868)
|
||||
pc.H(true, box.Width-22.72132)
|
||||
pc.C(true, 0.64918, 0, 1.08196, 0.43287, 1.08196, 1.08219)
|
||||
pc.V(true, 17.09863)
|
||||
pc.C(true, 0, 1.29863, 0.86557, 2.38082, 2.38032, 2.38082)
|
||||
pc.H(false, baseX+64.91803)
|
||||
pc.H(false, box.Width-1.08197)
|
||||
pc.C(true, .64918, 0, 1.08196, 0.43287, 1.08196, 1.08196)
|
||||
pc.V(false, baseY+77.91780)
|
||||
pc.C(false, baseX+64.99999, baseY+78.56712, baseX+65.56721, baseY+79, baseX+64.91803, baseY+79)
|
||||
pc.V(false, box.Height-1.0822)
|
||||
pc.C(false, box.Width-1.0, box.Height-0.43288, box.Width-0.43279, box.Height, box.Width-1.08197, box.Height)
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
|
@ -73,5 +83,19 @@ func (s shapePage) GetSVGPathData() []string {
|
|||
return []string{
|
||||
pageOuterPath(s.Box).PathData(),
|
||||
pageInnerPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapePage) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := width + padding*2
|
||||
totalHeight := height + padding*2
|
||||
// add space for corner with short pages
|
||||
if totalHeight < 3*pageCornerHeight {
|
||||
totalWidth += pageCornerWidth
|
||||
}
|
||||
totalWidth = math.Max(totalWidth, 2*pageCornerWidth)
|
||||
totalHeight = math.Max(totalHeight, pageCornerHeight)
|
||||
return totalWidth, totalHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ type shapeParallelogram struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const parallelWedgeWidth = 26.
|
||||
|
||||
func NewParallelogram(box *geo.Box) Shape {
|
||||
return shapeParallelogram{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,8 +20,17 @@ func NewParallelogram(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeParallelogram) GetInnerBox() *geo.Box {
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
width := s.Box.Width - 2*parallelWedgeWidth
|
||||
tl.X += parallelWedgeWidth
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func parallelogramPath(box *geo.Box) *svg.SvgPathContext {
|
||||
wedgeWidth := 26.0
|
||||
wedgeWidth := parallelWedgeWidth
|
||||
// Note: box width should always be larger than parallelWedgeWidth
|
||||
// this just handles after collapsing into a line
|
||||
if box.Width <= wedgeWidth {
|
||||
wedgeWidth = box.Width / 2.0
|
||||
}
|
||||
|
|
@ -40,5 +51,12 @@ func (s shapeParallelogram) Perimeter() []geo.Intersectable {
|
|||
func (s shapeParallelogram) GetSVGPathData() []string {
|
||||
return []string{
|
||||
parallelogramPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeParallelogram) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := width + padding*2 + parallelWedgeWidth*2
|
||||
return totalWidth, height + padding*2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ func NewPerson(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
const (
|
||||
personShoulderWidthFactor = 20.2 / 68.3
|
||||
)
|
||||
|
||||
func (s shapePerson) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
shoulderWidth := personShoulderWidthFactor * width
|
||||
tl.X += shoulderWidth
|
||||
width -= shoulderWidth * 2
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func personPath(box *geo.Box) *svg.SvgPathContext {
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, box.Width/68.3, box.Height/77.4)
|
||||
|
||||
|
|
@ -50,5 +63,16 @@ func (s shapePerson) Perimeter() []geo.Intersectable {
|
|||
func (s shapePerson) GetSVGPathData() []string {
|
||||
return []string{
|
||||
personPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapePerson) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := width + padding*2
|
||||
// see shapePackage
|
||||
shoulderWidth := totalWidth * personShoulderWidthFactor / (1 - 2*personShoulderWidthFactor)
|
||||
totalWidth += 2 * shoulderWidth
|
||||
totalHeight := height + padding*2
|
||||
return totalWidth, totalHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,46 +18,47 @@ func NewQueue(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func getArcWidth(box *geo.Box) float64 {
|
||||
arcWidth := defaultArcDepth
|
||||
// Note: box width should always be larger than 3*default
|
||||
// this just handles after collaping into an oval
|
||||
if box.Width < arcWidth*2 {
|
||||
arcWidth = box.Width / 2.0
|
||||
}
|
||||
return arcWidth
|
||||
}
|
||||
|
||||
func (s shapeQueue) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
arcDepth := 24.0
|
||||
if width < arcDepth*2 {
|
||||
arcDepth = width / 2.0
|
||||
}
|
||||
width -= 3 * arcDepth
|
||||
tl.X += arcDepth
|
||||
arcWidth := getArcWidth(s.Box)
|
||||
width -= 3 * arcWidth
|
||||
tl.X += arcWidth
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func queueOuterPath(box *geo.Box) *svg.SvgPathContext {
|
||||
arcDepth := 24.0
|
||||
arcWidth := getArcWidth(box)
|
||||
multiplier := 0.45
|
||||
if box.Width < arcDepth*2 {
|
||||
arcDepth = box.Width / 2.0
|
||||
}
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(arcDepth, 0))
|
||||
pc.H(true, box.Width-2*arcDepth)
|
||||
pc.StartAt(pc.Absolute(arcWidth, 0))
|
||||
pc.H(true, box.Width-2*arcWidth)
|
||||
pc.C(false, box.Width, 0, box.Width, box.Height*multiplier, box.Width, box.Height/2.0)
|
||||
pc.C(false, box.Width, box.Height-box.Height*multiplier, box.Width, box.Height, box.Width-arcDepth, box.Height)
|
||||
pc.H(true, -1*(box.Width-2*arcDepth))
|
||||
pc.C(false, box.Width, box.Height-box.Height*multiplier, box.Width, box.Height, box.Width-arcWidth, box.Height)
|
||||
pc.H(true, -1*(box.Width-2*arcWidth))
|
||||
pc.C(false, 0, box.Height, 0, box.Height-box.Height*multiplier, 0, box.Height/2.0)
|
||||
pc.C(false, 0, box.Height*multiplier, 0, 0, arcDepth, 0)
|
||||
pc.C(false, 0, box.Height*multiplier, 0, 0, arcWidth, 0)
|
||||
pc.Z()
|
||||
return pc
|
||||
}
|
||||
|
||||
func queueInnerPath(box *geo.Box) *svg.SvgPathContext {
|
||||
arcDepth := 24.0
|
||||
arcWidth := getArcWidth(box)
|
||||
multiplier := 0.45
|
||||
if box.Width < arcDepth*2 {
|
||||
arcDepth = box.Width / 2.0
|
||||
}
|
||||
pc := svg.NewSVGPathContext(box.TopLeft, 1, 1)
|
||||
pc.StartAt(pc.Absolute(box.Width-arcDepth, 0))
|
||||
pc.C(false, box.Width-2*arcDepth, 0, box.Width-2*arcDepth, box.Height*multiplier, box.Width-2*arcDepth, box.Height/2.0)
|
||||
pc.C(false, box.Width-2*arcDepth, box.Height-box.Height*multiplier, box.Width-2*arcDepth, box.Height, box.Width-arcDepth, box.Height)
|
||||
pc.StartAt(pc.Absolute(box.Width-arcWidth, 0))
|
||||
pc.C(false, box.Width-2*arcWidth, 0, box.Width-2*arcWidth, box.Height*multiplier, box.Width-2*arcWidth, box.Height/2.0)
|
||||
pc.C(false, box.Width-2*arcWidth, box.Height-box.Height*multiplier, box.Width-2*arcWidth, box.Height, box.Width-arcWidth, box.Height)
|
||||
return pc
|
||||
}
|
||||
|
||||
|
|
@ -69,5 +70,13 @@ func (s shapeQueue) GetSVGPathData() []string {
|
|||
return []string{
|
||||
queueOuterPath(s.Box).PathData(),
|
||||
queueInnerPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeQueue) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
// 1 arc left, width+ padding, 2 arcs right
|
||||
totalWidth := 3*defaultArcDepth + width + padding*2
|
||||
return totalWidth, height + padding*2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ func NewStep(box *geo.Box) Shape {
|
|||
|
||||
const STEP_WEDGE_WIDTH = 35.0
|
||||
|
||||
func (s shapeStep) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
width -= 2 * STEP_WEDGE_WIDTH
|
||||
tl.X += STEP_WEDGE_WIDTH
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func stepPath(box *geo.Box) *svg.SvgPathContext {
|
||||
wedgeWidth := STEP_WEDGE_WIDTH
|
||||
if box.Width <= wedgeWidth {
|
||||
|
|
@ -43,5 +51,12 @@ func (s shapeStep) Perimeter() []geo.Intersectable {
|
|||
func (s shapeStep) GetSVGPathData() []string {
|
||||
return []string{
|
||||
stepPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeStep) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := width + padding*2 + 2*STEP_WEDGE_WIDTH
|
||||
return totalWidth, height + padding*2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ type shapeStoredData struct {
|
|||
*baseShape
|
||||
}
|
||||
|
||||
const storedDataWedgeWidth = 15.
|
||||
|
||||
func NewStoredData(box *geo.Box) Shape {
|
||||
return shapeStoredData{
|
||||
baseShape: &baseShape{
|
||||
|
|
@ -18,8 +20,16 @@ func NewStoredData(box *geo.Box) Shape {
|
|||
}
|
||||
}
|
||||
|
||||
func (s shapeStoredData) GetInnerBox() *geo.Box {
|
||||
width := s.Box.Width
|
||||
tl := s.Box.TopLeft.Copy()
|
||||
width -= 2 * storedDataWedgeWidth
|
||||
tl.X += storedDataWedgeWidth
|
||||
return geo.NewBox(tl, width, s.Box.Height)
|
||||
}
|
||||
|
||||
func storedDataPath(box *geo.Box) *svg.SvgPathContext {
|
||||
wedgeWidth := 15.0
|
||||
wedgeWidth := storedDataWedgeWidth
|
||||
multiplier := 0.27
|
||||
if box.Width < wedgeWidth*2 {
|
||||
wedgeWidth = box.Width / 2.0
|
||||
|
|
@ -43,5 +53,12 @@ func (s shapeStoredData) Perimeter() []geo.Intersectable {
|
|||
func (s shapeStoredData) GetSVGPathData() []string {
|
||||
return []string{
|
||||
storedDataPath(s.Box).PathData(),
|
||||
// debugging
|
||||
boxPath(s.GetInnerBox()).PathData(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s shapeStoredData) GetDimensionsToFit(width, height, padding float64) (float64, float64) {
|
||||
totalWidth := width + padding*2 + 2*storedDataWedgeWidth
|
||||
return totalWidth, height + padding*2
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue