diff --git a/d2cli/main.go b/d2cli/main.go
index bb4eec08d7..fa18c638eb 100644
--- a/d2cli/main.go
+++ b/d2cli/main.go
@@ -479,19 +479,6 @@ func RouterResolver(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plu
}
func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs fs.FS, layout *string, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, monoFontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, boardPath []string, noChildren, bundle, forceAppendix bool, page playwright.Page, ext exportExtension, asciiMode string) (_ []byte, written bool, _ error) {
- // Use ELK layout for ascii outputs when layout is dagre or unspecified
- if ext == TXT {
- if layout == nil || *layout == "dagre" {
- if ms.Log.Debug != nil {
- prevLayout := "unspecified"
- if layout != nil {
- prevLayout = *layout
- }
- ms.Log.Debug.Printf("switching layout engine to ELK for ASCII format (was %s)", prevLayout)
- }
- layout = go2.Pointer("elk")
- }
- }
start := time.Now()
input, err := ms.ReadPath(inputPath)
@@ -508,6 +495,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
Ruler: ruler,
FontFamily: fontFamily,
MonoFontFamily: monoFontFamily,
+ ASCII: ext == TXT,
InputPath: inputPath,
LayoutResolver: LayoutResolver(ctx, ms, plugins),
Layout: layout,
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index dbd935eb8e..6000902c79 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -56,6 +56,9 @@ type Graph struct {
Layers []*Graph `json:"layers,omitempty"`
Scenarios []*Graph `json:"scenarios,omitempty"`
Steps []*Graph `json:"steps,omitempty"`
+
+ // ASCII indicates that this graph is being rendered for ASCII output
+ ASCII bool `json:"-"`
Theme *d2themes.Theme `json:"theme,omitempty"`
@@ -1002,8 +1005,13 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
labelDims.Width += fontSize
labelDims.Height += fontSize
} else if withLabelPadding {
- labelDims.Width += INNER_LABEL_PADDING
- labelDims.Height += INNER_LABEL_PADDING
+ // Use smaller padding for ASCII rendering since each unit is a character, not a pixel
+ padding := INNER_LABEL_PADDING
+ if ruler != nil && ruler.IsASCII() {
+ padding = 1 // was 5
+ }
+ labelDims.Width += padding
+ labelDims.Height += padding
}
switch dslShape {
@@ -1053,7 +1061,17 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
// └─┴─┴───────┴──────┴───┴──┘
// └─PrefixPadding └──TypePadding
// ├───────┤ + ├───┤ = maxWidth
- dims.Width = d2target.PrefixPadding + d2target.PrefixWidth + maxWidth + d2target.CenterPadding + d2target.TypePadding
+ // Use smaller padding for ASCII rendering since each unit is a character, not a pixel
+ prefixPadding, prefixWidth, centerPadding, typePadding := d2target.PrefixPadding, d2target.PrefixWidth, d2target.CenterPadding, d2target.TypePadding
+ if ruler != nil && ruler.IsASCII() {
+ // ASCII-friendly padding - much smaller since each unit is a character
+ prefixPadding = 1 // was 10
+ prefixWidth = 1 // was 20
+ centerPadding = 2 // was 50
+ typePadding = 1 // was 20
+ }
+
+ dims.Width = prefixPadding + prefixWidth + maxWidth + centerPadding + typePadding
// All rows should be the same height
var anyRowText *d2target.MText
@@ -1114,10 +1132,21 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
// The rows get padded a little due to header font being larger than row font
dims.Height = go2.Max(12, labelDims.Height*(len(obj.SQLTable.Columns)+1))
- headerWidth := d2target.HeaderPadding + labelDims.Width + d2target.HeaderPadding
- rowsWidth := d2target.NamePadding + maxNameWidth + d2target.TypePadding + maxTypeWidth + d2target.TypePadding + maxConstraintWidth
+
+ // Use smaller padding for ASCII rendering since each unit is a character, not a pixel
+ namePadding, typePadding, constraintPadding, headerPadding := d2target.NamePadding, d2target.TypePadding, d2target.ConstraintPadding, d2target.HeaderPadding
+ if ruler != nil && ruler.IsASCII() {
+ // ASCII-friendly padding - much smaller since each unit is a character
+ namePadding = 1 // was 10
+ typePadding = 1 // was 20
+ constraintPadding = 1 // was 20
+ headerPadding = 1 // was 10
+ }
+
+ headerWidth := headerPadding + labelDims.Width + headerPadding
+ rowsWidth := namePadding + maxNameWidth + typePadding + maxTypeWidth + typePadding + maxConstraintWidth
if maxConstraintWidth != 0 {
- rowsWidth += d2target.ConstraintPadding
+ rowsWidth += constraintPadding
}
dims.Width = go2.Max(12, go2.Max(headerWidth, rowsWidth))
}
@@ -1125,6 +1154,155 @@ func (obj *Object) GetDefaultSize(mtexts []*d2target.MText, ruler *textmeasure.R
return &dims, nil
}
+// getMaxReasonableASCIIWidth returns the maximum reasonable width for a shape in ASCII mode
+func getMaxReasonableASCIIWidth(shapeType string, fitWidth float64) float64 {
+ switch shapeType {
+ case shape.PERSON_TYPE:
+ // Person shapes should stay compact - too wide looks odd
+ return math.Max(fitWidth, 12)
+ case shape.CIRCLE_TYPE:
+ // Circles must remain square, so width is constrained by height
+ return math.Max(fitWidth, 15)
+ case shape.DIAMOND_TYPE:
+ // Diamonds can be wider but not excessively so
+ return math.Max(fitWidth, 20)
+ case shape.HEXAGON_TYPE, shape.CYLINDER_TYPE, shape.STORED_DATA_TYPE:
+ // These shapes can handle moderate width
+ return math.Max(fitWidth, 25)
+ case shape.CALLOUT_TYPE, shape.STEP_TYPE, shape.PARALLELOGRAM_TYPE, shape.QUEUE_TYPE:
+ // These shapes work well with moderate width
+ return math.Max(fitWidth, 20)
+ case shape.DOCUMENT_TYPE, shape.PAGE_TYPE, shape.PACKAGE_TYPE:
+ // Document-like shapes can be reasonably wide
+ return math.Max(fitWidth, 30)
+ case shape.OVAL_TYPE, shape.CLOUD_TYPE:
+ // These can be wider since they're meant to be wide
+ return math.Max(fitWidth, 35)
+ default:
+ // Default rectangular shapes can handle reasonable width
+ return math.Max(fitWidth, 25)
+ }
+}
+
+// getMaxReasonableASCIIHeight returns the maximum reasonable height for a shape in ASCII mode
+func getMaxReasonableASCIIHeight(shapeType string, fitHeight float64) float64 {
+ switch shapeType {
+ case shape.PERSON_TYPE:
+ // Person shapes can be taller (full body)
+ return math.Max(fitHeight, 10)
+ case shape.CIRCLE_TYPE:
+ // Circles must remain square
+ return math.Max(fitHeight, 15)
+ case shape.DIAMOND_TYPE:
+ // Diamonds should not be too tall
+ return math.Max(fitHeight, 10)
+ case shape.HEXAGON_TYPE, shape.CYLINDER_TYPE, shape.STORED_DATA_TYPE:
+ // These shapes work well with moderate height
+ return math.Max(fitHeight, 12)
+ case shape.CALLOUT_TYPE, shape.STEP_TYPE, shape.PARALLELOGRAM_TYPE:
+ // These shapes should stay relatively low
+ return math.Max(fitHeight, 8)
+ case shape.QUEUE_TYPE, shape.PACKAGE_TYPE, shape.PAGE_TYPE:
+ // These are typically not very tall
+ return math.Max(fitHeight, 10)
+ case shape.DOCUMENT_TYPE:
+ // Documents can be taller
+ return math.Max(fitHeight, 15)
+ case shape.OVAL_TYPE, shape.CLOUD_TYPE:
+ // These should not be too tall (wider than tall)
+ return math.Max(fitHeight, 8)
+ default:
+ // Default rectangular shapes can handle moderate height
+ return math.Max(fitHeight, 12)
+ }
+}
+
+// getASCIIDimensionsToFit returns ASCII-appropriate dimensions based on actual ASCII shape implementations
+func getASCIIDimensionsToFit(shapeType string, contentWidth, contentHeight, paddingX, paddingY float64) (float64, float64) {
+ // Base content dimensions
+ baseWidth := contentWidth + paddingX
+ baseHeight := contentHeight + paddingY
+
+ switch shapeType {
+ case shape.PERSON_TYPE:
+ // Person: MinPersonHeight=5, needs width for body+arms, head takes HeadHeight=2
+ return math.Max(baseWidth, 4), math.Max(baseHeight, 5)
+
+ case shape.CIRCLE_TYPE:
+ // Circle: Must be square, needs enough space for circular border
+ size := math.Max(math.Max(baseWidth, baseHeight), 5)
+ return size, size
+
+ case shape.DIAMOND_TYPE:
+ // Diamond: Forces odd dimensions, needs space for diagonal paths
+ width := math.Max(baseWidth, 5)
+ height := math.Max(baseHeight, 5)
+ // Ensure odd dimensions like the implementation
+ if int(width)%2 == 0 { width++ }
+ if int(height)%2 == 0 { height++ }
+ return width, height
+
+ case shape.HEXAGON_TYPE:
+ // Hexagon: Uses hoffset=height/2, needs width for top/bottom lines + diagonals
+ return math.Max(baseWidth, 7), math.Max(baseHeight, 5)
+
+ case shape.CYLINDER_TYPE:
+ // Cylinder: MinCylinderHeight=5, needs width>=6 for ellipse (x1+2, x2-2 must be valid)
+ return math.Max(baseWidth, 6), math.Max(baseHeight, 5)
+
+ case shape.STORED_DATA_TYPE:
+ // StoredData: MinStoredDataHeight=5, forces odd height, needs width for straight+curved parts
+ height := math.Max(baseHeight, 5)
+ if int(height)%2 == 0 { height++ }
+ return math.Max(baseWidth, 5), height
+
+ case shape.CALLOUT_TYPE:
+ // Callout: Needs space for speech bubble tail (x2-(tail+2) must be valid)
+ return math.Max(baseWidth, 6), math.Max(baseHeight, 4)
+
+ case shape.STEP_TYPE:
+ // Step: Forces even height, width must be >= height for diagonal cuts
+ height := math.Max(baseHeight, 4)
+ if int(height)%2 == 1 { height++ }
+ width := math.Max(baseWidth, height) // width >= height for cuts to show
+ return math.Max(width, 6), height
+
+ case shape.PARALLELOGRAM_TYPE:
+ // Parallelogram: Width must be > height for top/bottom lines (ix>=x1+hi, ix<=x2-hi)
+ height := math.Max(baseHeight, 3)
+ width := math.Max(baseWidth, height+2) // needs width > height for lines
+ return math.Max(width, 5), height
+
+ case shape.QUEUE_TYPE:
+ // Queue: Needs width>4 for x2-3 to be different from x1,x2
+ return math.Max(baseWidth, 6), math.Max(baseHeight, 3)
+
+ case shape.PACKAGE_TYPE:
+ // Package: Uses x3=x1+wi/2 for tab, needs space for tab to be visible
+ return math.Max(baseWidth, 5), math.Max(baseHeight, 3)
+
+ case shape.DOCUMENT_TYPE:
+ // Document: Uses MaxCurveHeight=3, n=(iw-2)/2 for curves, needs width>=6 for curves
+ return math.Max(baseWidth, 7), math.Max(baseHeight, 4)
+
+ case shape.PAGE_TYPE:
+ // Page: Like rectangle but with fold, needs width>=4 for fold (x2-1 valid), height>=3
+ return math.Max(baseWidth, 4), math.Max(baseHeight, 3)
+
+ case shape.OVAL_TYPE:
+ // Oval: Wider than tall, needs space for curved edges
+ return math.Max(baseWidth, 5), math.Max(baseHeight, 3)
+
+ case shape.CLOUD_TYPE:
+ // Cloud: Complex shape with curves, needs substantial space
+ return math.Max(baseWidth, 6), math.Max(baseHeight, 4)
+
+ default:
+ // Default rectangular shapes (including SQUARE_TYPE, REAL_SQUARE_TYPE): corners + lines
+ return math.Max(baseWidth, 3), math.Max(baseHeight, 3)
+ }
+}
+
// resizes the object to fit content of the given width and height in its inner box with the given padding.
// this accounts for the shape of the object, and if there is a desired width or height set for the object
func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY float64) {
@@ -1133,7 +1311,12 @@ func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY
s := shape.NewShape(shapeType, geo.NewBox(geo.NewPoint(0, 0), contentWidth, contentHeight))
var fitWidth, fitHeight float64
- if shapeType == shape.PERSON_TYPE {
+ isASCII := obj.Graph != nil && obj.Graph.ASCII
+
+ if isASCII {
+ // Use ASCII-appropriate dimensions
+ fitWidth, fitHeight = getASCIIDimensionsToFit(shapeType, contentWidth, contentHeight, paddingX, paddingY)
+ } else if shapeType == shape.PERSON_TYPE {
fitWidth = contentWidth + paddingX
fitHeight = contentHeight + paddingY
} else {
@@ -1143,7 +1326,15 @@ func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY
var desiredWidth int
if obj.WidthAttr != nil {
desiredWidth, _ = strconv.Atoi(obj.WidthAttr.Value)
- obj.Width = float64(desiredWidth)
+ if isASCII {
+ // For ASCII, intelligently scale user dimensions based on shape requirements
+ maxReasonableWidth := getMaxReasonableASCIIWidth(shapeType, fitWidth)
+ scaledWidth := math.Min(float64(desiredWidth), maxReasonableWidth)
+ scaledWidth = math.Max(scaledWidth, fitWidth) // Never go below minimum
+ obj.Width = scaledWidth
+ } else {
+ obj.Width = float64(desiredWidth)
+ }
} else {
obj.Width = fitWidth
}
@@ -1151,7 +1342,15 @@ func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY
var desiredHeight int
if obj.HeightAttr != nil {
desiredHeight, _ = strconv.Atoi(obj.HeightAttr.Value)
- obj.Height = float64(desiredHeight)
+ if isASCII {
+ // For ASCII, intelligently scale user dimensions based on shape requirements
+ maxReasonableHeight := getMaxReasonableASCIIHeight(shapeType, fitHeight)
+ scaledHeight := math.Min(float64(desiredHeight), maxReasonableHeight)
+ scaledHeight = math.Max(scaledHeight, fitHeight) // Never go below minimum
+ obj.Height = scaledHeight
+ } else {
+ obj.Height = float64(desiredHeight)
+ }
} else {
obj.Height = fitHeight
}
@@ -1162,15 +1361,45 @@ func (obj *Object) SizeToContent(contentWidth, contentHeight, paddingX, paddingY
}
if s.AspectRatio1() {
- sideLength := math.Max(obj.Width, obj.Height)
- obj.Width = sideLength
- obj.Height = sideLength
+ // Shapes that must be square (circles, real squares)
+ if isASCII {
+ // For ASCII circles, maintain square but within reasonable limits
+ maxSize := math.Min(getMaxReasonableASCIIWidth(shapeType, obj.Width), getMaxReasonableASCIIHeight(shapeType, obj.Height))
+ sideLength := math.Min(math.Max(obj.Width, obj.Height), maxSize)
+ obj.Width = sideLength
+ obj.Height = sideLength
+ } else {
+ sideLength := math.Max(obj.Width, obj.Height)
+ obj.Width = sideLength
+ obj.Height = sideLength
+ }
} else if desiredHeight == 0 || desiredWidth == 0 {
+ // Apply shape-specific aspect ratio limits
switch shapeType {
case shape.PERSON_TYPE:
- obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, shape.PERSON_AR_LIMIT)
+ if isASCII {
+ // ASCII person should be taller than wide but not extreme
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, 2.5)
+ } else {
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, shape.PERSON_AR_LIMIT)
+ }
case shape.OVAL_TYPE:
- obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, shape.OVAL_AR_LIMIT)
+ if isASCII {
+ // ASCII ovals should be wider than tall
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, 4.0)
+ } else {
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, shape.OVAL_AR_LIMIT)
+ }
+ case shape.CLOUD_TYPE:
+ if isASCII {
+ // ASCII clouds should be wider than tall
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, 3.0)
+ }
+ case shape.PARALLELOGRAM_TYPE, shape.STEP_TYPE:
+ if isASCII {
+ // These shapes work better when wider than tall
+ obj.Width, obj.Height = shape.LimitAR(obj.Width, obj.Height, 2.0)
+ }
}
}
if shapeType == shape.CLOUD_TYPE {
@@ -1548,15 +1777,24 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
dslShape != d2target.ShapeClass {
if dslShape == d2target.ShapeCircle || dslShape == d2target.ShapeSquare {
+ // Use smaller default size for ASCII rendering
sideLength := DEFAULT_SHAPE_SIZE
+ if ruler != nil && ruler.IsASCII() {
+ sideLength = 7 // was 100
+ }
if desiredWidth != 0 || desiredHeight != 0 {
sideLength = float64(go2.Max(desiredWidth, desiredHeight))
}
obj.Width = sideLength
obj.Height = sideLength
} else {
- obj.Width = DEFAULT_SHAPE_SIZE
- obj.Height = DEFAULT_SHAPE_SIZE
+ // Use smaller default size for ASCII rendering
+ defaultSize := DEFAULT_SHAPE_SIZE
+ if ruler != nil && ruler.IsASCII() {
+ defaultSize = 7 // was 100
+ }
+ obj.Width = defaultSize
+ obj.Height = defaultSize
if desiredWidth != 0 {
obj.Width = float64(desiredWidth)
}
@@ -1606,6 +1844,10 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape]
s := shape.NewShape(shapeType, contentBox)
paddingX, paddingY := s.GetDefaultPadding()
+ // Use minimal padding for ASCII rendering
+ if ruler != nil && ruler.IsASCII() {
+ paddingX, paddingY = 1., 1.
+ }
if desiredWidth != 0 {
paddingX = 0.
}
@@ -1619,6 +1861,10 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE, shape.TEXT_TYPE:
default:
labelHeight := float64(labelDims.Height + INNER_LABEL_PADDING)
+ // Use minimal padding for ASCII rendering
+ if ruler != nil && ruler.IsASCII() {
+ labelHeight = 1.
+ }
// Evenly pad enough to fit label above icon
if desiredWidth == 0 {
paddingX += labelHeight
@@ -1633,7 +1879,12 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
case shape.TABLE_TYPE, shape.CLASS_TYPE, shape.CODE_TYPE:
default:
if obj.Link != nil && obj.Tooltip != nil {
- paddingX += 64
+ tooltipPadding := 64.
+ // Use minimal padding for ASCII rendering
+ if ruler != nil && ruler.IsASCII() {
+ tooltipPadding = 1.
+ }
+ paddingX += tooltipPadding
}
}
}
diff --git a/d2graph/layout.go b/d2graph/layout.go
index d0f5a53dac..cd206534bc 100644
--- a/d2graph/layout.go
+++ b/d2graph/layout.go
@@ -411,7 +411,7 @@ func (obj *Object) GetIconTopLeft() *geo.Point {
return iconPosition.GetPointOnBox(box, label.PADDING, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
}
-func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (newStart, newEnd int) {
+func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int, labelPadding float64) (newStart, newEnd int) {
srcShape := edge.Src.ToShape()
dstShape := edge.Dst.ToShape()
@@ -424,12 +424,12 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
if labelPosition.IsOutside() {
labelWidth := float64(edge.Src.LabelDimensions.Width)
labelHeight := float64(edge.Src.LabelDimensions.Height)
- labelTL := labelPosition.GetPointOnBox(edge.Src.Box, label.PADDING, labelWidth, labelHeight)
+ labelTL := labelPosition.GetPointOnBox(edge.Src.Box, labelPadding, labelWidth, labelHeight)
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
- labelBox.TopLeft.X -= label.PADDING
- labelBox.Width += 2 * label.PADDING
+ labelBox.TopLeft.X -= labelPadding
+ labelBox.Width += 2 * labelPadding
for labelBox.Contains(startingSegment.End) && startIndex+1 > endIndex {
startingSegment.Start = startingSegment.End
@@ -459,7 +459,7 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
if iconPosition.IsOutside() {
iconWidth := float64(d2target.MAX_ICON_SIZE)
iconHeight := float64(d2target.MAX_ICON_SIZE)
- iconTL := iconPosition.GetPointOnBox(edge.Src.Box, label.PADDING, iconWidth, iconHeight)
+ iconTL := iconPosition.GetPointOnBox(edge.Src.Box, labelPadding, iconWidth, iconHeight)
iconBox := geo.NewBox(iconTL, iconWidth, iconHeight)
for iconBox.Contains(startingSegment.End) && startIndex+1 > endIndex {
@@ -507,12 +507,12 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
if labelPosition.IsOutside() {
labelWidth := float64(edge.Dst.LabelDimensions.Width)
labelHeight := float64(edge.Dst.LabelDimensions.Height)
- labelTL := labelPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, labelWidth, labelHeight)
+ labelTL := labelPosition.GetPointOnBox(edge.Dst.Box, labelPadding, labelWidth, labelHeight)
labelBox := geo.NewBox(labelTL, labelWidth, labelHeight)
// add left/right padding to box
- labelBox.TopLeft.X -= label.PADDING
- labelBox.Width += 2 * label.PADDING
+ labelBox.TopLeft.X -= labelPadding
+ labelBox.Width += 2 * labelPadding
for labelBox.Contains(endingSegment.Start) && endIndex-1 > startIndex {
endingSegment.End = endingSegment.Start
endingSegment.Start = points[endIndex-2]
@@ -542,7 +542,7 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
iconSize := d2target.GetIconSize(edge.Dst.Box, iconPosition.String())
iconWidth := float64(iconSize)
iconHeight := float64(iconSize)
- labelTL := iconPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, iconWidth, iconHeight)
+ labelTL := iconPosition.GetPointOnBox(edge.Dst.Box, labelPadding, iconWidth, iconHeight)
iconBox := geo.NewBox(labelTL, iconWidth, iconHeight)
for iconBox.Contains(endingSegment.Start) && endIndex-1 > startIndex {
diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go
index 57bf87e725..bf614781c9 100644
--- a/d2js/d2wasm/functions.go
+++ b/d2js/d2wasm/functions.go
@@ -31,7 +31,6 @@ import (
"oss.terrastruct.com/d2/lib/textmeasure"
"oss.terrastruct.com/d2/lib/urlenc"
"oss.terrastruct.com/d2/lib/version"
- "oss.terrastruct.com/util-go/go2"
)
const DEFAULT_INPUT_PATH = "index"
@@ -181,6 +180,7 @@ func Compile(args []js.Value) (interface{}, error) {
compileOpts := &d2lib.CompileOptions{
UTF16Pos: true,
+ ASCII: input.Opts != nil && input.Opts.ASCII != nil && *input.Opts.ASCII,
}
inputPath := DEFAULT_INPUT_PATH
@@ -245,12 +245,6 @@ func Compile(args []js.Value) (interface{}, error) {
compileOpts.Layout = input.Opts.Layout
}
- if input.Opts != nil && input.Opts.ASCII != nil && *input.Opts.ASCII {
- if compileOpts.Layout == nil || *compileOpts.Layout == "dagre" {
- compileOpts.Layout = go2.Pointer("elk")
- }
- }
-
renderOpts := &d2svg.RenderOpts{}
if input.Opts != nil && input.Opts.Sketch != nil {
renderOpts.Sketch = input.Opts.Sketch
diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go
index 3c46e49e64..4a88feb765 100644
--- a/d2layouts/d2dagrelayout/layout.go
+++ b/d2layouts/d2dagrelayout/layout.go
@@ -330,7 +330,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
}
}
- startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex)
+ startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex, float64(label.PADDING))
points = points[startIndex : endIndex+1]
// build a curved path from the dagre route
diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go
index 67e6b466ed..6551b878a0 100644
--- a/d2layouts/d2elklayout/layout.go
+++ b/d2layouts/d2elklayout/layout.go
@@ -122,6 +122,7 @@ var DefaultOpts = ConfigurableOpts{
var port_spacing = 40.
var edge_node_spacing = 40
+var edge_edge_between_layers_spacing = 50
type elkOpts struct {
EdgeNode int `json:"elk.spacing.edgeNode,omitempty"`
@@ -155,6 +156,22 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if opts == nil {
opts = &DefaultOpts
}
+
+ // Override all spacing variables for ASCII mode - use larger values for orthogonal routing
+ if g.ASCII {
+ // Create a copy of opts to avoid modifying the default
+ asciiOpts := *opts
+ asciiOpts.Padding = "[top=2,left=3,bottom=3,right=3]"
+ asciiOpts.NodeSpacing = 3
+ asciiOpts.EdgeNodeSpacing = 2
+ asciiOpts.SelfLoopSpacing = 5
+ opts = &asciiOpts
+ // Override global spacing variables for ASCII mode
+ port_spacing = 3.
+ edge_node_spacing = 3
+ edge_edge_between_layers_spacing = 1
+ }
+
defer xdefer.Errorf(&err, "failed to ELK layout")
runner := jsrunner.NewJSRunner()
@@ -178,7 +195,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
ID: "",
LayoutOptions: &elkOpts{
Thoroughness: 8,
- EdgeEdgeBetweenLayersSpacing: 50,
+ EdgeEdgeBetweenLayersSpacing: edge_edge_between_layers_spacing,
EdgeNode: edge_node_spacing,
HierarchyHandling: "INCLUDE_CHILDREN",
FixedAlignment: "BALANCED",
@@ -195,8 +212,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
},
}
if elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing == DefaultOpts.SelfLoopSpacing {
- // +5 for a tiny bit of padding
- elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(g.Root, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+5)
+ // Use smaller padding for ASCII mode
+ selfLoopPadding := 5
+ if g.ASCII {
+ selfLoopPadding = 1
+ }
+ elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(elkGraph.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(g.Root, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+selfLoopPadding)
}
switch g.Root.Direction.Value {
case "down":
@@ -257,13 +278,24 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if obj.HasLabel() && obj.HasIcon() {
// this gives shapes extra height for their label if they also have an icon
- obj.Height += float64(obj.LabelDimensions.Height + label.PADDING)
+ iconLabelPadding := label.PADDING
+ if g.ASCII {
+ iconLabelPadding = 1
+ }
+ obj.Height += float64(obj.LabelDimensions.Height + iconLabelPadding)
}
- margin, _ := obj.SpacingOpt(label.PADDING, label.PADDING, false)
- width := margin.Left + obj.Width + margin.Right
- height := margin.Top + obj.Height + margin.Bottom
- adjustments[obj] = margin
+ labelPadding := float64(label.PADDING)
+ width := obj.Width
+ height := obj.Height
+ if g.ASCII {
+ labelPadding = 1.
+ } else {
+ margin, _ := obj.SpacingOpt(labelPadding, labelPadding, false)
+ width = margin.Left + obj.Width + margin.Right
+ height = margin.Top + obj.Height + margin.Bottom
+ adjustments[obj] = margin
+ }
n := &ELKNode{
ID: obj.AbsID(),
@@ -275,7 +307,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
n.LayoutOptions = &elkOpts{
ForceNodeModelOrder: true,
Thoroughness: 8,
- EdgeEdgeBetweenLayersSpacing: 50,
+ EdgeEdgeBetweenLayersSpacing: edge_edge_between_layers_spacing,
HierarchyHandling: "INCLUDE_CHILDREN",
FixedAlignment: "BALANCED",
EdgeNode: edge_node_spacing,
@@ -291,7 +323,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
},
}
if n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing == DefaultOpts.SelfLoopSpacing {
- n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(obj, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+5)
+ selfLoopPadding := 5
+ if g.ASCII {
+ selfLoopPadding = 1
+ }
+ n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing = go2.Max(n.LayoutOptions.ConfigurableOpts.SelfLoopSpacing, childrenMaxSelfLoop(obj, g.Root.Direction.Value == "down" || g.Root.Direction.Value == "" || g.Root.Direction.Value == "up")/2+selfLoopPadding)
}
switch elkGraph.LayoutOptions.Direction {
@@ -308,7 +344,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
if obj.IsContainer() {
padding := parsePadding(opts.Padding)
- padding = adjustPadding(obj, width, height, padding)
+ padding = adjustPadding(g, obj, width, height, padding)
n.LayoutOptions.Padding = padding.String()
}
@@ -606,25 +642,32 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err
var originalSrcTL, originalDstTL *geo.Point
// if the edge passes through 3d/multiple, use the offset box for tracing to border
- if srcDx, srcDy := edge.Src.GetModifierElementAdjustments(); srcDx != 0 || srcDy != 0 {
- if start.X > edge.Src.TopLeft.X+srcDx &&
- start.Y < edge.Src.TopLeft.Y+edge.Src.Height-srcDy {
- originalSrcTL = edge.Src.TopLeft.Copy()
- edge.Src.TopLeft.X += srcDx
- edge.Src.TopLeft.Y -= srcDy
+ if !g.ASCII {
+ if srcDx, srcDy := edge.Src.GetModifierElementAdjustments(); srcDx != 0 || srcDy != 0 {
+ if start.X > edge.Src.TopLeft.X+srcDx &&
+ start.Y < edge.Src.TopLeft.Y+edge.Src.Height-srcDy {
+ originalSrcTL = edge.Src.TopLeft.Copy()
+ edge.Src.TopLeft.X += srcDx
+ edge.Src.TopLeft.Y -= srcDy
+ }
}
- }
- if dstDx, dstDy := edge.Dst.GetModifierElementAdjustments(); dstDx != 0 || dstDy != 0 {
- if end.X > edge.Dst.TopLeft.X+dstDx &&
- end.Y < edge.Dst.TopLeft.Y+edge.Dst.Height-dstDy {
- originalDstTL = edge.Dst.TopLeft.Copy()
- edge.Dst.TopLeft.X += dstDx
- edge.Dst.TopLeft.Y -= dstDy
+ if dstDx, dstDy := edge.Dst.GetModifierElementAdjustments(); dstDx != 0 || dstDy != 0 {
+ if end.X > edge.Dst.TopLeft.X+dstDx &&
+ end.Y < edge.Dst.TopLeft.Y+edge.Dst.Height-dstDy {
+ originalDstTL = edge.Dst.TopLeft.Copy()
+ edge.Dst.TopLeft.X += dstDx
+ edge.Dst.TopLeft.Y -= dstDy
+ }
}
}
- startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex)
- points = points[startIndex : endIndex+1]
+ // Use ASCII-adjusted padding if in ASCII mode
+ // padding := float64(label.PADDING)
+ // if g.ASCII {
+ // padding = 1.0
+ // }
+ // startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex, padding)
+ // points = points[startIndex : endIndex+1]
if edge.Label.Value != "" {
edge.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
@@ -690,7 +733,10 @@ func deleteBends(g *d2graph.Graph) {
}
isHorizontal := math.Ceil(start.Y) == math.Ceil(corner.Y)
- dx, dy := endpoint.GetModifierElementAdjustments()
+ var dx, dy float64
+ if !g.ASCII {
+ dx, dy = endpoint.GetModifierElementAdjustments()
+ }
// Make sure it's still attached
switch {
@@ -1015,7 +1061,7 @@ func (padding shapePadding) String() string {
return fmt.Sprintf("[top=%d,left=%d,bottom=%d,right=%d]", padding.top, padding.left, padding.bottom, padding.right)
}
-func adjustPadding(obj *d2graph.Object, width, height float64, padding shapePadding) shapePadding {
+func adjustPadding(g *d2graph.Graph, obj *d2graph.Object, width, height float64, padding shapePadding) shapePadding {
if !obj.IsContainer() {
return padding
}
@@ -1023,8 +1069,12 @@ func adjustPadding(obj *d2graph.Object, width, height float64, padding shapePadd
// compute extra space padding for label/icon
var extraTop, extraBottom, extraLeft, extraRight int
if obj.HasLabel() && obj.LabelPosition != nil {
- labelHeight := obj.LabelDimensions.Height + 2*label.PADDING
- labelWidth := obj.LabelDimensions.Width + 2*label.PADDING
+ labelPadding := 2 * label.PADDING
+ if g.ASCII {
+ labelPadding = 2
+ }
+ labelHeight := obj.LabelDimensions.Height + labelPadding
+ labelWidth := obj.LabelDimensions.Width + labelPadding
switch label.FromString(*obj.LabelPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
// Note: for corners we only add height
@@ -1038,7 +1088,11 @@ func adjustPadding(obj *d2graph.Object, width, height float64, padding shapePadd
}
}
if obj.HasIcon() && obj.IconPosition != nil {
- iconSize := d2target.MAX_ICON_SIZE + 2*label.PADDING
+ iconPadding := 2 * label.PADDING
+ if g.ASCII {
+ iconPadding = 2
+ }
+ iconSize := d2target.MAX_ICON_SIZE + iconPadding
switch label.FromString(*obj.IconPosition) {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
extraTop = go2.Max(extraTop, iconSize)
diff --git a/d2layouts/d2elklayout/wasm.go b/d2layouts/d2elklayout/wasm.go
index 4a27102b4a..e137695bf5 100644
--- a/d2layouts/d2elklayout/wasm.go
+++ b/d2layouts/d2elklayout/wasm.go
@@ -104,10 +104,18 @@ func ConvertGraph(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts)
if obj.HasLabel() && obj.HasIcon() {
// this gives shapes extra height for their label if they also have an icon
- obj.Height += float64(obj.LabelDimensions.Height + label.PADDING)
+ iconLabelPadding := label.PADDING
+ if g.ASCII {
+ iconLabelPadding = 1
+ }
+ obj.Height += float64(obj.LabelDimensions.Height + iconLabelPadding)
}
- margin, _ := obj.SpacingOpt(label.PADDING, label.PADDING, false)
+ labelPadding := float64(label.PADDING)
+ if g.ASCII {
+ labelPadding = 1.
+ }
+ margin, _ := obj.SpacingOpt(labelPadding, labelPadding, false)
width := margin.Left + obj.Width + margin.Right
height := margin.Top + obj.Height + margin.Bottom
adjustments[obj] = margin
@@ -155,7 +163,7 @@ func ConvertGraph(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts)
if obj.IsContainer() {
padding := parsePadding(opts.Padding)
- padding = adjustPadding(obj, width, height, padding)
+ padding = adjustPadding(g, obj, width, height, padding)
n.LayoutOptions.Padding = padding.String()
}
diff --git a/d2layouts/d2grid/layout.go b/d2layouts/d2grid/layout.go
index d07ddb4dec..f72fed8ac0 100644
--- a/d2layouts/d2grid/layout.go
+++ b/d2layouts/d2grid/layout.go
@@ -155,7 +155,7 @@ func Layout(ctx context.Context, g *d2graph.Graph) error {
}
// if edge is grid child, use simple routing
e.Route = []*geo.Point{e.Src.Center(), e.Dst.Center()}
- e.TraceToShape(e.Route, 0, 1)
+ e.TraceToShape(e.Route, 0, 1, float64(label.PADDING))
if e.Label.Value != "" {
e.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
}
diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go
index 87874a6b41..e0aba190c7 100644
--- a/d2layouts/d2layouts.go
+++ b/d2layouts/d2layouts.go
@@ -344,7 +344,7 @@ func DefaultRouter(ctx context.Context, graph *d2graph.Graph, edges []*d2graph.E
for _, e := range edges {
// TODO replace simple straight line edge routing
e.Route = []*geo.Point{e.Src.Center(), e.Dst.Center()}
- e.TraceToShape(e.Route, 0, 1)
+ e.TraceToShape(e.Route, 0, 1, float64(label.PADDING))
if e.Label.Value != "" {
e.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
}
diff --git a/d2layouts/d2sequence/constants.go b/d2layouts/d2sequence/constants.go
index 8d4f429ac2..c8647ffcb4 100644
--- a/d2layouts/d2sequence/constants.go
+++ b/d2layouts/d2sequence/constants.go
@@ -12,6 +12,13 @@ const MIN_ACTOR_DISTANCE = 150.
const MIN_ACTOR_WIDTH = 100.
+// ASCII-specific constants for minimal spacing
+const ASCII_HORIZONTAL_PAD = 1.
+const ASCII_LABEL_HORIZONTAL_PAD = 2.
+const ASCII_VERTICAL_PAD = 1.
+const ASCII_MIN_ACTOR_DISTANCE = 10.
+const ASCII_MIN_ACTOR_WIDTH = 7. // Minimum width for ASCII actors
+
const SELF_MESSAGE_HORIZONTAL_TRAVEL = 80.
const GROUP_CONTAINER_PADDING = 12.
@@ -46,3 +53,39 @@ const (
MESSAGE_Z_INDEX = 4
NOTE_Z_INDEX = 5
)
+
+// Helper functions to get appropriate constants based on ASCII mode
+func getMinActorWidth(isASCII bool) float64 {
+ if isASCII {
+ return ASCII_MIN_ACTOR_WIDTH
+ }
+ return MIN_ACTOR_WIDTH
+}
+
+func getHorizontalPad(isASCII bool) float64 {
+ if isASCII {
+ return ASCII_HORIZONTAL_PAD
+ }
+ return HORIZONTAL_PAD
+}
+
+func getLabelHorizontalPad(isASCII bool) float64 {
+ if isASCII {
+ return ASCII_LABEL_HORIZONTAL_PAD
+ }
+ return LABEL_HORIZONTAL_PAD
+}
+
+func getVerticalPad(isASCII bool) float64 {
+ if isASCII {
+ return ASCII_VERTICAL_PAD
+ }
+ return VERTICAL_PAD
+}
+
+func getMinActorDistance(isASCII bool) float64 {
+ if isASCII {
+ return ASCII_MIN_ACTOR_DISTANCE
+ }
+ return MIN_ACTOR_DISTANCE
+}
diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go
index f21860adca..4f45dffe7f 100644
--- a/d2layouts/d2sequence/sequence_diagram.go
+++ b/d2layouts/d2sequence/sequence_diagram.go
@@ -125,14 +125,15 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
sd.root = actor.Parent
sd.objectRank[actor] = rank
- if actor.Width < MIN_ACTOR_WIDTH {
+ minActorWidth := getMinActorWidth(sd.root.Graph.ASCII)
+ if actor.Width < minActorWidth {
dslShape := strings.ToLower(actor.Shape.Value)
switch dslShape {
case d2target.ShapePerson, d2target.ShapeOval, d2target.ShapeSquare, d2target.ShapeCircle:
// scale shape up to min width uniformly
- actor.Height *= MIN_ACTOR_WIDTH / actor.Width
+ actor.Height *= minActorWidth / actor.Width
}
- actor.Width = MIN_ACTOR_WIDTH
+ actor.Width = minActorWidth
}
sd.maxActorHeight = math.Max(sd.maxActorHeight, actor.Height)
@@ -167,10 +168,10 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
if rank != len(actors)-1 {
actorHW := actor.Width / 2.
nextActorHW := actors[rank+1].Width / 2.
- sd.actorXStep[rank] = math.Max(actorHW+nextActorHW+HORIZONTAL_PAD, MIN_ACTOR_DISTANCE)
- sd.actorXStep[rank] = math.Max(maxNoteWidth/2.+HORIZONTAL_PAD, sd.actorXStep[rank])
+ sd.actorXStep[rank] = math.Max(actorHW+nextActorHW+getHorizontalPad(sd.root.Graph.ASCII), getMinActorDistance(sd.root.Graph.ASCII))
+ sd.actorXStep[rank] = math.Max(maxNoteWidth/2.+getHorizontalPad(sd.root.Graph.ASCII), sd.actorXStep[rank])
if rank > 0 {
- sd.actorXStep[rank-1] = math.Max(maxNoteWidth/2.+HORIZONTAL_PAD, sd.actorXStep[rank-1])
+ sd.actorXStep[rank-1] = math.Max(maxNoteWidth/2.+getHorizontalPad(sd.root.Graph.ASCII), sd.actorXStep[rank-1])
}
}
}
@@ -184,7 +185,7 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
if rankDiff != 0 {
distributedLabelWidth := float64(message.LabelDimensions.Width) / rankDiff
for rank := go2.IntMin(sd.objectRank[message.Src], sd.objectRank[message.Dst]); rank <= go2.IntMax(sd.objectRank[message.Src], sd.objectRank[message.Dst])-1; rank++ {
- sd.actorXStep[rank] = math.Max(sd.actorXStep[rank], distributedLabelWidth+LABEL_HORIZONTAL_PAD)
+ sd.actorXStep[rank] = math.Max(sd.actorXStep[rank], distributedLabelWidth+getLabelHorizontalPad(sd.root.Graph.ASCII))
}
} else {
// self edge
@@ -204,8 +205,8 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) (*s
}
}
- sd.yStep += VERTICAL_PAD
- sd.maxActorHeight += VERTICAL_PAD
+ sd.yStep += getVerticalPad(sd.root.Graph.ASCII)
+ sd.maxActorHeight += getVerticalPad(sd.root.Graph.ASCII)
if sd.root.HasLabel() {
sd.maxActorHeight += float64(sd.root.LabelDimensions.Height)
}
@@ -250,9 +251,9 @@ func (sd *sequenceDiagram) placeGroup(group *d2graph.Object) {
for _, p := range m.Route {
labelHeight := float64(m.LabelDimensions.Height) / 2.
edgePad := math.Max(labelHeight+GROUP_CONTAINER_PADDING, MIN_MESSAGE_DISTANCE/2.)
- minX = math.Min(minX, p.X-HORIZONTAL_PAD)
+ minX = math.Min(minX, p.X-getHorizontalPad(sd.root.Graph.ASCII))
minY = math.Min(minY, p.Y-edgePad)
- maxX = math.Max(maxX, p.X+HORIZONTAL_PAD)
+ maxX = math.Max(maxX, p.X+getHorizontalPad(sd.root.Graph.ASCII))
maxY = math.Max(maxY, p.Y+edgePad)
}
}
@@ -277,9 +278,9 @@ func (sd *sequenceDiagram) placeGroup(group *d2graph.Object) {
}
}
if inGroup {
- minX = math.Min(minX, n.TopLeft.X-HORIZONTAL_PAD)
+ minX = math.Min(minX, n.TopLeft.X-getHorizontalPad(sd.root.Graph.ASCII))
minY = math.Min(minY, n.TopLeft.Y-MIN_MESSAGE_DISTANCE/2.)
- maxX = math.Max(maxX, n.TopLeft.X+n.Width+HORIZONTAL_PAD)
+ maxX = math.Max(maxX, n.TopLeft.X+n.Width+getHorizontalPad(sd.root.Graph.ASCII))
maxY = math.Max(maxY, n.TopLeft.Y+n.Height+MIN_MESSAGE_DISTANCE/2.)
}
}
diff --git a/d2lib/d2.go b/d2lib/d2.go
index aa456abef9..8a7a7b5bf3 100644
--- a/d2lib/d2.go
+++ b/d2lib/d2.go
@@ -42,6 +42,10 @@ type CompileOptions struct {
// MonoFontFamily controls the font family used for code/mono texts
MonoFontFamily *d2fonts.FontFamily
+ // ASCII indicates that the output will be rendered as ASCII text, which affects font choices
+ // for layout calculations to ensure alignment with character grid
+ ASCII bool
+
InputPath string
}
@@ -101,6 +105,7 @@ func compile(ctx context.Context, g *d2graph.Graph, compileOpts *CompileOptions,
}
if len(g.Objects) > 0 {
+ g.ASCII = compileOpts.ASCII
err := g.SetDimensions(compileOpts.MeasuredTexts, compileOpts.Ruler, compileOpts.FontFamily, compileOpts.MonoFontFamily)
if err != nil {
return nil, err
@@ -227,4 +232,10 @@ func applyDefaults(compileOpts *CompileOptions, renderOpts *d2svg.RenderOpts) {
if renderOpts.Center == nil {
renderOpts.Center = go2.Pointer(false)
}
+
+ if compileOpts.ASCII {
+ if compileOpts.Layout == nil || *compileOpts.Layout == "dagre" {
+ compileOpts.Layout = go2.Pointer("elk")
+ }
+ }
}
diff --git a/d2renderers/d2ascii/asciiroute/asciiroute.go b/d2renderers/d2ascii/asciiroute/asciiroute.go
index 7f83cc5a8c..db59dbcef5 100644
--- a/d2renderers/d2ascii/asciiroute/asciiroute.go
+++ b/d2renderers/d2ascii/asciiroute/asciiroute.go
@@ -2,6 +2,7 @@ package asciiroute
import (
"context"
+ "fmt"
"log/slog"
"math"
"strings"
@@ -56,16 +57,44 @@ func DrawRoute(rd RouteDrawer, conn d2target.Connection) {
ctx := rd.GetContext()
log.Debug(ctx, "starting edge route", slog.String("src", conn.Src), slog.String("dst", conn.Dst))
+ // fmt.Printf("DEBUG EDGE: %s -> %s, SrcArrow=%v DstArrow=%v, RoutePoints=%d\n", conn.Src, conn.Dst, conn.SrcArrow, conn.DstArrow, len(routes))
log.Debug(ctx, "initial route points", slog.Int("count", len(routes)))
+ fmt.Printf("ORIGINAL ROUTE %s->%s: ", conn.Src, conn.Dst)
for i, pt := range routes {
log.Debug(ctx, "route point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y))
+ fmt.Printf("[%d](%.3f,%.3f) ", i, pt.X, pt.Y)
}
+ fmt.Printf("\n")
frmShapeBoundary, toShapeBoundary := getConnectionBoundaries(rd, conn.Src, conn.Dst)
log.Debug(ctx, "boundaries", slog.Int("srcTLX", frmShapeBoundary.TL.X), slog.Int("srcTLY", frmShapeBoundary.TL.Y), slog.Int("srcBRX", frmShapeBoundary.BR.X), slog.Int("srcBRY", frmShapeBoundary.BR.Y), slog.Int("dstTLX", toShapeBoundary.TL.X), slog.Int("dstTLY", toShapeBoundary.TL.Y), slog.Int("dstBRX", toShapeBoundary.BR.X), slog.Int("dstBRY", toShapeBoundary.BR.Y))
+ // Debug shape coordinates and dimensions
+ diagram := rd.GetDiagram()
+ if diagram != nil {
+ for _, shape := range diagram.Shapes {
+ if shape.ID == conn.Src {
+ fmt.Printf("SHAPE %s: pos=(%d,%d) size=(%dx%d) boundary=TL(%d,%d)-BR(%d,%d)\n",
+ shape.ID, shape.Pos.X, shape.Pos.Y, shape.Width, shape.Height,
+ frmShapeBoundary.TL.X, frmShapeBoundary.TL.Y, frmShapeBoundary.BR.X, frmShapeBoundary.BR.Y)
+ } else if shape.ID == conn.Dst {
+ fmt.Printf("SHAPE %s: pos=(%d,%d) size=(%dx%d) boundary=TL(%d,%d)-BR(%d,%d)\n",
+ shape.ID, shape.Pos.X, shape.Pos.Y, shape.Width, shape.Height,
+ toShapeBoundary.TL.X, toShapeBoundary.TL.Y, toShapeBoundary.BR.X, toShapeBoundary.BR.Y)
+ }
+ }
+ }
+
+ // Skip shape boundary adjustment for now to see raw routes
routes = processRoute(ctx, rd, routes, frmShapeBoundary, toShapeBoundary)
+ fmt.Printf("PROCESSED ROUTE %s->%s: ", conn.Src, conn.Dst)
+ for i, pt := range routes {
+ fmt.Printf("[%d](%.3f,%.3f) ", i, pt.X, pt.Y)
+ }
+ fmt.Printf("\n")
+
+
turnDir := calculateTurnDirections(routes)
log.Debug(ctx, "turn directions calculated", slog.Int("count", len(turnDir)))
for key, dir := range turnDir {
@@ -97,6 +126,9 @@ func getCharacterMaps(rd RouteDrawer) (corners, arrows map[string]string) {
"-1001": chars.TopLeftCorner(), "0-110": chars.TopLeftCorner(),
"0-1-10": chars.TopRightCorner(), "1001": chars.TopRightCorner(),
"01-10": chars.BottomRightCorner(), "100-1": chars.BottomRightCorner(),
+ // These are straight lines, not corners - they should not be in this map
+ // "0101": chars.Vertical(), "1010": chars.Horizontal(),
+ // "-10-1": chars.Vertical(), "01-01": chars.Horizontal(),
}
arrows = map[string]string{
"0-1": chars.ArrowUp(), "10": chars.ArrowRight(), "01": chars.ArrowDown(), "-10": chars.ArrowLeft(),
diff --git a/d2renderers/d2ascii/asciiroute/drawing.go b/d2renderers/d2ascii/asciiroute/drawing.go
index 4ccb786785..c4b36c8b6f 100644
--- a/d2renderers/d2ascii/asciiroute/drawing.go
+++ b/d2renderers/d2ascii/asciiroute/drawing.go
@@ -88,19 +88,27 @@ func drawRoutePoint(rd RouteDrawer, x, y int, sx, sy float64, segmentIndex, rout
// Check for corners first
if char, ok := corners[turnDir[key]]; ok {
log.Debug(rd.GetContext(), "drawing corner", slog.Int("x", x), slog.Int("y", y), slog.String("char", char), slog.String("direction", turnDir[key]))
+ // fmt.Printf("DEBUG CORNER: pos(%d,%d) dir='%s' char='%s' existing='%c'\n", x, y, turnDir[key], char, existingChar)
canvas.Set(x, y, char)
return
+ } else if dir, exists := turnDir[key]; exists {
+ // This is expected for straight lines like '0101', '1010' etc - they're not corners
+ if dir != "0101" && dir != "1010" && dir != "-10-1" && dir != "01-01" {
+ }
}
// Check for destination arrow
if segmentIndex == routeLen-1 && x == int(math.Round(cx)) && y == int(math.Round(cy)) && conn.DstArrow != d2target.NoArrowhead {
log.Debug(rd.GetContext(), "drawing destination arrow", slog.Int("x", x), slog.Int("y", y))
+ // fmt.Printf("DEBUG DST ARROW: pos(%d,%d) arrowType=%v sx=%f sy=%f routeEnd=(%.1f,%.1f)\n", x, y, conn.DstArrow, sx, sy, cx, cy)
+ // fmt.Printf("DEBUG DST ARROW: checking conditions - segmentIndex=%d routeLen-1=%d x=%d cx_rounded=%d y=%d cy_rounded=%d\n", segmentIndex, routeLen-1, x, int(math.Round(cx)), y, int(math.Round(cy)))
drawArrowhead(rd, x, y, sx, sy, arrows)
if conn.DstLabel != nil {
log.Debug(rd.GetContext(), "drawing destination label", slog.String("label", conn.DstLabel.Label))
drawDestinationLabel(rd, conn.DstLabel.Label, cx, cy, sx, sy)
}
return
+ } else if segmentIndex == routeLen-1 && x == int(math.Round(cx)) && y == int(math.Round(cy)) {
}
// Check for source arrow
@@ -117,6 +125,7 @@ func drawRoutePoint(rd RouteDrawer, x, y int, sx, sy float64, segmentIndex, rout
// Default: draw route segment
log.Debug(rd.GetContext(), "drawing route segment", slog.Int("x", x), slog.Int("y", y), slog.String("existing", string(existingChar)))
+ // fmt.Printf("DEBUG SEGMENT: pos(%d,%d) sx=%f sy=%f existing='%c' segmentIndex=%d routeLen=%d\n", x, y, sx, sy, existingChar, segmentIndex, routeLen)
drawRouteSegment(rd.GetContext(), rd, x, y, sx, sy, frmBoundary, toBoundary)
}
@@ -191,12 +200,14 @@ func drawArrowhead(rd RouteDrawer, x, y int, sx, sy float64, arrows map[string]s
// Place arrow one step back to avoid touching boundary
arrowX := x - int(math.Round(sx))
arrowY := y - int(math.Round(sy))
+ // fmt.Printf("DEBUG ARROWHEAD BOUNDARY: moving from (%d,%d) to (%d,%d)\n", x, y, arrowX, arrowY)
if canvas.IsInBounds(arrowX, arrowY) {
canvas.Set(arrowX, arrowY, arrows[arrowKey])
} else {
canvas.Set(x, y, arrows[arrowKey])
}
} else {
+ // fmt.Printf("DEBUG ARROWHEAD SET: pos(%d,%d) char='%s'\n", x, y, arrowChar)
canvas.Set(x, y, arrows[arrowKey])
}
}
diff --git a/d2renderers/d2ascii/asciiroute/routing.go b/d2renderers/d2ascii/asciiroute/routing.go
index 113d8147b4..3c44adbc99 100644
--- a/d2renderers/d2ascii/asciiroute/routing.go
+++ b/d2renderers/d2ascii/asciiroute/routing.go
@@ -188,11 +188,22 @@ func adjustRouteStartPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Po
secondY := routes[1].Y
log.Debug(ctx, "adjusting start point", slog.Float64("firstX", firstX), slog.Float64("firstY", firstY), slog.Float64("secondX", secondX), slog.Float64("secondY", secondY))
+ fmt.Printf("ADJUST START: from (%.3f,%.3f) to (%.3f,%.3f)\n", firstX, firstY, secondX, secondY)
+ fmt.Printf("START SEGMENT: deltaX=%.3f deltaY=%.3f\n", secondX-firstX, secondY-firstY)
+ if math.Abs(firstY-secondY) < 0.1 {
+ fmt.Printf("START: This is a HORIZONTAL line (Y diff=%.3f < 0.1)\n", math.Abs(firstY-secondY))
+ } else if math.Abs(firstX-secondX) < 0.1 {
+ fmt.Printf("START: This is a VERTICAL line (X diff=%.3f < 0.1)\n", math.Abs(firstX-secondX))
+ } else {
+ fmt.Printf("START: This is a DIAGONAL line (X diff=%.3f, Y diff=%.3f)\n", math.Abs(firstX-secondX), math.Abs(firstY-secondY))
+ }
// Check if end point is inside the to boundary
// Move along the vector of the last segment until outside the boundary if so
if fromBoundary.Contains(int(math.Round(firstX)), int(math.Round(firstY))) {
log.Debug(ctx, "start point inside source boundary, moving along vector")
+ fmt.Printf("START BOUNDARY: point (%.0f,%.0f) is inside boundary TL(%d,%d)-BR(%d,%d)\n",
+ firstX, firstY, fromBoundary.TL.X, fromBoundary.TL.Y, fromBoundary.BR.X, fromBoundary.BR.Y)
vectorX := secondX - firstX
vectorY := secondY - firstY
@@ -201,6 +212,7 @@ func adjustRouteStartPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Po
vectorX /= length
vectorY /= length
log.Debug(ctx, "movement vector", slog.Float64("x", vectorX), slog.Float64("y", vectorY))
+ fmt.Printf("START VECTOR: moving along segment direction (%.3f,%.3f)\n", vectorX, vectorY)
steps := 0
for fromBoundary.Contains(int(math.Round(routes[0].X)), int(math.Round(routes[0].Y))) {
@@ -209,12 +221,13 @@ func adjustRouteStartPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Po
steps++
}
log.Debug(ctx, "moved to exit boundary", slog.Int("steps", steps), slog.Float64("x", routes[0].X), slog.Float64("y", routes[0].Y))
+ fmt.Printf("START MOVED: after %d steps to (%.3f,%.3f)\n", steps, routes[0].X, routes[0].Y)
}
return
}
// Determine line direction and keep shifting until empty space
- if math.Abs(firstY-secondY) < 0.1 { // Horizontal line
+ if math.Abs(firstX-secondX) < 0.1 { // Vertical line (X coordinates are same)
log.Debug(ctx, "horizontal line detected")
deltaX := 0.0
if secondX > firstX {
@@ -288,7 +301,7 @@ func adjustRouteEndPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Poin
}
// Determine line direction and keep shifting until empty space
- if math.Abs(lastY-secondLastY) < 0.1 { // Horizontal line
+ if math.Abs(lastY-secondLastY) < 0.1 { // Horizontal line (Y coordinates are same)
log.Debug(ctx, "horizontal line detected")
deltaX := 0.0
if secondLastX > lastX {
@@ -302,7 +315,7 @@ func adjustRouteEndPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Poin
if deltaX != 0 {
shiftPointUntilEmpty(ctx, rd, &routes[lastIdx].X, &routes[lastIdx].Y, deltaX, 0)
}
- } else if math.Abs(lastX-secondLastX) < 0.1 { // Vertical line
+ } else if math.Abs(lastX-secondLastX) < 0.1 { // Vertical line (X coordinates are same)
log.Debug(ctx, "vertical line detected")
deltaY := 0.0
if secondLastY > lastY {
diff --git a/d2renderers/d2ascii/asciishapes/asciishapes.go b/d2renderers/d2ascii/asciishapes/asciishapes.go
index 4ef547b655..e8c2882fce 100644
--- a/d2renderers/d2ascii/asciishapes/asciishapes.go
+++ b/d2renderers/d2ascii/asciishapes/asciishapes.go
@@ -27,6 +27,7 @@ const (
HeadHeight = 2
MinCylinderHeight = 5
MinStoredDataHeight = 5
+ MinPersonHeight = 5
MaxCurveHeight = 3
)
diff --git a/d2renderers/d2ascii/asciishapes/person.go b/d2renderers/d2ascii/asciishapes/person.go
index 8347d41cd0..13f7fac1b6 100644
--- a/d2renderers/d2ascii/asciishapes/person.go
+++ b/d2renderers/d2ascii/asciishapes/person.go
@@ -4,6 +4,9 @@ import "math"
func DrawPerson(ctx *Context, x, y, w, h float64, label, labelPosition string) {
xi, yi, wi, hi := ctx.Calibrate(x, y, w, h)
+ if hi < MinPersonHeight {
+ hi = MinPersonHeight
+ }
x1, y1 := xi, yi
x2, y2 := xi+wi-1, yi+hi-1
head := HeadHeight
diff --git a/d2renderers/d2ascii/d2ascii.go b/d2renderers/d2ascii/d2ascii.go
index 557291e499..3d7aaa56a4 100644
--- a/d2renderers/d2ascii/d2ascii.go
+++ b/d2renderers/d2ascii/d2ascii.go
@@ -17,8 +17,8 @@ import (
)
const (
- defaultFontWidth = 9.75
- defaultFontHeight = 18.0
+ defaultFontWidth = 1
+ defaultFontHeight = 1
defaultScale = 1.0
)
@@ -53,18 +53,13 @@ func NewBoundary(tl, br Point) *Boundary {
}
func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) {
+ log.Debug(a.ctx, "GetBoundary called", slog.String("id", s.ID), slog.String("label", s.Label))
- // For multiple shapes, expand boundary to match the expanded rendering
posX := float64(s.Pos.X)
posY := float64(s.Pos.Y)
width := float64(s.Width)
height := float64(s.Height)
-
- if s.Multiple {
- posX -= d2target.MULTIPLE_OFFSET // Move left to include shadow area
- width += d2target.MULTIPLE_OFFSET // Include shadow width
- height += d2target.MULTIPLE_OFFSET // Include shadow height
- }
+ log.Debug(a.ctx, "original shape dimensions", slog.Float64("posX", posX), slog.Float64("posY", posY), slog.Float64("width", width), slog.Float64("height", height))
// Use the same calibration logic as the drawing functions
shapeCtx := &asciishapes.Context{
@@ -76,6 +71,7 @@ func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) {
Ctx: a.ctx,
}
x1, y1, wC, hC := shapeCtx.Calibrate(posX, posY, width, height)
+ log.Debug(a.ctx, "calibrated dimensions", slog.Int("x1", x1), slog.Int("y1", y1), slog.Int("wC", wC), slog.Int("hC", hC))
// Apply the same width adjustments as the drawing code
preserveWidth := hasConnectionsAtRightEdge(s, a.diagram.Connections, a.FW)
@@ -101,8 +97,10 @@ func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) {
// Apply the same width adjustments as DrawRect for labels
wC = asciishapes.AdjustWidthForLabel(shapeCtx, posX, posY, width, height, wC, s.Label)
+ log.Debug(a.ctx, "final width after label adjustment", slog.Int("wC", wC))
x2, y2 := x1+wC, y1+hC
+ log.Debug(a.ctx, "final boundary", slog.Int("x1", x1), slog.Int("y1", y1), slog.Int("x2", x2), slog.Int("y2", y2))
return Point{X: x1, Y: y1}, Point{X: x2, Y: y2}
}
@@ -139,6 +137,21 @@ func NewASCIIartist() *ASCIIartist {
func (a *ASCIIartist) calculateExtendedBounds(diagram *d2target.Diagram) (tl, br d2target.Point) {
tl, br = diagram.NestedBoundingBox()
+ log.Debug(a.ctx, "initial bounding box", slog.Int("tl.X", tl.X), slog.Int("tl.Y", tl.Y), slog.Int("br.X", br.X), slog.Int("br.Y", br.Y))
+
+ // Log each shape's contribution to bounds
+ for i, shape := range diagram.Shapes {
+ log.Debug(a.ctx, "shape bounds",
+ slog.Int("index", i),
+ slog.String("id", shape.ID),
+ slog.String("label", shape.Label),
+ slog.Int("pos.X", shape.Pos.X),
+ slog.Int("pos.Y", shape.Pos.Y),
+ slog.Int("width", shape.Width),
+ slog.Int("height", shape.Height),
+ slog.Int("right_edge", shape.Pos.X+shape.Width),
+ slog.Int("bottom_edge", shape.Pos.Y+shape.Height))
+ }
for _, conn := range diagram.Connections {
if conn.Label != "" && len(conn.Route) > 1 {
@@ -243,32 +256,46 @@ func (a *ASCIIartist) Render(ctx context.Context, diagram *d2target.Diagram, opt
yOffset := 0
a.diagram = *diagram
tl, br := a.calculateExtendedBounds(diagram)
+ log.Debug(ctx, "extended bounds calculated", slog.Int("tl.X", tl.X), slog.Int("tl.Y", tl.Y), slog.Int("br.X", br.X), slog.Int("br.Y", br.Y))
if tl.X < 0 {
xOffset = -tl.X
br.X += -tl.X
tl.X = 0
+ log.Debug(ctx, "adjusted for negative X", slog.Int("xOffset", xOffset), slog.Int("new_br.X", br.X))
}
if tl.Y < 0 {
yOffset = -tl.Y
br.Y += -tl.Y
tl.Y = 0
+ log.Debug(ctx, "adjusted for negative Y", slog.Int("yOffset", yOffset), slog.Int("new_br.Y", br.Y))
}
w := int(math.Ceil(float64(br.X - tl.X)))
h := int(math.Ceil(float64(br.Y - tl.Y)))
+ log.Debug(ctx, "raw canvas dimensions", slog.Int("width", w), slog.Int("height", h))
w = int(math.Round((float64(w) / a.FW) * a.SCALE))
h = int(math.Round((float64(h) / a.FH) * a.SCALE))
- maxLabelLen := 0
- for _, shape := range diagram.Shapes {
- if len(shape.Label) > maxLabelLen {
- maxLabelLen = len(shape.Label)
- }
+ // Calculate the actual needed canvas size based on shape positions after offset adjustments
+ canvasWidth := br.X + xOffset
+ canvasHeight := br.Y + yOffset
+ log.Debug(ctx, "calculated canvas size", slog.Int("canvasWidth", canvasWidth), slog.Int("canvasHeight", canvasHeight), slog.Int("xOffset", xOffset), slog.Int("yOffset", yOffset))
+
+ // Use the larger of the two width calculations to ensure all content fits
+ if w < canvasWidth {
+ w = canvasWidth
+ log.Debug(ctx, "using absolute canvas width", slog.Int("width", w))
+ }
+ if h < canvasHeight {
+ h = canvasHeight
+ log.Debug(ctx, "using absolute canvas height", slog.Int("height", h))
}
- padding := maxLabelLen + asciishapes.MinLabelPadding
- log.Debug(ctx, "canvas setup", slog.Int("maxLabelLen", maxLabelLen), slog.Int("padding", padding), slog.Int("width", w+padding+1), slog.Int("height", h+padding+1))
- a.canvas = asciicanvas.New(w+padding+1, h+padding+1)
+ // Add minimal padding for label overflow
+ padding := 2
+ log.Debug(ctx, "canvas setup", slog.Int("padding", padding), slog.Int("final_width", w+padding), slog.Int("final_height", h+padding))
+
+ a.canvas = asciicanvas.New(w+padding, h+padding)
log.Debug(ctx, "processing shapes", slog.Int("count", len(diagram.Shapes)), slog.Int("xOffset", xOffset), slog.Int("yOffset", yOffset))
for i, shape := range diagram.Shapes {
@@ -304,24 +331,11 @@ func (a *ASCIIartist) Render(ctx context.Context, diagram *d2target.Diagram, opt
}
}
- // For multiple shapes, expand to fill the entire space that would be occupied by the multiple effect
drawX := float64(adjustedX)
drawY := float64(adjustedY)
drawWidth := float64(adjustedWidth)
drawHeight := float64(shape.Height)
- if shape.Multiple {
- log.Debug(ctx, "multiple shape adjustments", slog.Int("offset", d2target.MULTIPLE_OFFSET))
- // Move position to top-left of total occupied area (shadow extends left and down)
- drawX -= d2target.MULTIPLE_OFFSET // Move left to include shadow area
- // Y stays the same since shadow goes down, not up
-
- // Expand size to fill entire multiple effect area
- drawWidth += d2target.MULTIPLE_OFFSET // Include shadow width
- drawHeight += d2target.MULTIPLE_OFFSET // Include shadow height
- log.Debug(ctx, "multiple dimensions", slog.Float64("origX", float64(shape.Pos.X)), slog.Float64("origY", float64(shape.Pos.Y)), slog.Float64("origW", float64(shape.Width)), slog.Float64("origH", float64(shape.Height)), slog.Float64("drawX", drawX), slog.Float64("drawY", drawY), slog.Float64("drawW", drawWidth), slog.Float64("drawH", drawHeight))
- }
-
log.Debug(ctx, "final draw parameters", slog.Float64("x", drawX), slog.Float64("y", drawY), slog.Float64("width", drawWidth), slog.Float64("height", drawHeight), slog.String("label", shape.Label))
log.Debug(ctx, "drawing shape", slog.String("type", shape.Type))
diff --git a/e2etests/asciitxtar.txt b/e2etests/asciitxtar.txt
index c844de82b8..b30a5ec217 100644
--- a/e2etests/asciitxtar.txt
+++ b/e2etests/asciitxtar.txt
@@ -1,3 +1,14 @@
+-- shape-container --
+a: {
+ b.shape: step
+}
+
+c: {
+ d.shape: cylinder
+}
+
+a.b -> c.d
+
-- three-right --
direction: right
x -> y -> z
@@ -42,7 +53,6 @@ network: {
user: {
shape: person
- width: 130
}
user -> network.cell tower: make call
@@ -87,7 +97,6 @@ network: {
user: {
shape: person
- width: 130
}
user -> network.cell tower: make call
@@ -267,3 +276,8 @@ b: "A"
shape: sequence_diagram
alice -> bob: What does it mean\nto be well-adjusted?
bob -> alice: The ability to play bridge or\ngolf as if they were games.
+
+-- long-sequence --
+shape: sequence_diagram
+alice -> bob: What does it mean to be well-adjusted?
+bob -> alice: The ability to play bridge or golf as if they were games.
diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go
index 99d555b555..815b398c19 100644
--- a/e2etests/e2e_test.go
+++ b/e2etests/e2e_test.go
@@ -119,7 +119,7 @@ func runASCIITxtarTest(t *testing.T, tc testCase) {
ctx = log.WithTB(ctx, t)
ctx = log.Leveled(ctx, slog.LevelDebug)
- ruler, err := textmeasure.NewRuler()
+ ruler, err := textmeasure.NewASCIIRuler()
trequire.Nil(t, err)
serde(t, tc, ruler)
@@ -131,8 +131,9 @@ func runASCIITxtarTest(t *testing.T, tc testCase) {
compileOpts := &d2lib.CompileOptions{
Ruler: ruler,
- Layout: go2.Pointer("elk"),
LayoutResolver: layoutResolver,
+ Layout: go2.Pointer("elk"),
+ ASCII: true,
}
renderOpts := &d2svg.RenderOpts{
Pad: go2.Pointer(int64(0)),
diff --git a/e2etests/testdata/asciitxtar/all-shapes/extended.exp.txt b/e2etests/testdata/asciitxtar/all-shapes/extended.exp.txt
index e6267d69bc..056004a37b 100644
--- a/e2etests/testdata/asciitxtar/all-shapes/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/all-shapes/extended.exp.txt
@@ -1,23 +1,29 @@
- ┌────────┐ ***
- ╱‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾╱ ╱‾‾‾‾‾‾‾‾‾╱╲ │callout │ **** **** ╱‾‾‾‾‾‾‾‾‾╲
-┌──────────┐ ╱ ╱ │ │ │ └────────┘ *** diamond *** ╱ ╲
-│rectangle │ ╱ parallelogram ╱ │ queue │ │ │ ╱ **** **** ╲ hexagon ╱
-│ │ ╱_______________╱ ╲________ ╲╱ │╱ ***** ╲_________╱
-└──────────┘
- │ │ │ │ │ │
- ▼ ▼ ▼ ▼ │ ▼
- ┌─────────┐ ┌──────────┐ ┌────┐ ╱‾‾‾‾‾‾‾‾‾‾‾╱ ▼ ┌☁─────────┐
- │ │ │ document │ │ └────┐ ╱ ╱ ┌⬭────────┐ │ │
- │ square │ │ .-`-.│ │ package │ │ stored_data │ oval │ │ cloud │
- │ │ `-.-` └─────────┘ ╲ ╲ │ │ │ │
- │ │ ╲___________╲ └─────────┘ │ │
- └─────────┘ │ │ │ │ └──────────┘
- │ │ │ │ │
- ▼ ▼ ▼ ▼ │
- ┌─────┐ .-‾‾‾‾-. ╲‾‾‾‾‾‾‾ ╲ ╱‾‾╲ ▼
- │ ╲┐ │╲-____-╱│ ╲ ╲ ╲__╱ ┌⬭────────┐
- │ page │ │ │ ╲ ╲ ╱‾‾‾‾╲ │ │
- │ │ │ │ ╱ step ╱ ‾‾‾‾‾‾ │ circle │
- └──────┘ │cylinder│ ╱ ╱ person │ │
- │ │ ╱_______ ╱ │ │
- ╲-____-╱ └─────────┘
+ ╱‾‾‾‾‾‾╱
+ ╱ ╱
+ ╱ ╱
+ ╱ ╱ * ╱‾‾‾╲
+ parallelogram ┌───────┐ ** ** ╱ ╲
+┌──────────┐ ╱ ╱ ╱‾‾╱╲ └callout┘ *diamond* ╳hexagon╳
+│rectangle │ ╱ ╱ │queue│ │ ╱ ** ** ╲ ╱
+│ │ ╱______╱ ╲_ ╲╱ │ ╱ *** ╲___╱
+└──────────┘
+ │ │ │ │ │ │
+ ▼ │ │ ▼ │ ▼
+ ┌───────┐ ▼ ▼ ╱‾‾‾‾‾‾‾‾‾╱ ▼ ┌☁─────┐
+ │ │ ┌────────┐ ┌───┐ ╱ ╱ ┌⬭────┐ │cloud │
+ │ │ │document│ │package┐ │stored_data │oval │ │ │
+ │square │ │ .-`.│ └───────┘ ╲ ╲ │ │ └──────┘
+ │ │ `-.` ╲_________╲ └─────┘
+ │ │ │ │
+ │ │ │ │ │ │
+ └───────┘ │ │ │ │
+ │ │ │ │ │
+ ▼ ▼ ▼ ▼ │
+ ┌───┐ .-‾‾‾‾-. ╲‾‾ ╲ ╱‾‾╲ ▼
+ │page┐ │╲-____-╱│ ╲ ╲ ╲__╱ ┌⬭──────┐
+ └────┘ │ │ step╱ ╱‾‾‾‾╲ │ │
+ │cylinder│ ╱__ ╱ ╱ ╲ │ │
+ ╲-____-╱ ‾‾‾‾‾‾‾‾ │circle │
+ person │ │
+ │ │
+ └───────┘
diff --git a/e2etests/testdata/asciitxtar/all-shapes/sketch.exp.svg b/e2etests/testdata/asciitxtar/all-shapes/sketch.exp.svg
index 8ab1d6b57a..c73e37bb5d 100644
--- a/e2etests/testdata/asciitxtar/all-shapes/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/all-shapes/sketch.exp.svg
@@ -1,9 +1,9 @@
-
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/all-shapes/standard.exp.txt b/e2etests/testdata/asciitxtar/all-shapes/standard.exp.txt
index 41b023177a..4a6d5a81a9 100644
--- a/e2etests/testdata/asciitxtar/all-shapes/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/all-shapes/standard.exp.txt
@@ -1,23 +1,29 @@
- +--------+ ***
- /---------------/ /---------/\ |callout | **** **** /---------\
-+----------+ / / | | | +--------+ *** diamond *** / \
-|rectangle | / parallelogram / | queue | | | / **** **** \ hexagon /
-| | /_______________/ \________ \/ |/ ***** \_________/
-+----------+
- | | | | | |
- v v v v | v
- +---------+ +----------+ +----+ /-----------/ v +@---------+
- | | | document | | +----+ / / +O--------+ | |
- | square | | .-`-.| | package | | stored_data | oval | | cloud |
- | | `-.-` +---------+ \ \ | | | |
- | | \___________\ +---------+ | |
- +---------+ | | | | +----------+
- | | | | |
- v v v v |
- +-----+ .------. \------- \ /--\ v
- | \+ |\-____-/| \ \ \__/ +O--------+
- | page | | | \ \ /----\ | |
- | | | | / step / ------ | circle |
- +------+ |cylinder| / / person | |
- | | /_______ / | |
- \-____-/ +---------+
+ /------/
+ / /
+ / /
+ / / * /---\
+ parallelogram +-------+ ** ** / \
++----------+ / / /--/\ +callout+ *diamond* XhexagonX
+|rectangle | / / |queue| | / ** ** \ /
+| | /______/ \_ \/ | / *** \___/
++----------+
+ | | | | | |
+ v | | v | v
+ +-------+ v v /---------/ v +@-----+
+ | | +--------+ +---+ / / +O----+ |cloud |
+ | | |document| |package+ |stored_data |oval | | |
+ |square | | .-`.| +-------+ \ \ | | +------+
+ | | `-.` \_________\ +-----+
+ | | | |
+ | | | | | |
+ +-------+ | | | |
+ | | | | |
+ v v v v |
+ +---+ .------. \-- \ /--\ v
+ |page+ |\-____-/| \ \ \__/ +O------+
+ +----+ | | step/ /----\ | |
+ |cylinder| /__ / / \ | |
+ \-____-/ -------- |circle |
+ person | |
+ | |
+ +-------+
diff --git a/e2etests/testdata/asciitxtar/basic-newlines/extended.exp.txt b/e2etests/testdata/asciitxtar/basic-newlines/extended.exp.txt
index 4dfc3b2352..ce4dc2f546 100644
--- a/e2etests/testdata/asciitxtar/basic-newlines/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/basic-newlines/extended.exp.txt
@@ -1,6 +1,5 @@
-┌────┐
-│ │ ┌────┐
-│ A │ │ A │
-│ a │ │ │
-│ │ └────┘
-└────┘
+ ┌──┐
+┌────┐ │A │
+│ A │ │ │
+│ a │ └──┘
+└────┘
diff --git a/e2etests/testdata/asciitxtar/basic-newlines/sketch.exp.svg b/e2etests/testdata/asciitxtar/basic-newlines/sketch.exp.svg
index d3b0b284f2..9c971a7bdc 100644
--- a/e2etests/testdata/asciitxtar/basic-newlines/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/basic-newlines/sketch.exp.svg
@@ -1,9 +1,9 @@
-AaA
-
+ .d2-4160833631 .fill-N1{fill:#0A0F25;}
+ .d2-4160833631 .fill-N2{fill:#676C7E;}
+ .d2-4160833631 .fill-N3{fill:#9499AB;}
+ .d2-4160833631 .fill-N4{fill:#CFD2DD;}
+ .d2-4160833631 .fill-N5{fill:#DEE1EB;}
+ .d2-4160833631 .fill-N6{fill:#EEF1F8;}
+ .d2-4160833631 .fill-N7{fill:#FFFFFF;}
+ .d2-4160833631 .fill-B1{fill:#0D32B2;}
+ .d2-4160833631 .fill-B2{fill:#0D32B2;}
+ .d2-4160833631 .fill-B3{fill:#E3E9FD;}
+ .d2-4160833631 .fill-B4{fill:#E3E9FD;}
+ .d2-4160833631 .fill-B5{fill:#EDF0FD;}
+ .d2-4160833631 .fill-B6{fill:#F7F8FE;}
+ .d2-4160833631 .fill-AA2{fill:#4A6FF3;}
+ .d2-4160833631 .fill-AA4{fill:#EDF0FD;}
+ .d2-4160833631 .fill-AA5{fill:#F7F8FE;}
+ .d2-4160833631 .fill-AB4{fill:#EDF0FD;}
+ .d2-4160833631 .fill-AB5{fill:#F7F8FE;}
+ .d2-4160833631 .stroke-N1{stroke:#0A0F25;}
+ .d2-4160833631 .stroke-N2{stroke:#676C7E;}
+ .d2-4160833631 .stroke-N3{stroke:#9499AB;}
+ .d2-4160833631 .stroke-N4{stroke:#CFD2DD;}
+ .d2-4160833631 .stroke-N5{stroke:#DEE1EB;}
+ .d2-4160833631 .stroke-N6{stroke:#EEF1F8;}
+ .d2-4160833631 .stroke-N7{stroke:#FFFFFF;}
+ .d2-4160833631 .stroke-B1{stroke:#0D32B2;}
+ .d2-4160833631 .stroke-B2{stroke:#0D32B2;}
+ .d2-4160833631 .stroke-B3{stroke:#E3E9FD;}
+ .d2-4160833631 .stroke-B4{stroke:#E3E9FD;}
+ .d2-4160833631 .stroke-B5{stroke:#EDF0FD;}
+ .d2-4160833631 .stroke-B6{stroke:#F7F8FE;}
+ .d2-4160833631 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-4160833631 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-4160833631 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-4160833631 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-4160833631 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-4160833631 .background-color-N1{background-color:#0A0F25;}
+ .d2-4160833631 .background-color-N2{background-color:#676C7E;}
+ .d2-4160833631 .background-color-N3{background-color:#9499AB;}
+ .d2-4160833631 .background-color-N4{background-color:#CFD2DD;}
+ .d2-4160833631 .background-color-N5{background-color:#DEE1EB;}
+ .d2-4160833631 .background-color-N6{background-color:#EEF1F8;}
+ .d2-4160833631 .background-color-N7{background-color:#FFFFFF;}
+ .d2-4160833631 .background-color-B1{background-color:#0D32B2;}
+ .d2-4160833631 .background-color-B2{background-color:#0D32B2;}
+ .d2-4160833631 .background-color-B3{background-color:#E3E9FD;}
+ .d2-4160833631 .background-color-B4{background-color:#E3E9FD;}
+ .d2-4160833631 .background-color-B5{background-color:#EDF0FD;}
+ .d2-4160833631 .background-color-B6{background-color:#F7F8FE;}
+ .d2-4160833631 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-4160833631 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-4160833631 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-4160833631 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-4160833631 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-4160833631 .color-N1{color:#0A0F25;}
+ .d2-4160833631 .color-N2{color:#676C7E;}
+ .d2-4160833631 .color-N3{color:#9499AB;}
+ .d2-4160833631 .color-N4{color:#CFD2DD;}
+ .d2-4160833631 .color-N5{color:#DEE1EB;}
+ .d2-4160833631 .color-N6{color:#EEF1F8;}
+ .d2-4160833631 .color-N7{color:#FFFFFF;}
+ .d2-4160833631 .color-B1{color:#0D32B2;}
+ .d2-4160833631 .color-B2{color:#0D32B2;}
+ .d2-4160833631 .color-B3{color:#E3E9FD;}
+ .d2-4160833631 .color-B4{color:#E3E9FD;}
+ .d2-4160833631 .color-B5{color:#EDF0FD;}
+ .d2-4160833631 .color-B6{color:#F7F8FE;}
+ .d2-4160833631 .color-AA2{color:#4A6FF3;}
+ .d2-4160833631 .color-AA4{color:#EDF0FD;}
+ .d2-4160833631 .color-AA5{color:#F7F8FE;}
+ .d2-4160833631 .color-AB4{color:#EDF0FD;}
+ .d2-4160833631 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-4160833631);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-4160833631);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-4160833631);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-4160833631);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-4160833631);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-4160833631);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-4160833631);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-4160833631);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>AaA
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/basic-newlines/standard.exp.txt b/e2etests/testdata/asciitxtar/basic-newlines/standard.exp.txt
index 0182c7a642..d65ca0b8ad 100644
--- a/e2etests/testdata/asciitxtar/basic-newlines/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/basic-newlines/standard.exp.txt
@@ -1,6 +1,5 @@
-+----+
-| | +----+
-| A | | A |
-| a | | |
-| | +----+
-+----+
+ +--+
++----+ |A |
+| A | | |
+| a | +--+
++----+
diff --git a/e2etests/testdata/asciitxtar/basic-simple/extended.exp.txt b/e2etests/testdata/asciitxtar/basic-simple/extended.exp.txt
index 797771e829..85bf79106d 100644
--- a/e2etests/testdata/asciitxtar/basic-simple/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/basic-simple/extended.exp.txt
@@ -1,10 +1,10 @@
-┌────┐
-│ x │
-│ │
-└────┘
- │
- ▼
-┌────┐
-│ y │
-│ │
-└────┘
+┌──┐
+│x │
+│ │
+└──┘
+ │
+ ▼
+┌──┐
+│y │
+│ │
+└──┘
diff --git a/e2etests/testdata/asciitxtar/basic-simple/sketch.exp.svg b/e2etests/testdata/asciitxtar/basic-simple/sketch.exp.svg
index 42bd40e467..da7ef3d977 100644
--- a/e2etests/testdata/asciitxtar/basic-simple/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/basic-simple/sketch.exp.svg
@@ -1,9 +1,9 @@
-xy
-
+ .d2-1901374348 .fill-N1{fill:#0A0F25;}
+ .d2-1901374348 .fill-N2{fill:#676C7E;}
+ .d2-1901374348 .fill-N3{fill:#9499AB;}
+ .d2-1901374348 .fill-N4{fill:#CFD2DD;}
+ .d2-1901374348 .fill-N5{fill:#DEE1EB;}
+ .d2-1901374348 .fill-N6{fill:#EEF1F8;}
+ .d2-1901374348 .fill-N7{fill:#FFFFFF;}
+ .d2-1901374348 .fill-B1{fill:#0D32B2;}
+ .d2-1901374348 .fill-B2{fill:#0D32B2;}
+ .d2-1901374348 .fill-B3{fill:#E3E9FD;}
+ .d2-1901374348 .fill-B4{fill:#E3E9FD;}
+ .d2-1901374348 .fill-B5{fill:#EDF0FD;}
+ .d2-1901374348 .fill-B6{fill:#F7F8FE;}
+ .d2-1901374348 .fill-AA2{fill:#4A6FF3;}
+ .d2-1901374348 .fill-AA4{fill:#EDF0FD;}
+ .d2-1901374348 .fill-AA5{fill:#F7F8FE;}
+ .d2-1901374348 .fill-AB4{fill:#EDF0FD;}
+ .d2-1901374348 .fill-AB5{fill:#F7F8FE;}
+ .d2-1901374348 .stroke-N1{stroke:#0A0F25;}
+ .d2-1901374348 .stroke-N2{stroke:#676C7E;}
+ .d2-1901374348 .stroke-N3{stroke:#9499AB;}
+ .d2-1901374348 .stroke-N4{stroke:#CFD2DD;}
+ .d2-1901374348 .stroke-N5{stroke:#DEE1EB;}
+ .d2-1901374348 .stroke-N6{stroke:#EEF1F8;}
+ .d2-1901374348 .stroke-N7{stroke:#FFFFFF;}
+ .d2-1901374348 .stroke-B1{stroke:#0D32B2;}
+ .d2-1901374348 .stroke-B2{stroke:#0D32B2;}
+ .d2-1901374348 .stroke-B3{stroke:#E3E9FD;}
+ .d2-1901374348 .stroke-B4{stroke:#E3E9FD;}
+ .d2-1901374348 .stroke-B5{stroke:#EDF0FD;}
+ .d2-1901374348 .stroke-B6{stroke:#F7F8FE;}
+ .d2-1901374348 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-1901374348 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-1901374348 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-1901374348 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-1901374348 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-1901374348 .background-color-N1{background-color:#0A0F25;}
+ .d2-1901374348 .background-color-N2{background-color:#676C7E;}
+ .d2-1901374348 .background-color-N3{background-color:#9499AB;}
+ .d2-1901374348 .background-color-N4{background-color:#CFD2DD;}
+ .d2-1901374348 .background-color-N5{background-color:#DEE1EB;}
+ .d2-1901374348 .background-color-N6{background-color:#EEF1F8;}
+ .d2-1901374348 .background-color-N7{background-color:#FFFFFF;}
+ .d2-1901374348 .background-color-B1{background-color:#0D32B2;}
+ .d2-1901374348 .background-color-B2{background-color:#0D32B2;}
+ .d2-1901374348 .background-color-B3{background-color:#E3E9FD;}
+ .d2-1901374348 .background-color-B4{background-color:#E3E9FD;}
+ .d2-1901374348 .background-color-B5{background-color:#EDF0FD;}
+ .d2-1901374348 .background-color-B6{background-color:#F7F8FE;}
+ .d2-1901374348 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-1901374348 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-1901374348 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-1901374348 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-1901374348 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-1901374348 .color-N1{color:#0A0F25;}
+ .d2-1901374348 .color-N2{color:#676C7E;}
+ .d2-1901374348 .color-N3{color:#9499AB;}
+ .d2-1901374348 .color-N4{color:#CFD2DD;}
+ .d2-1901374348 .color-N5{color:#DEE1EB;}
+ .d2-1901374348 .color-N6{color:#EEF1F8;}
+ .d2-1901374348 .color-N7{color:#FFFFFF;}
+ .d2-1901374348 .color-B1{color:#0D32B2;}
+ .d2-1901374348 .color-B2{color:#0D32B2;}
+ .d2-1901374348 .color-B3{color:#E3E9FD;}
+ .d2-1901374348 .color-B4{color:#E3E9FD;}
+ .d2-1901374348 .color-B5{color:#EDF0FD;}
+ .d2-1901374348 .color-B6{color:#F7F8FE;}
+ .d2-1901374348 .color-AA2{color:#4A6FF3;}
+ .d2-1901374348 .color-AA4{color:#EDF0FD;}
+ .d2-1901374348 .color-AA5{color:#F7F8FE;}
+ .d2-1901374348 .color-AB4{color:#EDF0FD;}
+ .d2-1901374348 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1901374348);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1901374348);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1901374348);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1901374348);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1901374348);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1901374348);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1901374348);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1901374348);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>xy
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/basic-simple/standard.exp.txt b/e2etests/testdata/asciitxtar/basic-simple/standard.exp.txt
index f6d0bc1fb9..57919b5c6c 100644
--- a/e2etests/testdata/asciitxtar/basic-simple/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/basic-simple/standard.exp.txt
@@ -1,10 +1,10 @@
-+----+
-| x |
-| |
-+----+
- |
- v
-+----+
-| y |
-| |
-+----+
++--+
+|x |
+| |
++--+
+ |
+ v
++--+
+|y |
+| |
++--+
diff --git a/e2etests/testdata/asciitxtar/curved-line/extended.exp.txt b/e2etests/testdata/asciitxtar/curved-line/extended.exp.txt
index fae9d699cd..bbbfa0a7fc 100644
--- a/e2etests/testdata/asciitxtar/curved-line/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/curved-line/extended.exp.txt
@@ -1,83 +1,83 @@
- ┌──────────────────────────────────────────────────────┐
- │ k │
- │ │
- │ ┌───────────────────────┐ │
- │ │ cell tower │ │
- │ │ │ │
- │ │ ╱‾‾‾‾‾‾‾‾‾‾╱ │ │
- │ │ ╱ ╱ │ │
- │ │ │ satellites │ │
- │ │ ╲ ╲ │ │
- │ │ ╲__________╲ │ ┌────────────────┐ │
- │ │ │ │ │ │ │ online portal │ │
- │ │ │ │ │ │ │ ╱‾‾╲ │ │
- │ │ │ │ │ │ │ ╱ ╲ │ │
- │ │ │ │ │ │ │ ╲ ui ╱ │ │
- │ │ │ │ │ │ │ ╲__╱ │ │
- │ │ sendsend send │ │ │ │
- │ │ │ │ │ │ │ │ │
- │ │ │ │ │ │ └────────────────┘ │
- │ │ ▼ ▼ ▼ │ │
- │ │ ┌────────────┐ │ │
- │ │ │transmitter │ │ │
- │ │ │ │ │ │
- │ │ └────────────┘ │ │
- │ │ │ │ │
- │ └───────────│───────────┘ │
- │ │ │
- │ phone logs │
- │ │ │
- │ ┌─────────│─────────┐ │
- │ │ data processor │ │
- │ │ │ │ │
- │ │ ▼ │ │
- │ │ .-‾‾‾‾‾-. │ │
- │ │ │╲-_____-╱│ │ │
- │ │ │ │ │ │
- │ │ │ storage │ │ │
- │ │ │ │ │ │
- ╱‾‾‾╲ │ │ ╲-_____-╱ │ │
- ╲___╱ │ │ │ │
- ╱‾‾‾‾‾‾‾‾‾╲ │ │ │ │
- ╱ ╲ │ └───────────────────┘ │
- ‾‾‾‾‾‾‾‾‾‾‾‾‾─┐│ │
- user ││ │
- │ │└──────────────────────────────────────────────────────┘
- │ │
- │ access
- │ │
- ┌────┘ │ ┌──────────────────────────────────┐
- │ │ │ │
-┌──────────│───────────────│──│────────────────────────────┐ │
-│ make call network │ │
-│ │ │ │ │ │
-│ ▼ ┌───────│──│─────┐ │ │
-│ ┌───────────┐ │ online│portal │ │ │
-│ │cell tower │ │ │ │ │ │ │
-│ │ │ │ ▼ ▼ │ │ │
-│ └───────────┘ │ ┌───────┐ │ │ │
-│ │ │ ui │ │ │ │
-│ │ │ │ │ │ │
-│ │ └───────┘ │ ┌───────────────┐ │ display
-│ │ │ │data processor │ │ │
-│ └────────────────┘ │ │ │ │
-│ └───────────────┘ │ │
-│ │ │ │
-└─────────────────────────────────────────────────────│────┘ │
- │ │
- │ ┌──────┘
- │ │
- ▼ │
- ┌───────────┐
- │api server │
- │ │
- └───────────┘
- │
- persist
- │
- ▼
- ┌─────┐
- │ ╲┐
- │ logs │
- │ │
- └──────┘
+ ┌────────────────────────────────────────────────────────────────────────────────┐
+ │ k │
+ │ │
+ │ ┌───────────────────────────────────────┐ │
+ │ │ cell tower │ │
+ │ │ │ │
+ │ │ ╱‾‾‾‾‾‾‾‾╱ │ │
+ │ │ ╱ ╱ │ │
+ │ │ │satellites │ │
+ │ │ ╲ ╲ │ │
+ │ │ ╲________╲ │ │
+ │ │ │ ┌──────────────┐ │
+ │ │ │ │ │ │ │online portal │ │
+ │ │ ┌───send────┘ │ └───send────┐ │ │ │ │
+ │ │ │ │ │ │ │ ╱‾╲ │ │
+ │ │ │ │ │ │ │ ╱ ╲ │ │
+ │ │ │ send │ │ │ ╳ ui ╳ │ │
+ │ │ │ │ │ │ │ ╲ ╱ │ │
+ │ │ └──────────┐ │ ┌───────────┘ │ │ ╲_╱ │ │
+ │ │ ▼ ▼ ▼ │ │ │ │
+ │ │ ┌────────────┐ │ │ │ │
+ │ │ │transmitter │ │ │ │ │
+ │ │ │ │ │ └──────────────┘ │
+ │ │ └────────────┘ │ │
+ │ │ │ │ │
+ │ └────────────────────│──────────────────┘ │
+ │ │ │
+ │ phone logs │
+ │ │ │
+ │ ┌────────│──────┐ │
+ │ │data processor │ │
+ │ │ ▼ │ │
+ │ │ .-‾‾‾-. │ │
+ │ │ │╲-___-╱│ │ │
+ │ │ │ │ │ │
+ │ │ │storage│ │ │
+ │ │ ╲-___-╱ │ │
+ │ │ │ │
+ ╱‾‾╲ │ │ │ │
+ ╲__╱ │ │ │ │
+ ╱‾‾‾‾‾‾‾‾╲ │ └───────────────┘ │
+ ╱ ╲ │ │
+ ‾‾‾‾‾‾‾‾‾‾‾‾ │ │
+ user └────────────────────────────────────────────────────────────────────────────────┘
+ │ │
+ ┌────────────┘ └───────────┐ ┌───────────────────────────────display───────────────────────────────┐
+ │ │ │ │
+ make call │ │ │
+┌────────│────────────────────────────│─────│────────────────────────────────────────────┐ │
+│ │ │ network │ │
+│ │ access │ │ │
+│ │ │ │ │ │
+│ ▼ │ │ │ │
+│ ┌───────────┐ ┌──│─────│─────┐ │ │
+│ │cell tower │ │online portal │ │ │
+│ │ │ │ │ │ │ │ │
+│ └───────────┘ │ │ │ │ │ │
+│ │ │ │ │ │ │
+│ │ └───┐ │ │ │ │
+│ │ ▼ ▼ │ │ │
+│ │ ┌─────┐ │ │ │
+│ │ │ ui │ │ │ │
+│ │ │ │ │ │ │
+│ │ └─────┘ │ ┌───────────────┐ │ │
+│ │ │ │data processor │ │ │
+│ │ │ │ │ │ │
+│ └──────────────┘ └───────────────┘ │ │
+│ │ │ │
+└──────────────────────────────────────────────────────────────────────────────│─────────┘ │
+ │ │
+ └──────────────┐ ┌───────────────┘
+ ▼ │
+ ┌───────────┐
+ │api server │
+ │ │
+ └───────────┘
+ │
+ persist
+ │
+ ▼
+ ┌───┐
+ │logs┐
+ └────┘
diff --git a/e2etests/testdata/asciitxtar/curved-line/sketch.exp.svg b/e2etests/testdata/asciitxtar/curved-line/sketch.exp.svg
index 817b9ffdad..146ceb8213 100644
--- a/e2etests/testdata/asciitxtar/curved-line/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/curved-line/sketch.exp.svg
@@ -1,23 +1,23 @@
-kusernetworkapi serverlogscell toweronline portaldata processorcell toweronline portaldata processorsatellitestransmitteruistorageui sendsendsendphone logsmake call accessdisplaypersist
-
-
-
-
-
-
-
-
-
+ .d2-2436927691 .fill-N1{fill:#0A0F25;}
+ .d2-2436927691 .fill-N2{fill:#676C7E;}
+ .d2-2436927691 .fill-N3{fill:#9499AB;}
+ .d2-2436927691 .fill-N4{fill:#CFD2DD;}
+ .d2-2436927691 .fill-N5{fill:#DEE1EB;}
+ .d2-2436927691 .fill-N6{fill:#EEF1F8;}
+ .d2-2436927691 .fill-N7{fill:#FFFFFF;}
+ .d2-2436927691 .fill-B1{fill:#0D32B2;}
+ .d2-2436927691 .fill-B2{fill:#0D32B2;}
+ .d2-2436927691 .fill-B3{fill:#E3E9FD;}
+ .d2-2436927691 .fill-B4{fill:#E3E9FD;}
+ .d2-2436927691 .fill-B5{fill:#EDF0FD;}
+ .d2-2436927691 .fill-B6{fill:#F7F8FE;}
+ .d2-2436927691 .fill-AA2{fill:#4A6FF3;}
+ .d2-2436927691 .fill-AA4{fill:#EDF0FD;}
+ .d2-2436927691 .fill-AA5{fill:#F7F8FE;}
+ .d2-2436927691 .fill-AB4{fill:#EDF0FD;}
+ .d2-2436927691 .fill-AB5{fill:#F7F8FE;}
+ .d2-2436927691 .stroke-N1{stroke:#0A0F25;}
+ .d2-2436927691 .stroke-N2{stroke:#676C7E;}
+ .d2-2436927691 .stroke-N3{stroke:#9499AB;}
+ .d2-2436927691 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2436927691 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2436927691 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2436927691 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2436927691 .stroke-B1{stroke:#0D32B2;}
+ .d2-2436927691 .stroke-B2{stroke:#0D32B2;}
+ .d2-2436927691 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2436927691 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2436927691 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2436927691 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2436927691 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2436927691 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2436927691 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2436927691 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2436927691 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2436927691 .background-color-N1{background-color:#0A0F25;}
+ .d2-2436927691 .background-color-N2{background-color:#676C7E;}
+ .d2-2436927691 .background-color-N3{background-color:#9499AB;}
+ .d2-2436927691 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2436927691 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2436927691 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2436927691 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2436927691 .background-color-B1{background-color:#0D32B2;}
+ .d2-2436927691 .background-color-B2{background-color:#0D32B2;}
+ .d2-2436927691 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2436927691 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2436927691 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2436927691 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2436927691 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2436927691 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2436927691 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2436927691 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2436927691 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2436927691 .color-N1{color:#0A0F25;}
+ .d2-2436927691 .color-N2{color:#676C7E;}
+ .d2-2436927691 .color-N3{color:#9499AB;}
+ .d2-2436927691 .color-N4{color:#CFD2DD;}
+ .d2-2436927691 .color-N5{color:#DEE1EB;}
+ .d2-2436927691 .color-N6{color:#EEF1F8;}
+ .d2-2436927691 .color-N7{color:#FFFFFF;}
+ .d2-2436927691 .color-B1{color:#0D32B2;}
+ .d2-2436927691 .color-B2{color:#0D32B2;}
+ .d2-2436927691 .color-B3{color:#E3E9FD;}
+ .d2-2436927691 .color-B4{color:#E3E9FD;}
+ .d2-2436927691 .color-B5{color:#EDF0FD;}
+ .d2-2436927691 .color-B6{color:#F7F8FE;}
+ .d2-2436927691 .color-AA2{color:#4A6FF3;}
+ .d2-2436927691 .color-AA4{color:#EDF0FD;}
+ .d2-2436927691 .color-AA5{color:#F7F8FE;}
+ .d2-2436927691 .color-AB4{color:#EDF0FD;}
+ .d2-2436927691 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2436927691);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2436927691);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2436927691);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2436927691);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2436927691);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2436927691);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2436927691);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2436927691);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>kusernetworkapi serverlogscell toweronline portaldata processorcell toweronline portaldata processorsatellitestransmitteruistorageui sendsendsendphone logsmake call accessdisplaypersist
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/curved-line/standard.exp.txt b/e2etests/testdata/asciitxtar/curved-line/standard.exp.txt
index e3e72e9703..e45c8fba1e 100644
--- a/e2etests/testdata/asciitxtar/curved-line/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/curved-line/standard.exp.txt
@@ -1,83 +1,83 @@
- +------------------------------------------------------+
- | k |
- | |
- | +-----------------------+ |
- | | cell tower | |
- | | | |
- | | /----------/ | |
- | | / / | |
- | | | satellites | |
- | | \ \ | |
- | | \__________\ | +----------------+ |
- | | | | | | | online portal | |
- | | | | | | | /--\ | |
- | | | | | | | / \ | |
- | | | | | | | \ ui / | |
- | | | | | | | \__/ | |
- | | sendsend send | | | |
- | | | | | | | | |
- | | | | | | +----------------+ |
- | | v v v | |
- | | +------------+ | |
- | | |transmitter | | |
- | | | | | |
- | | +------------+ | |
- | | | | |
- | +-----------|-----------+ |
- | | |
- | phone logs |
- | | |
- | +---------|---------+ |
- | | data processor | |
- | | | | |
- | | v | |
- | | .-------. | |
- | | |\-_____-/| | |
- | | | | | |
- | | | storage | | |
- | | | | | |
- /---\ | | \-_____-/ | |
- \___/ | | | |
- /---------\ | | | |
- / \ | +-------------------+ |
- --------------+| |
- user || |
- | |+------------------------------------------------------+
- | |
- | access
- | |
- +----+ | +----------------------------------+
- | | | |
-+----------|---------------|--|----------------------------+ |
-| make call network | |
-| | | | | |
-| v +-------|--|-----+ | |
-| +-----------+ | online|portal | | |
-| |cell tower | | | | | | |
-| | | | v v | | |
-| +-----------+ | +-------+ | | |
-| | | ui | | | |
-| | | | | | |
-| | +-------+ | +---------------+ | display
-| | | |data processor | | |
-| +----------------+ | | | |
-| +---------------+ | |
-| | | |
-+-----------------------------------------------------|----+ |
- | |
- | +------+
- | |
- v |
- +-----------+
- |api server |
- | |
- +-----------+
- |
- persist
- |
- v
- +-----+
- | \+
- | logs |
- | |
- +------+
+ +--------------------------------------------------------------------------------+
+ | k |
+ | |
+ | +---------------------------------------+ |
+ | | cell tower | |
+ | | | |
+ | | /--------/ | |
+ | | / / | |
+ | | |satellites | |
+ | | \ \ | |
+ | | \________\ | |
+ | | | +--------------+ |
+ | | | | | | |online portal | |
+ | | +---send----+ | +---send----+ | | | |
+ | | | | | | | /-\ | |
+ | | | | | | | / \ | |
+ | | | send | | | X ui X | |
+ | | | | | | | \ / | |
+ | | +----------+ | +-----------+ | | \_/ | |
+ | | v v v | | | |
+ | | +------------+ | | | |
+ | | |transmitter | | | | |
+ | | | | | +--------------+ |
+ | | +------------+ | |
+ | | | | |
+ | +--------------------|------------------+ |
+ | | |
+ | phone logs |
+ | | |
+ | +--------|------+ |
+ | |data processor | |
+ | | v | |
+ | | .-----. | |
+ | | |\-___-/| | |
+ | | | | | |
+ | | |storage| | |
+ | | \-___-/ | |
+ | | | |
+ /--\ | | | |
+ \__/ | | | |
+ /--------\ | +---------------+ |
+ / \ | |
+ ------------ | |
+ user +--------------------------------------------------------------------------------+
+ | |
+ +------------+ +-----------+ +-------------------------------display-------------------------------+
+ | | | |
+ make call | | |
++--------|----------------------------|-----|--------------------------------------------+ |
+| | | network | |
+| | access | | |
+| | | | | |
+| v | | | |
+| +-----------+ +--|-----|-----+ | |
+| |cell tower | |online portal | | |
+| | | | | | | | |
+| +-----------+ | | | | | |
+| | | | | | |
+| | +---+ | | | |
+| | v v | | |
+| | +-----+ | | |
+| | | ui | | | |
+| | | | | | |
+| | +-----+ | +---------------+ | |
+| | | |data processor | | |
+| | | | | | |
+| +--------------+ +---------------+ | |
+| | | |
++------------------------------------------------------------------------------|---------+ |
+ | |
+ +--------------+ +---------------+
+ v |
+ +-----------+
+ |api server |
+ | |
+ +-----------+
+ |
+ persist
+ |
+ v
+ +---+
+ |logs+
+ +----+
diff --git a/e2etests/testdata/asciitxtar/long-sequence/extended.exp.txt b/e2etests/testdata/asciitxtar/long-sequence/extended.exp.txt
new file mode 100644
index 0000000000..1e018c7784
--- /dev/null
+++ b/e2etests/testdata/asciitxtar/long-sequence/extended.exp.txt
@@ -0,0 +1,9 @@
+┌──────┐ ┌──────┐
+│alice │ │ bob │
+│ │ │ │
+└──────┘ └──────┘
+ │ │
+ ──────────What does it mean to─be─well─adjusted───────────▶│
+ │ │
+ │◀The─ability─to─play─bridge─or golf as if they were games.─
+ │ │
diff --git a/e2etests/testdata/asciitxtar/long-sequence/sketch.exp.svg b/e2etests/testdata/asciitxtar/long-sequence/sketch.exp.svg
new file mode 100644
index 0000000000..4759b75f56
--- /dev/null
+++ b/e2etests/testdata/asciitxtar/long-sequence/sketch.exp.svg
@@ -0,0 +1,103 @@
+alicebob What does it mean to be well-adjusted?The ability to play bridge or golf as if they were games.
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/long-sequence/standard.exp.txt b/e2etests/testdata/asciitxtar/long-sequence/standard.exp.txt
new file mode 100644
index 0000000000..fa06a11477
--- /dev/null
+++ b/e2etests/testdata/asciitxtar/long-sequence/standard.exp.txt
@@ -0,0 +1,9 @@
++------+ +------+
+|alice | | bob |
+| | | |
++------+ +------+
+ | |
+ ----------What does it mean to-be-well-adjusted----------->|
+ | |
+ |cookHeartbeatErrUIUIModeStoryChangedStoryCookingStartedLoopReadyBaseDBStartingStartBaseDBReadyErrMemExceptionStepCommentsReadyGenStepCommentsJokesReadyRestoreJokesGenJokesStoryWakingUpStoryJokeMsgInterruptedInputPendingResumeCheckingOfferRefsStoryIngredientsPickingStoryRecipePickingStoryMealReadyStoryMemoryWipeStoryStartAgainResourcesReadyRestoreResourcesGenResourcesBaseDBSavingErrDBStepsReadyRecipeReadyGenStepsCheckStoriesGenCharacterDBReadyCharacterReadyRestoreCharacterUICleanOutputRegisterDisposalHealthcheckIngredientsReadyErrHandlerTimeoutDBStartingErrNetworkUISrvListeningRequestingToolRequestingErrIngredientsPromptUIReadyDisposedDisposingUISessDisconnErrSendPayloadUISaveOutputInputBlockedErrLLMUIButtonSendErrProvidingUISessConnOrientingMoveRequestingLLMSendPayloadErrCookingUIButtonInttStepCompletedOrientingMockastool-searxng-cookmemory-cook requirerequirerequire double removerequiredouble removedouble removedouble removerequire adddouble remove removeremoveremoveremoveremoveremoveremoveremoveremovedouble removedouble removerequirerequiredouble removerequirerequiredouble removedouble removerequirerequiredouble removerequirerequirerequiredouble removerequirerequireaddaddaddrequirerequirerequireadddouble removerequireremovedouble removeremoverequirerequirerequirerequireaddrequirerequiredouble removerequirerequireremoveremoverequirerequirerequirerequirerequirerequiredouble removerequireaddrequirerequirerequirerequirerequirerequirerequireremove addadd
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/many-lines/standard.exp.txt b/e2etests/testdata/asciitxtar/many-lines/standard.exp.txt
new file mode 100644
index 0000000000..9f4462332a
--- /dev/null
+++ b/e2etests/testdata/asciitxtar/many-lines/standard.exp.txt
@@ -0,0 +1,519 @@
++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| cook |
+| +----------+ |
+| |Heartbeat | |
+| | | |
+| +----------+ |
+| |
+| +------+ |
+| |ErrUI | |
+| | |-------------------------------------+ |
+| +------+ | |
+| | |
+| +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + |
+| | | | |
+| +-------------+ | | | |
+| |StoryChanged | |+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |
+| | | || | | | |
+| +-------------+ || | | | |
+| || | | | |
+| ||+--------------------------------+| | | |
+| ||| || | | |
+| |||+-----------------------------+ || | | |
+| |||| || | | |
+| |||| | || | | |
+| |||| require | | |
+| +------+ |||| | || | | |
+| | |-+||| | || | | |
+| | | ||| | || | | |
+| | |--+|| | || | | |
+| | | || | || | | |
+| |Ready |---+| | || | | |
+| | | | | || | | |
+| | |<---+ | || | | |
+| | | | || | | |
+| | |-+ | || | | |
+| | | | | || | | |
+| +------+ | | || | | |
+| | | || | | |
+| | | || | require |
+| | | || | | |
+| | |add | | |
+| | | || | | |
+| | | | | | |
+| | require | | |
+| | | | | | |
+| | | | | |
+| | | | |
+| | | |
+| | | +-------+ |
+| | | | | |
+| | | +->| | |
+| | | | | |
+| | +--------------->| | |
+| | | | | | |
+| | |+-------------->| | |
+| | | | | | +-------------------------------------------------+ |
+| | | +------------->| | | | |
+| | | | | | | | |
+| | | | |UIMode | | | |
+| | | +----------->| | | | |
+| | | | | | | | |
+| | | +-------->| | | | |
+| | | | | | | | |
+| | | +----->| | | | |
+| | | | | | | | |
+| | | |+--->| | | | |
+| | | | | | | | |
+| | | | +->| | | | |
+| | | | | | | | |
+| | | | +-------+ | | |
+| | | | | | |
+| +------------------------------+ | | | |
+| | | | | | |
+| +-------+ | | | | | |
+| |ErrMem | | | | | | |
+| | |-------------require---|-------+| | +-----+ | | |
+| +-------+ | || | |Loop | | | |
+| | || | +-->| | | | |
+| | || +-----+-----+ | | |
+| | || | | |
+| | || require | |
+| | || | | |
+| | || |+-----------------------------------------------+| |
+| | || || || |
+| || || || || +-----------+ |
+| || || || || | | |
+| +------------------+ || || || || | | |
+| |StepCommentsReady | || || || || | | +-------------+ |
+| | |<--------------------+ || || +----------+ || || | +--->|CheckStories | |
+| +------------------+ | || || | | || || | +----| | |
+| | || || | | ||+---------------------------------------------+|| | | +-------------+ |
+| | || +|------>| | ||| ||| | | |
+| | || | | | ||| ||| | | |
+| | || | | | ||| ||| add | |
+| |+||-------|------>| | ||| ||| | | |
+| |||| | | | ||| ||| | | |
+| |||| +---|------>| | ||| ||| | | |
+| |||| | | |Exception | ||| ||| | | |
+| |||| | +|------>| | ||| ||| +------+ | | |
+| |||| | || | | ||| ||| | | | require |
+| |||| | || | | ||| ||| | | | | |
+| |||| | || +---->| | ||| |||+->| | | | |
+| |||| | || | | ||| ||| | | | | |
+| |||| | || +-->| | ||| ||| | | | | |
+| |||| | || | | ||| ||+-->| |------+ | |
+| require | || | | ||| || | | | |
+| |||| | || +>| | ||| || | | | |
+| |||| | || | | ||| |+--->| | | |
+| +-------------+ |||| | || +----------+ +-------------------------------------------------------------------+ ||| | | | | |
+| |BaseDBSaving | double remove| || | | require | | | | |
+| | | |||| | || | | ||| +-----| |<-----------------+ |
+| +-------------+ |||| | || | | ||| +------------+ | | |
+| |||| | || | | ||| |BaseDBReady | |Start | |
+| require | || | | |||+-------->| |----------require--------->| | |
+| |||| | || | | |||| +------------+ | | |
+| |||| | || | | |||| | | |
+| |||| | || | | |||| +---->| |---------------------+ |
+| |||| | || | | |||| | | | | |
+| |||| | || | +------------------+ | |||| | | | | |
+| |||| | || | |CheckingOfferRefs | | |||| |+--->| | | |
+| |||| | || | +------------------------------->| |-------------------------------------------+||| | | | | |
+| |||| | || | | +------------------+ | ||| | | | | |
+| |||| | || | | | add | +-->| |<-----+ | |
+| |||| | || | | | ||| | | | | |
+| |||| | || | | | ||| | +->| | | |
+| |||| | || | | | ||| require | | | |
+| |||| | || | | | ||| | +------+ | |
+| |||| | || +----------------+ | | | ||| | | |
+| |||| | || |GenStepComments | | | | ||| | | |
+| +|||---|--||------->| |-------------------------------------+ | ||| | | |
+| +------+ ||| | || +----------------+ | || | ||| | | |
+| |ErrDB | ||| | || | || | ||| | | |
+| | |----------------------+|| | || | || | ||| | | |
+| +------+ || | || | || | ||| +----+ | | |
+| || | || | || | ||| |Msg | | | |
+| || | || | || | ||| | |-------------------+ | | |
+| || | || | || | ||| +----+ | | |
+| || | || | || | ||| | | |
+| || | || | || | double remove | | |
+| || | || | || | ||| | | |
+| || | || | || | +------------------+|| | | |
+| || | || | || | | || | | |
+| || | || | || double remove--------------+ | || | | |
+| || | || | || | | |-+ || | | |
+| || | || | || | |BaseDBStarting |<-+ || | | |
+| ||| | || | || | | | | || | | |
+| +---------------------|||--|+ || | || | | |<+| || | | |
+| | require|| || +-----------+ | || | +---------------+ || || | | |
+| | ||| || || | | | || | || || | | |
+| | ||| || || |JokesReady |<---------------------+ || | || || | | |
+| | ||| || || | | || | |+------------------+| | | |
+| | ||| || || | |<+ || | | | | | |
+| | ||| || || +-----------+ | || | | | | | |
+| | ||| || || | || | | | | | |
+| | ||| || || +--------------------------------+|| | | | | | |
+| +-----------+ | ||| || || ||| | | | | | |
+| | | | ||| || || ||| | | | | | | |
+| |StepsReady |-+ ||| || || require | | | | | | |
+| | | ||| || || ||| | | | | | | |
+| | |<+ ||require| ||| | | | | | | |
+| +-----------+ | ||| || || ||| | | | | | | |
+| | ||| || || ||| | | | | | | |
+| +---------------------|||+ || || ||| | +--------------------+ | | | |
+| |||| || || ||| | | | | |
+| |||| || || +---------------------------------+| | | | | |
+| ||||||| || | | | | | | | |
+| |||||require | | | | | | | |
+| ||||||| || | | | | | | | |
+| ||||||| || | | | | +---------+ | | | |
+| +--------------+ ||||||| || |+-------------------+ | | | | | | | | |
+| |UICleanOutput | ||||||| || || | | | +----->|GenJokes |--------------------------------+ | | | |
+| | |-----------------------+|||||| || || | | | | | | | | | |
+| +--------------+ require| || || | | | +-----------------------++----->| |-----+ | | | | |
+| |||||| || || | | | | || +---------+ | | | | | |
+| |||||| || || | | | +--------------------+ | || | | | | | |
+| |||||| || || | | | | | | || | | | | | |
+| |||||| || || | double remove | | || | | | | | |
+| |||||| || || | | +>| | | || | | | | | |
+| |||||| || || | | |StoryCookingStarted | | || | | | | | |
+| |||||| || || +-------------->| | | || | | | | | |
+| |||||| || | || | | | | || | | | | | |
+| |||||| || | ||+-----------------------------+|+->| | | || | | | | | |
+| |||||| || | ||| || +--------------------+ | || +-------------------------+| | | | |
+| |||||| || | ||| || | || || | | | |
+| requirequire ||| || | || || | | | |
+| |||||| || | ||| || | || || | | | |
+| |||||| || | remove || | || || | | | |
+| |||||| || | ||| || | || || | | | |
+| +------------+ |||||| || | ||| || | || || | | | |
+| |Healthcheck | |||||| || | ||| || | || || | | | |
+| | | |||||| || | remove || | || || | | | |
+| +------------+ |||||| || | ||| || | double remove-----------------------------------------+|| | | | |
+| |||||| || | ||| || | | ||| | | | |
+| |||||| || | |||+---------------------------+|| | | ||| | | | |
+| |||||| || | |||| ||| | | ||| | | | |
+| ||||||||| | |||| ||| +-------------+ | | ||| | | | |
+| ||||||||| | |||| ||| | | | | ||| | | | |
+| ||||||||| | |||| ||| | |----+ | ||| | | | |
+| ||||||||| | |||| ||| |RestoreJokes | | ||| | | | |
+| ||||||||| | |||| ||+---------->| |<----+ | ||| | | add |
+| |||require| |||| || | | | | ||| | | | |
+| ||||||||| | |||| || | | | | ||| | | | |
+| ||||||||| | |||| || | |----+| | ||| | | | |
+| ||||||||| | |||| remove +-------------+ || | ||| | require | |
+| ||||||||| | |||| || || | ||| | | | |
+| ||||||||| | +------------+ |||| || || | ||| | | | |
+| +------------------+ ||||||||| | | | |||| || || | ||| | | | |
+| |ErrHandlerTimeout | ||||||||| | | | |||| || || | ||| | | | |
+| | |------------------------||-|+|||| | | | |||| || || | ||| | | | |
+| +------------------+ |||| |||| | | |-+|||+-------------------------+|| || | ||| | | | |
+| |||| |||| | | | |||| ||| |+-----------------------+ ||| | | | |
+| |||| |||| | | |--+||| ||| | ||| | | | |
+| |||| |||| | | | ||| ||| | ||| | | | |
+| |||| |||| | | |---+|| ||| | ||| require | | |
+| |||| |||| | | | || ||| | ||| | | | |
+| |||| |||| | | | |----+| ||| | ||| | | | |
+| |||| |||| | | | | | ||| | ||| | | | |
+| |||| ||| | | | |<----+ ||| | ||| | | | |
+| |||| ||| | | |Interrupted | ||| | ||| | | | |
+| |||| ||require | |------------------------------+||| | ||| | | | |
+| |||| ||| | | | | remove | ||| | | | |
+| |||| ||| | | | | |||| | ||| | | | |
+| |||| ||| | | | |-----------------------------+|||| | ||| | | | |
+| |||| ||| | | | | ||||| | ||| | | | |
+| |||| ||| | | | |----------------------------+||||| | ||| | | | |
+| +-----------+ |||| ||| | | | | |||||| | | ||| | | | |
+| |ErrNetwork | |||| ||| | | | | |||||| | +--------------+ +-----------------------------------------------------------------------+||| | | | |
+| | |------------------------||-|--|+ | | | |---------------------------+|||||| | |StoryWakingUp | |||| | | | |
+| +-----------+ |||| || | | | | ||||||+-|->| | |||| | | | |
+| |||| || | | | |--------------------------+|||||| | +--------------+ |||| | | | |
+| |||| || | | | | ||||||| | |||| | | | |
+| |||| || | | | |-+ ||||||| | |||| | | | |
+| |||| || | | | | | ||||||| | |||| | | | |
+| |||| || | | +------------+ | ||||||| | |||| | | | |
+| |||| || | | | ||||||| | |||| | | | |
+| |||| || || | | ||||||| | |||| | | | |
+| |||| || || | | double remove |||| | | | |
+| |||| || || | | ||||||| | |||| | | | |
+| |||| || require | ||||||| | require | | | |
+| |||| || || | | ||||||| | |||| | | | |
+| +---------------+ |||| || || | | ||||||| | |||| | | | |
+| |UISrvListening | |||| || || | | ||||||| | +----------+ |||| | | | |
+| | |------------------------+||| || || | | ||||||| | |StoryJoke | |||| | | | |
+| +---------------+ ||| || || | | ||||||+--|->| | |||| | | | |
+| ||| || || | | |||||| | +----------+ |||| | | | |
+| ||| || || | | |||||| | |||| | | | |
+| ||| || || | | |||||| | require | | | |
+| ||| || || | | |||add | |||| | | | |
+| ||| || || | | |||||| | |||| | | | |
+| ||| || || | | | |||||| | |||| | | | |
+| |require|| | | | |||||| | |||| | | | |
+| ||| || || | | | |||||| | |||| | | | |
+| ||| || ||require | |||||| | |||| | | | |
+| ||| || || | | | |||||| | |||| | | | |
+| ||| || || | | | |||||| | require | | | |
+| ||| || || | | | |||||| | |||| | | | |
+| +---------------+ ||| || || | | | remove | |||| | | | |
+| |RequestingTool | ||| || || | | | |||||| | +-------+ |||| | | | |
+| | |---------------------+ ||| || || | | | |||||| | |Resume | |||| | | | |
+| +---------------+ | ||| || || | | | |||||+---|->| | |||| | | | |
+| | ||| || || | | | ||||| | +-------+ |||| | | | |
+| | ||| || || | | | ||||| | |||| | | | +-----------------+ |
+| | ||| || || | | | ||||| | |||| | | | | | |
+| | ||| || || | | | ||||| | |||| | | +-->|RegisterDisposal | |
+| | ||| || || | | | ||||| | |||| | | | | | |
+| | ||| || || | | | ||||| | |||| | | +->| | |
+| | ||| || || | | | ||||| | |||| | | | +-----------------+ |
+| double remove|||| | | ||||| | |||| | | | |
+| | ||| || |||| | | ||||| | |||| | | | |
+| | ||| || |||| | | remove | |||| | | | |
+| | ||| || |||| | | ||||| | |||| || | | |
+| | ||| || |||| | | ||||| | |||| || | |
+| | ||| || |||| | | ||||| | |||| require | |
+| | ||| || |||| | | ||||| | |||| || | |
+| +---------------+ | ||| || |||| | | ||||| | |||| || | |
+| |ErrIngredients | | ||| || |||| | | ||||| | +-------------+ |||| || | |
+| | | | ||| || |||| | | ||||+-require------------------------------------------------------>| | |||| || | |
+| +---------------+ | ||| || |||| | | remove | |InputPending |<---------------------+ |||| || | |
+| | ||| || |||| | | |||| | +--->| | | require || | |
+| | ||| || |||| | | |||| | | | | | |||| || | |
+| | ||| || |||| | | |||| | | +-------------+ | |||| || | |
+| | ||| || |||| | | |||| | | | |||| || | |
+| | ||| || |||| | | |||| | | | |||| || | +-----------+ |
+| | ||| || |||| | | |||| | | | |||| || | +>| | |
+| | ||| || |||| | | |||| | | | |||| || | |DBStarting | |
+| | ||| || |||| | +-----------------------+|||| | | | |||| || +----------------| | |
+| | ||| || |||| | ||||| | | | |||| || +>| | |
+| | ||| || |||| | ||||| | | | |||| || | +-----------+ |
+| | ||| || |||| | ||||| | | | |||| || | |
+| | ||| || |||| | remove| | | | |||| || | |
+| | ||| || |||| | ||||| | | | |||| || | |
+| | ||| || |||| | ||||| | | | |||| || | | |
+| | ||| || |||| | ||||| | | | |||| ||| | | |
+| +--------+ | ||| || |||| | ||||| | | | |||| ||| | | |
+| |UIReady | | ||| || |||| | ||||| | +------------------------+ | | |||| ||| | | |
+| | |-------------------------+|| || |||| | ||||| | |StoryIngredientsPicking | | | |||| ||| | | |
+| +--------+ | || || |||| | ||||+-----|->| | | | |||| ||| | | |
+| | || | |||| | |||| | +------------------------+ | | |||| ||| | | |
+| | || | |||| | |||| | | | |||| ||| | | |
+| | || | |||| | +---------------+ +----------------------+|||| | | | |||| +--------+ ||| | | |
+| | || | |||| | | | | ||||| | | | |||| | | ||| ||| |
+| | || | |||| | |ResourcesReady |<-+ ||||| | | | |||+-->| | ||| ||| |
+| | || | +||||-|-->| | ||||| | | | ||| | | ||| ||| |
+| | || | |||| | | |<-+ ||||| | | | ||| | | ||| ||| |
+| | || | |||| | +---------------+ | ||||| | | | |+|--->| | ||| ||| |
+| | || | |||| | | ||||| | | | | | | |--------------------------+|| ||| |
+| | || | |||| | | ||||| | | |+------|--->| | || ||| |
+| | || | |||| | | ||||| | remove || | | |DBReady | || ||| |
+| | || | |||| | | ||||| | | || | | | | || ||| |
+| | || | |||| | | ||||| | | ||+---|-|--->| | || ||| |
+| | || | |||| | | ||||| | | ||| | | | | || ||| |
+| | || | |||| | | ||||| | | |||+-------->| |<-----------------------double remove----------------------|+ |
+| | || | |||| | | ||||| | | |||| | | | | || || |
+| | | | |||| | | ||||| | | |||| +|-|--->| | || || |
+| +--------------+ | | | |||| | | ||||| | | |||| || | | | || || |
+| |UISessDisconn | | | | |||| | | ||||| | +-------------------+ | |||| || | +--------+ || || |
+| | |---------------------------+ | |||| | | ||||| | |StoryRecipePicking | | |||| || | || || |
+| +--------------+ | | |||| | | ||||+------|->| | | |||| || | || || |
+| | | |||| | | |||| | +-------------------+ | |||| || | || || |
+| | | |||| | | |||| | | |||| || | || || |
+| | | |||| | | remove | | |||| || | || || |
+| | | |||| | | |||| | | |||| || | || || |
+| | | |||| | | |||| | | |||| || | +---------------+ || || |
+| | | |||| | | |||| | | |||| || | | | || || |
+| +---------------+ | | |||| | | |||| | | |||| || | | | || || |
+| |ErrSendPayload | | | |||| | | |||| | +---------------+ | |||| || | +>| | || || |
+| | |------------------------------|--|+|| | | |||| | |StoryMealReady | | |||| || | | | || || |
+| +---------------+ | | | || | | |||+-------|----->| |------------------------------+ |||| || | | | || || |
+| | | | || | | ||| | +---------------+ require|| +-->| | || || |
+| | | | || | | ||| | |||| || | | || || |
+| | | | || | | ||| | |||| |+---->| | || || |
+| | | | || | | ||| | |||| | | | || || |
+| | | | || | | ||| | |||| | |CharacterReady | || || |
+| | | | || | | ||| | ||||+------>| | || || |
+| | | | || | | ||| | |||||| | | || || |
+| | | | || | | ||| | ||||||+---->| | || || |
+| | | | || | +---------------------+||| | ||||||| | | || || |
+| | | | || | |||| | |||||||+--->| | || || |
+| | | | || | |||| | |||||||| | | || || |
+| | | | || | |||| | ||||||||+-->| | || || |
+| | | | || | double remove | ||||||||| | | || || |
+| | | | || | |||| | ||||||||| +---------------+ || || |
+| | | | || | |||| | ||||||||| || || |
+| +-------------+ | | | || | |||| | ||||||||| || || |
+| |UISaveOutput | | | | || | |||| | +----------------+ ||||||||| || || |
+| | |------------------------------+ | || | |||| | |StoryMemoryWipe | ||||||||| || || |
+| +-------------+ | | || | |||+--------|->| | ||||||||| || || |
+| | | || | ||| | +----------------+ double remove|| || || |
+| | | || | ||| | ||||||||| || || |
+| | | || | ||| | ||||||||| || remove |
+| | | || | ||| | ||||||||| || || |
+| | | || | +------------+ ||| | ||||||||| || || |
+| | | || | |RecipeReady | ||| | ||||||||| || || |
+| | +---|-||-|--->| |-------------------------+||| | ||||||||| || || |
+| | | || | +------------+ |||| | ||||||||| || remove |
+| | | || | |||| | ||||||||| || || |
+| | | || | |||| | +------------------------------------------------------------------+||||||| || || |
+| | | || | |||| | | | ||||||| || || |
+| +-------+ | | || | |||| | | require||| || || |
+| |ErrLLM | | | || | |||| | +----------------+ | | ||||||| || || |
+| | |---------------------------------|-|+ | |||| | |StoryStartAgain | | | ||||||| || || |
+| +-------+ | | | | |||+---------|->| | | | ||||||| || || |
+| | | | | ||| | +----------------+ | | ||||||| || || |
+| | | | | ||| | | |require| || || |
+| | | | | ||| | | require|| || || |
+| | | | | ||| | | | ||||||| || || |
+| | | | | +---------+ ||| | | | ||||||| || || |
+| | | | | |GenSteps | ||| | | | ||||||| || || |
+| | +------|-|--|---->| |----------------------------------------+ | | ||||||| || || |
+| | | | | +---------+ ||| | | ||||||| || || |
+| | | | | ||| | | ||||||| || || |
+| | | | | ||| | | require || || |
+| | | | | ||| | | ||||||| || || |
+| +-------------+ | | | | ||| | | ||||||| || || |
+| |UIButtonSend | | | | | ||| +-----------------+ | | ||||||| || || |
+| | |---------------------------------+ | | ||| | | | | ||||||| || || |
+| +-------------+ | | | ||| | | | | ||||||| || || |
+| | | | ||| | |---+ | ||||||| || || |
+| | | | ||| |RestoreResources | | ||||||| || || |
+| | | | ||+----------------->| |----+ | ||||||| || || |
+| | | | || | | | | ||||||| || || |
+| | | | || | |<--+| | ||||||| || || |
+| | | | || | | || | |require || || |
+| | | | || +-----------------+ || | ||||||| || || |
+| | | | || || | ||||||| || || |
+| | | | double remove || | ||||||| || || |
+| | | | || || | ||||||| || || |
+| +-------------+ | | | || || |double remove || || |
+| |ErrProviding | | | | || || | ||||||| || || |
+| | |-----------------------------------|--+ || |+-------------------------------------------------------------------|+|||| || || |
+| +-------------+ | | || | | || |||| || || |
+| | | || double remove | || |||| || || |
+| | | || | | || |||| || || |
+| add | || | | || |||| || || |
+| | | || | | || |||| || || |
+| | | || | | double remove || || |
+| | | || | | || |||| || || |
+| | || | | || |||| || || |
+| | require | | || |||| || || |
+| | || | | || |||| || || |
+| +-----------+ | || +------------------------+ | || |||| || || |
+| |UISessConn | | || | | || |||| || || |
+| | |-----------------------------------+ || | | || |||| || || |
+| +-----------+ || | | || |||| +-------+ || || |
+| || | +--|--|------>| | || || |
+| || | || |||| |Prompt |---------------------------+| || |
+| || | || ||||+--->| | | || |
+| || +------------------------|------------------------------------------+| ||||| | | | || |
+| || | | | ||||| +-------+ | || |
+| || | | | ||||| | || |
+| || +-------------+ | | | ||||| | || |
+| || | | | | +-------------+ | ||||| | || |
+| || | |-+ | | | | ||||| | || |
+| || | | +--->|GenResources |------------------------+ ||||| | || |
+| || |GenCharacter | | | ||||| | || |
+| || | |<+ +--->| |---+ ||||| | || |
+| || | | +------double remove------+ +-------------+ | ||||| | || |
+| || | |<+ || | ||||| | || |
+| || | | | || | ||||| | || |
+| +--------------+ || +-------------+ | || | ||||| | || |
+| |OrientingMove | || | || | ||||| | || |
+| | | || | || | ||||| | || |
+| +--------------+ || | || | ||||| | || |
+| || | || | ||||| | || |
+| || | || | ||||| +---------+ | || |
+| || | || | ||||| | | | || |
+| || | || +-----------------------+||| | | | || |
+| || | || | ||| | |----------------------|-----------------------------+| |
+| || | || | ||| |Disposed | | | |
+| || | || | ||| | |----------------------+ | |
+| || | || | ||| | | | |
+| || | || | ||| | |<+ | |
+| || | || | ||| | | | | |
+| || | || | ||| +---------+ | | |
+| || | || | ||| | | |
+| || | || +-----------------+ | ||| | | |
+| || | || | | | ||| | | |
+| || | || |RestoreCharacter |------------------------+ ||| | +----------+ | |
+| || | |+>| | ||| | |Disposing | | |
+| || | | | |<+ ||| +-----double remove---->| |----------------+ |
+| || | | +-----------------+ | ||| +----------+ |
+| || | | | ||| |
+| || | | | remove |
+| |+-----------------------------------------|------------------------+ | ||| |
+| | | | ||| |
+| | | +------------------------+|| |
+| | | || |
+| | +-------------------------------------------------------------------------+| |
+| | | |
+| | +-----------------+ | |
+| | |IngredientsReady | | |
+| +-------------->| | | |
+| +-----------------+ | |
+| | |
+| +-------------+ | |
+| |InputBlocked | | |
+| | |----------------------------+ |
+| +-------------+ |
+| |
+| | |
+| | +-----------+ |
+| +---------------|->| | |
+| | |Requesting | |
+| |+>| | |
+| || | | |
+| || +-----------+ |
+| || |
+| add |
+| || |
+| +--------------+ || |
+| |RequestingLLM | || |
+| | |-------------------------------------|+ |
+| +--------------+ | |
+| | |
+| +------------+ | |
+| |SendPayload | | |
+| | | | |
+| +------------+ | |
+| | |
+| +-----------+ | |
+| |ErrCooking | | |
+| | | | |
+| +-----------+ | |
+| | |
+| +-------------+ | |
+| |UIButtonIntt | | |
+| | |-------------------------------------+ |
+| +-------------+ |
+| |
+| +--------------+ |
+| |StepCompleted | |
+| | | |
+| +--------------+ |
+| |
+| +----------+ |
+| |Orienting | |
+| | | |
+| +----------+ |
+| |
+| +-----+ |
+| |Mock | |
+| | | |
+| +-----+ |
+| |
+| +--------add--------+ |
+| | | |
+| +---+ | | +------------------+ |
+| |as |-+ +----------------->|tool-searxng-cook | |
+| | |<+ +------------------| | |
+| +---+ | | +------------------+ |
+| | | |
+| +--------add--------+ |
+| |
+| +------------+ |
+| |memory-cook | |
+| | | |
+| +------------+ |
+| |
++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
diff --git a/e2etests/testdata/asciitxtar/network-horizontal/extended.exp.txt b/e2etests/testdata/asciitxtar/network-horizontal/extended.exp.txt
index 1ee949a98d..35c52e36ce 100644
--- a/e2etests/testdata/asciitxtar/network-horizontal/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/network-horizontal/extended.exp.txt
@@ -1,28 +1,51 @@
- ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ network │
- │ │
- │ ┌─────────────────────────────────────────────────────┐ │
- │ │ cell tower │ ┌───────────────────┐ │
- │ │ ┌───────────┐ ┌────────────┐ │ │ data processor │ │
- ┌───────make call──────▶│ │ │ │ │ │ │ │ │
- │ │ │ │ │───────send──────▶│ │ │ │ │────────────────┐
- │ │ │ │satellites │ │transmitter │ │ │ ┌────────┐ │ │ │
- │ │ │ │ │───────send──────▶│ │───────────phone logs───────────▶│storage │ │ │ │
- │ │ │ │ │───────send──────▶│ │ │ │ │ │ │ │ │
-┌─────────────┐ │ │ │ │ │ │ │ │ │ └────────┘ │ │ │
-│ user │───┘ │ │ └───────────┘ └────────────┘ │ │ │ │ │
-│ │───┐ │ │ │ └───────────────────┘ │ │
-└─────────────┘ │ │ └─────────────────────────────────────────────────────┘ │ │
- │ │ │ │
- │ │ ┌────────────────┐ │ │ ┌───────────┐
- │ │ │ online portal │ │ └──▶│api server │ ┌─────┐
- │ │ │ │ │ ┌───│ │──────persist────▶│logs │
- └───────────access────────────▶┌─────┐ │ │ │ └───────────┘ │ │
- │ │ │ ui │ │ │ │ └─────┘
- ┌───────────────▶│ │ │ │ │
- │ │ │ └─────┘ │ │ │
- │ │ │ │ │ │
- │ │ └────────────────┘ │ │
- │ │ │ │
- │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │
- └───────────────────────────────────────────────────────────display──────────────────────────────────────────────────────────┘
+ ┌──────────────────────────────────────────────────────────────────────────────────────────┐
+ │ network │
+ │ │
+ │ ┌─────────────────────────────────────────┐ │
+ │ │ cell tower │ │
+ │ │ │ │
+ │ │ ┌───────┐ │ │
+ │ │ │ │ │ │
+ │ │ send │ │ │
+ │ │ │ │ │ │
+ │ │ ┌───────────┐ │ │ ┌────────────┐ │ ┌───────────────┐ │
+ ┌──────make call──────▶│ │ │ │ │ │ │ │ │data processor │ │
+ │ │ │ │ │ │ │ │ │ │ │ │ │
+ │ │ │ │ │─┘ └▶│ │ │ │ ┌────────┐ │ │
+ │ │ │ │satellites │ │transmitter │ │ │ │storage │ │────────────┐
+ │ │ │ │ │───send───▶│ │──────────phone logs──────────▶│ │ │ │ │
+ │ │ │ │ │ │ │ │ │ └────────┘ │ │ │
+ │ │ │ │ │─┐ ┌▶│ │ │ │ │ │ │
+ │ │ │ │ │ │ │ │ │ │ │ │ │ │
+ │ │ │ └───────────┘ │ │ └────────────┘ │ └───────────────┘ │ │
+ │ │ │ │ │ │ │ │
+ │ │ │ send │ │ │ │
+ │ │ │ │ │ │ │ │
+ │ │ │ └───────┘ │ │ │
+ │ │ │ │ │ │
+┌─────┐ │ │ │ │ │ │
+│ │─┘ │ │ │ │ │
+│user │ │ └─────────────────────────────────────────┘ │ │
+│ │─┐ │ │ │
+│ │ │ │ │ │
+└─────┘ │ │ │ │
+ │ │ │ │
+ │ │ │ │ ┌───────────┐ ┌─────┐
+ │ │ │ └▶│ │ │logs │
+ │ │ │ │api server │───persist─▶│ │
+ │ │ ┌──────────────┐ │ ┌─│ │ └─────┘
+ │ │ │online portal │ │ │ │ │
+ │ │ │ │ │ │ └───────────┘
+ │ │ │ │ │ │
+ │ │ │ ┌───┐ │ │ │
+ └─────────────access──────────────▶│ │ │ │ │
+ │ │ │ui │ │ │ │
+ ┌────────────────────▶│ │ │ │ │
+ │ │ │ │ │ │ │ │
+ │ │ │ └───┘ │ │ │
+ │ │ │ │ │ │
+ │ │ └──────────────┘ │ │
+ │ │ │ │
+ │ └──────────────────────────────────────────────────────────────────────────────────────────┘ │
+ │ │
+ └──────────────────────────────────────────────────display──────────────────────────────────────────────────┘
diff --git a/e2etests/testdata/asciitxtar/network-horizontal/sketch.exp.svg b/e2etests/testdata/asciitxtar/network-horizontal/sketch.exp.svg
index 66ac991238..ea76da9432 100644
--- a/e2etests/testdata/asciitxtar/network-horizontal/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/network-horizontal/sketch.exp.svg
@@ -1,23 +1,23 @@
-networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist
-
-
-
-
-
-
-
-
-
+ .d2-2888206063 .fill-N1{fill:#0A0F25;}
+ .d2-2888206063 .fill-N2{fill:#676C7E;}
+ .d2-2888206063 .fill-N3{fill:#9499AB;}
+ .d2-2888206063 .fill-N4{fill:#CFD2DD;}
+ .d2-2888206063 .fill-N5{fill:#DEE1EB;}
+ .d2-2888206063 .fill-N6{fill:#EEF1F8;}
+ .d2-2888206063 .fill-N7{fill:#FFFFFF;}
+ .d2-2888206063 .fill-B1{fill:#0D32B2;}
+ .d2-2888206063 .fill-B2{fill:#0D32B2;}
+ .d2-2888206063 .fill-B3{fill:#E3E9FD;}
+ .d2-2888206063 .fill-B4{fill:#E3E9FD;}
+ .d2-2888206063 .fill-B5{fill:#EDF0FD;}
+ .d2-2888206063 .fill-B6{fill:#F7F8FE;}
+ .d2-2888206063 .fill-AA2{fill:#4A6FF3;}
+ .d2-2888206063 .fill-AA4{fill:#EDF0FD;}
+ .d2-2888206063 .fill-AA5{fill:#F7F8FE;}
+ .d2-2888206063 .fill-AB4{fill:#EDF0FD;}
+ .d2-2888206063 .fill-AB5{fill:#F7F8FE;}
+ .d2-2888206063 .stroke-N1{stroke:#0A0F25;}
+ .d2-2888206063 .stroke-N2{stroke:#676C7E;}
+ .d2-2888206063 .stroke-N3{stroke:#9499AB;}
+ .d2-2888206063 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2888206063 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2888206063 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2888206063 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2888206063 .stroke-B1{stroke:#0D32B2;}
+ .d2-2888206063 .stroke-B2{stroke:#0D32B2;}
+ .d2-2888206063 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2888206063 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2888206063 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2888206063 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2888206063 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2888206063 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2888206063 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2888206063 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2888206063 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2888206063 .background-color-N1{background-color:#0A0F25;}
+ .d2-2888206063 .background-color-N2{background-color:#676C7E;}
+ .d2-2888206063 .background-color-N3{background-color:#9499AB;}
+ .d2-2888206063 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2888206063 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2888206063 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2888206063 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2888206063 .background-color-B1{background-color:#0D32B2;}
+ .d2-2888206063 .background-color-B2{background-color:#0D32B2;}
+ .d2-2888206063 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2888206063 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2888206063 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2888206063 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2888206063 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2888206063 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2888206063 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2888206063 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2888206063 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2888206063 .color-N1{color:#0A0F25;}
+ .d2-2888206063 .color-N2{color:#676C7E;}
+ .d2-2888206063 .color-N3{color:#9499AB;}
+ .d2-2888206063 .color-N4{color:#CFD2DD;}
+ .d2-2888206063 .color-N5{color:#DEE1EB;}
+ .d2-2888206063 .color-N6{color:#EEF1F8;}
+ .d2-2888206063 .color-N7{color:#FFFFFF;}
+ .d2-2888206063 .color-B1{color:#0D32B2;}
+ .d2-2888206063 .color-B2{color:#0D32B2;}
+ .d2-2888206063 .color-B3{color:#E3E9FD;}
+ .d2-2888206063 .color-B4{color:#E3E9FD;}
+ .d2-2888206063 .color-B5{color:#EDF0FD;}
+ .d2-2888206063 .color-B6{color:#F7F8FE;}
+ .d2-2888206063 .color-AA2{color:#4A6FF3;}
+ .d2-2888206063 .color-AA4{color:#EDF0FD;}
+ .d2-2888206063 .color-AA5{color:#F7F8FE;}
+ .d2-2888206063 .color-AB4{color:#EDF0FD;}
+ .d2-2888206063 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2888206063);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2888206063);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2888206063);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2888206063);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2888206063);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2888206063);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2888206063);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2888206063);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/network-horizontal/standard.exp.txt b/e2etests/testdata/asciitxtar/network-horizontal/standard.exp.txt
index b4a9b8cd2e..b6134851c8 100644
--- a/e2etests/testdata/asciitxtar/network-horizontal/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/network-horizontal/standard.exp.txt
@@ -1,28 +1,51 @@
- +----------------------------------------------------------------------------------------------------------+
- | network |
- | |
- | +-----------------------------------------------------+ |
- | | cell tower | +-------------------+ |
- | | +-----------+ +------------+ | | data processor | |
- +-------make call------>| | | | | | | | |
- | | | | |-------send------>| | | | |----------------+
- | | | |satellites | |transmitter | | | +--------+ | | |
- | | | | |-------send------>| |-----------phone logs----------->|storage | | | |
- | | | | |-------send------>| | | | | | | | |
-+-------------+ | | | | | | | | | +--------+ | | |
-| user |---+ | | +-----------+ +------------+ | | | | |
-| |---+ | | | +-------------------+ | |
-+-------------+ | | +-----------------------------------------------------+ | |
- | | | |
- | | +----------------+ | | +-----------+
- | | | online portal | | +-->|api server | +-----+
- | | | | | +---| |------persist---->|logs |
- +-----------access------------>+-----+ | | | +-----------+ | |
- | | | ui | | | | +-----+
- +--------------->| | | | |
- | | | +-----+ | | |
- | | | | | |
- | | +----------------+ | |
- | | | |
- | +----------------------------------------------------------------------------------------------------------+ |
- +-----------------------------------------------------------display----------------------------------------------------------+
+ +------------------------------------------------------------------------------------------+
+ | network |
+ | |
+ | +-----------------------------------------+ |
+ | | cell tower | |
+ | | | |
+ | | +-------+ | |
+ | | | | | |
+ | | send | | |
+ | | | | | |
+ | | +-----------+ | | +------------+ | +---------------+ |
+ +------make call------>| | | | | | | | |data processor | |
+ | | | | | | | | | | | | |
+ | | | | |-+ +>| | | | +--------+ | |
+ | | | |satellites | |transmitter | | | |storage | |------------+
+ | | | | |---send--->| |----------phone logs---------->| | | | |
+ | | | | | | | | | +--------+ | | |
+ | | | | |-+ +>| | | | | | |
+ | | | | | | | | | | | | | |
+ | | | +-----------+ | | +------------+ | +---------------+ | |
+ | | | | | | | |
+ | | | send | | | |
+ | | | | | | | |
+ | | | +-------+ | | |
+ | | | | | |
++-----+ | | | | | |
+| |-+ | | | | |
+|user | | +-----------------------------------------+ | |
+| |-+ | | |
+| | | | | |
++-----+ | | | |
+ | | | |
+ | | | | +-----------+ +-----+
+ | | | +>| | |logs |
+ | | | |api server |---persist->| |
+ | | +--------------+ | +-| | +-----+
+ | | |online portal | | | | |
+ | | | | | | +-----------+
+ | | | | | |
+ | | | +---+ | | |
+ +-------------access-------------->| | | | |
+ | | |ui | | | |
+ +-------------------->| | | | |
+ | | | | | | | |
+ | | | +---+ | | |
+ | | | | | |
+ | | +--------------+ | |
+ | | | |
+ | +------------------------------------------------------------------------------------------+ |
+ | |
+ +--------------------------------------------------display--------------------------------------------------+
diff --git a/e2etests/testdata/asciitxtar/network-vertical/extended.exp.txt b/e2etests/testdata/asciitxtar/network-vertical/extended.exp.txt
index 7f6356b9d3..e9cadee893 100644
--- a/e2etests/testdata/asciitxtar/network-vertical/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/network-vertical/extended.exp.txt
@@ -1,63 +1,63 @@
- ┌───────────┐
- │ user │
- │ │
- └───────────┘
- │ │
- ┌──────────┘ └──────────┐
- │ │
- make call │
- │ │ ┌─────────────────┐
- │ access│ │
-┌──────────│──────────────────────────│──│───────────┐ │
-│ │ network │ │ │ │
-│ ▼ │ │ │ │
-│ ┌─────────────────────┐ ┌──────│──│──────┐ │ │
-│ │ cell tower │ │ online portal │ │ │
-│ │ │ │ │ │ │ │ │
-│ │ │ │ ▼ ▼ │ │ │
-│ │ ┌───────────┐ │ │ ┌───────┐ │ │ │
-│ │ │satellites │ │ │ │ ui │ │ │ │
-│ │ │ │ │ │ │ │ │ │ │
-│ │ └───────────┘ │ │ └───────┘ │ │ │
-│ │ │ │ │ │ │ │ │ │
-│ │ │ │ │ │ └────────────────┘ │ │
-│ │ │ │ │ │ │ │
-│ │ sendsend send │ │ │
-│ │ │ │ │ │ │ │
-│ │ ▼ ▼ ▼ │ │ │
-│ │ ┌────────────┐ │ │ │
-│ │ │transmitter │ │ │ │
-│ │ │ │ │ │ │
-│ │ └────────────┘ │ │ │
-│ │ │ │ │ │
-│ └───────────│─────────┘ │ display
-│ │ │ │
-│ phone logs │ │
-│ │ │ │
-│ ┌─────────│─────────┐ │ │
-│ │ data processor │ │ │
-│ │ ▼ │ │ │
-│ │ ┌────────┐ │ │ │
-│ │ │storage │ │ │ │
-│ │ │ │ │ │ │
-│ │ └────────┘ │ │ │
-│ │ │ │ │
-│ └───────────────────┘ │ │
-│ │ │ │
-└───────────│────────────────────────────────────────┘ │
- │ │
- └────────────────────┐ ┌─────────────────────┘
- │ │
- ▼ │
- ┌───────────┐
- │api server │
- │ │
- └───────────┘
- │
- persist
- │
- ▼
- ┌─────┐
- │logs │
- │ │
- └─────┘
+ ┌─────┐
+ │user │
+ │ │
+ └─────┘
+ │ │
+ ┌───────make call───────┘ └──────────access───────────┐
+ │ │
+ │ │ ┌─────────────────────────────────┐
+ │ │ │ │
+┌────────────────│─────────────────────────────────────────────────────│─│─────────┐ │
+│ │ network │ │ │ │
+│ ▼ │ │ │ │
+│ ┌───────────────────────────────────────┐ │ │ │ │
+│ │ cell tower │ │ │ │ │
+│ │ │ │ │ │ │
+│ │ ┌───────────┐ │ │ │ │ │
+│ │ │satellites │ │ │ │ │ │
+│ │ │ │ │ ┌──────│─│─────┐ │ │
+│ │ └───────────┘ │ │online│portal │ │ │
+│ │ │ │ │ │ │ │ │ │ │ │
+│ │ ┌───send────┘ │ └───send────┐ │ │ │ │ │ │ │
+│ │ │ │ │ │ │ │ │ │ │ │
+│ │ │ send │ │ │ ▼ ▼ │ │ │
+│ │ │ │ │ │ │ ┌─────┐ │ │ │
+│ │ └───────────┐ │ ┌───────────┘ │ │ │ ui │ │ │ │
+│ │ ▼ ▼ ▼ │ │ │ │ │ │ │
+│ │ ┌────────────┐ │ │ └─────┘ │ │ │
+│ │ │transmitter │ │ │ │ │ │
+│ │ │ │ │ │ │ │ │
+│ │ └────────────┘ │ └──────────────┘ │ │
+│ │ │ │ │ │
+│ └───────────────────│───────────────────┘ │ │
+│ │ │ │
+│ │ │ display
+│ │ │ │
+│ phone logs │ │
+│ │ │ │
+│ ┌───────│───────┐ │ │
+│ │data processor │ │ │
+│ │ ▼ │ │ │
+│ │ ┌────────┐ │ │ │
+│ │ │storage │ │ │ │
+│ │ │ │ │ │ │
+│ │ └────────┘ │ │ │
+│ │ │ │ │
+│ └───────────────┘ │ │
+│ │ │ │
+└──────────────────│───────────────────────────────────────────────────────────────┘ │
+ │ │
+ └─────────────────────────────────────────┐ ┌─────────────────────────────────────────┘
+ ▼ │
+ ┌───────────┐
+ │api server │
+ │ │
+ └───────────┘
+ │
+ persist
+ │
+ ▼
+ ┌─────┐
+ │logs │
+ │ │
+ └─────┘
diff --git a/e2etests/testdata/asciitxtar/network-vertical/sketch.exp.svg b/e2etests/testdata/asciitxtar/network-vertical/sketch.exp.svg
index 4e75d620d6..f21f3486fb 100644
--- a/e2etests/testdata/asciitxtar/network-vertical/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/network-vertical/sketch.exp.svg
@@ -1,23 +1,23 @@
-networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist
-
-
-
-
-
-
-
-
-
+ .d2-1051873423 .fill-N1{fill:#0A0F25;}
+ .d2-1051873423 .fill-N2{fill:#676C7E;}
+ .d2-1051873423 .fill-N3{fill:#9499AB;}
+ .d2-1051873423 .fill-N4{fill:#CFD2DD;}
+ .d2-1051873423 .fill-N5{fill:#DEE1EB;}
+ .d2-1051873423 .fill-N6{fill:#EEF1F8;}
+ .d2-1051873423 .fill-N7{fill:#FFFFFF;}
+ .d2-1051873423 .fill-B1{fill:#0D32B2;}
+ .d2-1051873423 .fill-B2{fill:#0D32B2;}
+ .d2-1051873423 .fill-B3{fill:#E3E9FD;}
+ .d2-1051873423 .fill-B4{fill:#E3E9FD;}
+ .d2-1051873423 .fill-B5{fill:#EDF0FD;}
+ .d2-1051873423 .fill-B6{fill:#F7F8FE;}
+ .d2-1051873423 .fill-AA2{fill:#4A6FF3;}
+ .d2-1051873423 .fill-AA4{fill:#EDF0FD;}
+ .d2-1051873423 .fill-AA5{fill:#F7F8FE;}
+ .d2-1051873423 .fill-AB4{fill:#EDF0FD;}
+ .d2-1051873423 .fill-AB5{fill:#F7F8FE;}
+ .d2-1051873423 .stroke-N1{stroke:#0A0F25;}
+ .d2-1051873423 .stroke-N2{stroke:#676C7E;}
+ .d2-1051873423 .stroke-N3{stroke:#9499AB;}
+ .d2-1051873423 .stroke-N4{stroke:#CFD2DD;}
+ .d2-1051873423 .stroke-N5{stroke:#DEE1EB;}
+ .d2-1051873423 .stroke-N6{stroke:#EEF1F8;}
+ .d2-1051873423 .stroke-N7{stroke:#FFFFFF;}
+ .d2-1051873423 .stroke-B1{stroke:#0D32B2;}
+ .d2-1051873423 .stroke-B2{stroke:#0D32B2;}
+ .d2-1051873423 .stroke-B3{stroke:#E3E9FD;}
+ .d2-1051873423 .stroke-B4{stroke:#E3E9FD;}
+ .d2-1051873423 .stroke-B5{stroke:#EDF0FD;}
+ .d2-1051873423 .stroke-B6{stroke:#F7F8FE;}
+ .d2-1051873423 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-1051873423 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-1051873423 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-1051873423 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-1051873423 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-1051873423 .background-color-N1{background-color:#0A0F25;}
+ .d2-1051873423 .background-color-N2{background-color:#676C7E;}
+ .d2-1051873423 .background-color-N3{background-color:#9499AB;}
+ .d2-1051873423 .background-color-N4{background-color:#CFD2DD;}
+ .d2-1051873423 .background-color-N5{background-color:#DEE1EB;}
+ .d2-1051873423 .background-color-N6{background-color:#EEF1F8;}
+ .d2-1051873423 .background-color-N7{background-color:#FFFFFF;}
+ .d2-1051873423 .background-color-B1{background-color:#0D32B2;}
+ .d2-1051873423 .background-color-B2{background-color:#0D32B2;}
+ .d2-1051873423 .background-color-B3{background-color:#E3E9FD;}
+ .d2-1051873423 .background-color-B4{background-color:#E3E9FD;}
+ .d2-1051873423 .background-color-B5{background-color:#EDF0FD;}
+ .d2-1051873423 .background-color-B6{background-color:#F7F8FE;}
+ .d2-1051873423 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-1051873423 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-1051873423 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-1051873423 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-1051873423 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-1051873423 .color-N1{color:#0A0F25;}
+ .d2-1051873423 .color-N2{color:#676C7E;}
+ .d2-1051873423 .color-N3{color:#9499AB;}
+ .d2-1051873423 .color-N4{color:#CFD2DD;}
+ .d2-1051873423 .color-N5{color:#DEE1EB;}
+ .d2-1051873423 .color-N6{color:#EEF1F8;}
+ .d2-1051873423 .color-N7{color:#FFFFFF;}
+ .d2-1051873423 .color-B1{color:#0D32B2;}
+ .d2-1051873423 .color-B2{color:#0D32B2;}
+ .d2-1051873423 .color-B3{color:#E3E9FD;}
+ .d2-1051873423 .color-B4{color:#E3E9FD;}
+ .d2-1051873423 .color-B5{color:#EDF0FD;}
+ .d2-1051873423 .color-B6{color:#F7F8FE;}
+ .d2-1051873423 .color-AA2{color:#4A6FF3;}
+ .d2-1051873423 .color-AA4{color:#EDF0FD;}
+ .d2-1051873423 .color-AA5{color:#F7F8FE;}
+ .d2-1051873423 .color-AB4{color:#EDF0FD;}
+ .d2-1051873423 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1051873423);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1051873423);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1051873423);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1051873423);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1051873423);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1051873423);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1051873423);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1051873423);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/network-vertical/standard.exp.txt b/e2etests/testdata/asciitxtar/network-vertical/standard.exp.txt
index d13ad7551c..eaa64c701b 100644
--- a/e2etests/testdata/asciitxtar/network-vertical/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/network-vertical/standard.exp.txt
@@ -1,63 +1,63 @@
- +-----------+
- | user |
- | |
- +-----------+
- | |
- +----------+ +----------+
- | |
- make call |
- | | +-----------------+
- | access| |
-+----------|--------------------------|--|-----------+ |
-| | network | | | |
-| v | | | |
-| +---------------------+ +------|--|------+ | |
-| | cell tower | | online portal | | |
-| | | | | | | | |
-| | | | v v | | |
-| | +-----------+ | | +-------+ | | |
-| | |satellites | | | | ui | | | |
-| | | | | | | | | | |
-| | +-----------+ | | +-------+ | | |
-| | | | | | | | | |
-| | | | | | +----------------+ | |
-| | | | | | | |
-| | sendsend send | | |
-| | | | | | | |
-| | v v v | | |
-| | +------------+ | | |
-| | |transmitter | | | |
-| | | | | | |
-| | +------------+ | | |
-| | | | | |
-| +-----------|---------+ | display
-| | | |
-| phone logs | |
-| | | |
-| +---------|---------+ | |
-| | data processor | | |
-| | v | | |
-| | +--------+ | | |
-| | |storage | | | |
-| | | | | | |
-| | +--------+ | | |
-| | | | |
-| +-------------------+ | |
-| | | |
-+-----------|----------------------------------------+ |
- | |
- +--------------------+ +---------------------+
- | |
- v |
- +-----------+
- |api server |
- | |
- +-----------+
- |
- persist
- |
- v
- +-----+
- |logs |
- | |
- +-----+
+ +-----+
+ |user |
+ | |
+ +-----+
+ | |
+ +-------make call-------+ +----------access-----------+
+ | |
+ | | +---------------------------------+
+ | | | |
++----------------|-----------------------------------------------------|-|---------+ |
+| | network | | | |
+| v | | | |
+| +---------------------------------------+ | | | |
+| | cell tower | | | | |
+| | | | | | |
+| | +-----------+ | | | | |
+| | |satellites | | | | | |
+| | | | | +------|-|-----+ | |
+| | +-----------+ | |online|portal | | |
+| | | | | | | | | | | |
+| | +---send----+ | +---send----+ | | | | | | |
+| | | | | | | | | | | |
+| | | send | | | v v | | |
+| | | | | | | +-----+ | | |
+| | +-----------+ | +-----------+ | | | ui | | | |
+| | v v v | | | | | | |
+| | +------------+ | | +-----+ | | |
+| | |transmitter | | | | | |
+| | | | | | | | |
+| | +------------+ | +--------------+ | |
+| | | | | |
+| +-------------------|-------------------+ | |
+| | | |
+| | | display
+| | | |
+| phone logs | |
+| | | |
+| +-------|-------+ | |
+| |data processor | | |
+| | v | | |
+| | +--------+ | | |
+| | |storage | | | |
+| | | | | | |
+| | +--------+ | | |
+| | | | |
+| +---------------+ | |
+| | | |
++------------------|---------------------------------------------------------------+ |
+ | |
+ +-----------------------------------------+ +-----------------------------------------+
+ v |
+ +-----------+
+ |api server |
+ | |
+ +-----------+
+ |
+ persist
+ |
+ v
+ +-----+
+ |logs |
+ | |
+ +-----+
diff --git a/e2etests/testdata/asciitxtar/overextended-lines/extended.exp.txt b/e2etests/testdata/asciitxtar/overextended-lines/extended.exp.txt
index 8e0e5c0a62..9e117cfd9c 100644
--- a/e2etests/testdata/asciitxtar/overextended-lines/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/overextended-lines/extended.exp.txt
@@ -1,41 +1,41 @@
-┌────────────────────────────────────────────────────┐
-│ k │
-│ ┌───────────────────────┐ │
-│ │ cell tower │ │
-│ │ │ │
-│ │ ╱‾‾‾‾‾‾‾‾‾╱ │ │
-│ │ ╱ ╱ │ │
-│ │ │satellites │ │
-│ │ ╲ ╲ │ │
-│ │ ╲_________╲ │ ┌────────────────┐ │
-│ │ │ │ │ │ │ online portal │ │
-│ │ │ │ │ │ │ │ │
-│ │ │ │ │ │ │ ╱‾‾╲ │ │
-│ │ │ │ │ │ │ ╱ ╲ │ │
-│ │ │ │ │ │ │ ╲ ui ╱ │ │
-│ │ sendsend send │ │ ╲__╱ │ │
-│ │ │ │ │ │ │ │ │
-│ │ │ │ │ │ └────────────────┘ │
-│ │ ▼ ▼ ▼ │ │
-│ │ ┌────────────┐ │ │
-│ │ │transmitter │ │ │
-│ │ │ │ │ │
-│ │ └────────────┘ │ │
-│ │ │ │ │
-│ └───────────│───────────┘ │
-│ │ │
-│ phone logs │
-│ │ │
-│ ┌──────────│────────┐ │
-│ │ data processor │ │
-│ │ ▼ │ │
-│ │ .-‾‾‾‾‾-. │ │
-│ │ │╲-_____-╱│ │ │
-│ │ │ │ │ │
-│ │ │ storage │ │ │
-│ │ │ │ │ │
-│ │ ╲-_____-╱ │ │
-│ │ │ │
-│ └───────────────────┘ │
-│ │
-└────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────────────┐
+│ k │
+│ │
+│ ┌───────────────────────────────────────┐ │
+│ │ cell tower │ │
+│ │ │ │
+│ │ ╱‾‾‾‾‾‾‾‾╱ │ │
+│ │ ╱ ╱ │ │
+│ │ │satellites │ │
+│ │ ╲ ╲ │ │
+│ │ ╲________╲ │ │
+│ │ │ ┌──────────────┐ │
+│ │ │ │ │ │ │online portal │ │
+│ │ ┌───send────┘ │ └───send────┐ │ │ │ │
+│ │ │ │ │ │ │ ╱‾╲ │ │
+│ │ │ │ │ │ │ ╱ ╲ │ │
+│ │ │ send │ │ │ ╳ ui ╳ │ │
+│ │ │ │ │ │ │ ╲ ╱ │ │
+│ │ └───────────┐ │ ┌───────────┘ │ │ ╲_╱ │ │
+│ │ ▼ ▼ ▼ │ │ │ │
+│ │ ┌────────────┐ │ │ │ │
+│ │ │transmitter │ │ │ │ │
+│ │ │ │ │ └──────────────┘ │
+│ │ └────────────┘ │ │
+│ │ │ │ │
+│ └───────────────────│───────────────────┘ │
+│ │ │
+│ phone logs │
+│ │ │
+│ ┌───────│───────┐ │
+│ │data processor │ │
+│ │ ▼ │ │
+│ │ .-‾‾‾-. │ │
+│ │ │╲-___-╱│ │ │
+│ │ │ │ │ │
+│ │ │storage│ │ │
+│ │ ╲-___-╱ │ │
+│ │ │ │
+│ └───────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────────────┘
diff --git a/e2etests/testdata/asciitxtar/overextended-lines/sketch.exp.svg b/e2etests/testdata/asciitxtar/overextended-lines/sketch.exp.svg
index 9237faff36..5900fd8e18 100644
--- a/e2etests/testdata/asciitxtar/overextended-lines/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/overextended-lines/sketch.exp.svg
@@ -1,23 +1,23 @@
-kcell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logs
-
-
-
-
-
+ .d2-2348904205 .fill-N1{fill:#0A0F25;}
+ .d2-2348904205 .fill-N2{fill:#676C7E;}
+ .d2-2348904205 .fill-N3{fill:#9499AB;}
+ .d2-2348904205 .fill-N4{fill:#CFD2DD;}
+ .d2-2348904205 .fill-N5{fill:#DEE1EB;}
+ .d2-2348904205 .fill-N6{fill:#EEF1F8;}
+ .d2-2348904205 .fill-N7{fill:#FFFFFF;}
+ .d2-2348904205 .fill-B1{fill:#0D32B2;}
+ .d2-2348904205 .fill-B2{fill:#0D32B2;}
+ .d2-2348904205 .fill-B3{fill:#E3E9FD;}
+ .d2-2348904205 .fill-B4{fill:#E3E9FD;}
+ .d2-2348904205 .fill-B5{fill:#EDF0FD;}
+ .d2-2348904205 .fill-B6{fill:#F7F8FE;}
+ .d2-2348904205 .fill-AA2{fill:#4A6FF3;}
+ .d2-2348904205 .fill-AA4{fill:#EDF0FD;}
+ .d2-2348904205 .fill-AA5{fill:#F7F8FE;}
+ .d2-2348904205 .fill-AB4{fill:#EDF0FD;}
+ .d2-2348904205 .fill-AB5{fill:#F7F8FE;}
+ .d2-2348904205 .stroke-N1{stroke:#0A0F25;}
+ .d2-2348904205 .stroke-N2{stroke:#676C7E;}
+ .d2-2348904205 .stroke-N3{stroke:#9499AB;}
+ .d2-2348904205 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2348904205 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2348904205 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2348904205 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2348904205 .stroke-B1{stroke:#0D32B2;}
+ .d2-2348904205 .stroke-B2{stroke:#0D32B2;}
+ .d2-2348904205 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2348904205 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2348904205 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2348904205 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2348904205 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2348904205 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2348904205 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2348904205 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2348904205 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2348904205 .background-color-N1{background-color:#0A0F25;}
+ .d2-2348904205 .background-color-N2{background-color:#676C7E;}
+ .d2-2348904205 .background-color-N3{background-color:#9499AB;}
+ .d2-2348904205 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2348904205 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2348904205 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2348904205 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2348904205 .background-color-B1{background-color:#0D32B2;}
+ .d2-2348904205 .background-color-B2{background-color:#0D32B2;}
+ .d2-2348904205 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2348904205 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2348904205 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2348904205 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2348904205 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2348904205 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2348904205 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2348904205 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2348904205 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2348904205 .color-N1{color:#0A0F25;}
+ .d2-2348904205 .color-N2{color:#676C7E;}
+ .d2-2348904205 .color-N3{color:#9499AB;}
+ .d2-2348904205 .color-N4{color:#CFD2DD;}
+ .d2-2348904205 .color-N5{color:#DEE1EB;}
+ .d2-2348904205 .color-N6{color:#EEF1F8;}
+ .d2-2348904205 .color-N7{color:#FFFFFF;}
+ .d2-2348904205 .color-B1{color:#0D32B2;}
+ .d2-2348904205 .color-B2{color:#0D32B2;}
+ .d2-2348904205 .color-B3{color:#E3E9FD;}
+ .d2-2348904205 .color-B4{color:#E3E9FD;}
+ .d2-2348904205 .color-B5{color:#EDF0FD;}
+ .d2-2348904205 .color-B6{color:#F7F8FE;}
+ .d2-2348904205 .color-AA2{color:#4A6FF3;}
+ .d2-2348904205 .color-AA4{color:#EDF0FD;}
+ .d2-2348904205 .color-AA5{color:#F7F8FE;}
+ .d2-2348904205 .color-AB4{color:#EDF0FD;}
+ .d2-2348904205 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2348904205);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2348904205);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2348904205);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2348904205);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2348904205);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2348904205);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2348904205);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2348904205);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>kcell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logs
+
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/overextended-lines/standard.exp.txt b/e2etests/testdata/asciitxtar/overextended-lines/standard.exp.txt
index e14eb91263..6d48077c9a 100644
--- a/e2etests/testdata/asciitxtar/overextended-lines/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/overextended-lines/standard.exp.txt
@@ -1,41 +1,41 @@
-+----------------------------------------------------+
-| k |
-| +-----------------------+ |
-| | cell tower | |
-| | | |
-| | /---------/ | |
-| | / / | |
-| | |satellites | |
-| | \ \ | |
-| | \_________\ | +----------------+ |
-| | | | | | | online portal | |
-| | | | | | | | |
-| | | | | | | /--\ | |
-| | | | | | | / \ | |
-| | | | | | | \ ui / | |
-| | sendsend send | | \__/ | |
-| | | | | | | | |
-| | | | | | +----------------+ |
-| | v v v | |
-| | +------------+ | |
-| | |transmitter | | |
-| | | | | |
-| | +------------+ | |
-| | | | |
-| +-----------|-----------+ |
-| | |
-| phone logs |
-| | |
-| +----------|--------+ |
-| | data processor | |
-| | v | |
-| | .-------. | |
-| | |\-_____-/| | |
-| | | | | |
-| | | storage | | |
-| | | | | |
-| | \-_____-/ | |
-| | | |
-| +-------------------+ |
-| |
-+----------------------------------------------------+
++--------------------------------------------------------------------------------+
+| k |
+| |
+| +---------------------------------------+ |
+| | cell tower | |
+| | | |
+| | /--------/ | |
+| | / / | |
+| | |satellites | |
+| | \ \ | |
+| | \________\ | |
+| | | +--------------+ |
+| | | | | | |online portal | |
+| | +---send----+ | +---send----+ | | | |
+| | | | | | | /-\ | |
+| | | | | | | / \ | |
+| | | send | | | X ui X | |
+| | | | | | | \ / | |
+| | +-----------+ | +-----------+ | | \_/ | |
+| | v v v | | | |
+| | +------------+ | | | |
+| | |transmitter | | | | |
+| | | | | +--------------+ |
+| | +------------+ | |
+| | | | |
+| +-------------------|-------------------+ |
+| | |
+| phone logs |
+| | |
+| +-------|-------+ |
+| |data processor | |
+| | v | |
+| | .-----. | |
+| | |\-___-/| | |
+| | | | | |
+| | |storage| | |
+| | \-___-/ | |
+| | | |
+| +---------------+ |
+| |
++--------------------------------------------------------------------------------+
diff --git a/e2etests/testdata/asciitxtar/sequence-newlines/extended.exp.txt b/e2etests/testdata/asciitxtar/sequence-newlines/extended.exp.txt
index a0ccc61b1d..9c390d5afd 100644
--- a/e2etests/testdata/asciitxtar/sequence-newlines/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/sequence-newlines/extended.exp.txt
@@ -1,11 +1,11 @@
-┌────────┐ ┌────────┐
-│ alice │ │ bob │
-│ │ │ │
-└────────┘ └────────┘
- │ │
- │──What does it─mean────▶│
- │ to be well-adjusted? │
- │ │
- The─ability─to play bridge or
- golf as if they were games.
- │ │
+┌──────┐ ┌──────┐
+│alice │ │ bob │
+│ │ │ │
+└──────┘ └──────┘
+ │ │
+ ─────What does it─mean────────▶│
+ │ to be well-adjusted? │
+ │ │
+ │◀The─ability─to play bridge or─
+ │ golf as if they were games. │
+ │ │
diff --git a/e2etests/testdata/asciitxtar/sequence-newlines/sketch.exp.svg b/e2etests/testdata/asciitxtar/sequence-newlines/sketch.exp.svg
index c29eb98602..dbc43babf6 100644
--- a/e2etests/testdata/asciitxtar/sequence-newlines/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/sequence-newlines/sketch.exp.svg
@@ -1,16 +1,16 @@
-alicebob What does it meanto be well-adjusted?The ability to play bridge orgolf as if they were games.
-
-
-
+ .d2-1907459332 .fill-N1{fill:#0A0F25;}
+ .d2-1907459332 .fill-N2{fill:#676C7E;}
+ .d2-1907459332 .fill-N3{fill:#9499AB;}
+ .d2-1907459332 .fill-N4{fill:#CFD2DD;}
+ .d2-1907459332 .fill-N5{fill:#DEE1EB;}
+ .d2-1907459332 .fill-N6{fill:#EEF1F8;}
+ .d2-1907459332 .fill-N7{fill:#FFFFFF;}
+ .d2-1907459332 .fill-B1{fill:#0D32B2;}
+ .d2-1907459332 .fill-B2{fill:#0D32B2;}
+ .d2-1907459332 .fill-B3{fill:#E3E9FD;}
+ .d2-1907459332 .fill-B4{fill:#E3E9FD;}
+ .d2-1907459332 .fill-B5{fill:#EDF0FD;}
+ .d2-1907459332 .fill-B6{fill:#F7F8FE;}
+ .d2-1907459332 .fill-AA2{fill:#4A6FF3;}
+ .d2-1907459332 .fill-AA4{fill:#EDF0FD;}
+ .d2-1907459332 .fill-AA5{fill:#F7F8FE;}
+ .d2-1907459332 .fill-AB4{fill:#EDF0FD;}
+ .d2-1907459332 .fill-AB5{fill:#F7F8FE;}
+ .d2-1907459332 .stroke-N1{stroke:#0A0F25;}
+ .d2-1907459332 .stroke-N2{stroke:#676C7E;}
+ .d2-1907459332 .stroke-N3{stroke:#9499AB;}
+ .d2-1907459332 .stroke-N4{stroke:#CFD2DD;}
+ .d2-1907459332 .stroke-N5{stroke:#DEE1EB;}
+ .d2-1907459332 .stroke-N6{stroke:#EEF1F8;}
+ .d2-1907459332 .stroke-N7{stroke:#FFFFFF;}
+ .d2-1907459332 .stroke-B1{stroke:#0D32B2;}
+ .d2-1907459332 .stroke-B2{stroke:#0D32B2;}
+ .d2-1907459332 .stroke-B3{stroke:#E3E9FD;}
+ .d2-1907459332 .stroke-B4{stroke:#E3E9FD;}
+ .d2-1907459332 .stroke-B5{stroke:#EDF0FD;}
+ .d2-1907459332 .stroke-B6{stroke:#F7F8FE;}
+ .d2-1907459332 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-1907459332 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-1907459332 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-1907459332 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-1907459332 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-1907459332 .background-color-N1{background-color:#0A0F25;}
+ .d2-1907459332 .background-color-N2{background-color:#676C7E;}
+ .d2-1907459332 .background-color-N3{background-color:#9499AB;}
+ .d2-1907459332 .background-color-N4{background-color:#CFD2DD;}
+ .d2-1907459332 .background-color-N5{background-color:#DEE1EB;}
+ .d2-1907459332 .background-color-N6{background-color:#EEF1F8;}
+ .d2-1907459332 .background-color-N7{background-color:#FFFFFF;}
+ .d2-1907459332 .background-color-B1{background-color:#0D32B2;}
+ .d2-1907459332 .background-color-B2{background-color:#0D32B2;}
+ .d2-1907459332 .background-color-B3{background-color:#E3E9FD;}
+ .d2-1907459332 .background-color-B4{background-color:#E3E9FD;}
+ .d2-1907459332 .background-color-B5{background-color:#EDF0FD;}
+ .d2-1907459332 .background-color-B6{background-color:#F7F8FE;}
+ .d2-1907459332 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-1907459332 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-1907459332 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-1907459332 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-1907459332 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-1907459332 .color-N1{color:#0A0F25;}
+ .d2-1907459332 .color-N2{color:#676C7E;}
+ .d2-1907459332 .color-N3{color:#9499AB;}
+ .d2-1907459332 .color-N4{color:#CFD2DD;}
+ .d2-1907459332 .color-N5{color:#DEE1EB;}
+ .d2-1907459332 .color-N6{color:#EEF1F8;}
+ .d2-1907459332 .color-N7{color:#FFFFFF;}
+ .d2-1907459332 .color-B1{color:#0D32B2;}
+ .d2-1907459332 .color-B2{color:#0D32B2;}
+ .d2-1907459332 .color-B3{color:#E3E9FD;}
+ .d2-1907459332 .color-B4{color:#E3E9FD;}
+ .d2-1907459332 .color-B5{color:#EDF0FD;}
+ .d2-1907459332 .color-B6{color:#F7F8FE;}
+ .d2-1907459332 .color-AA2{color:#4A6FF3;}
+ .d2-1907459332 .color-AA4{color:#EDF0FD;}
+ .d2-1907459332 .color-AA5{color:#F7F8FE;}
+ .d2-1907459332 .color-AB4{color:#EDF0FD;}
+ .d2-1907459332 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1907459332);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1907459332);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1907459332);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1907459332);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1907459332);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1907459332);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1907459332);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1907459332);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>alicebob What does it meanto be well-adjusted?The ability to play bridge orgolf as if they were games.
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/sequence-newlines/standard.exp.txt b/e2etests/testdata/asciitxtar/sequence-newlines/standard.exp.txt
index 763eb023f7..172afe51e2 100644
--- a/e2etests/testdata/asciitxtar/sequence-newlines/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/sequence-newlines/standard.exp.txt
@@ -1,11 +1,11 @@
-+--------+ +--------+
-| alice | | bob |
-| | | |
-+--------+ +--------+
- | |
- |--What does it-mean---->|
- | to be well-adjusted? |
- | |
- The-ability-to play bridge or
- golf as if they were games.
- | |
++------+ +------+
+|alice | | bob |
+| | | |
++------+ +------+
+ | |
+ -----What does it-mean-------->|
+ | to be well-adjusted? |
+ | |
+ |acbd
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/shape-container/standard.exp.txt b/e2etests/testdata/asciitxtar/shape-container/standard.exp.txt
new file mode 100644
index 0000000000..28dc131d7f
--- /dev/null
+++ b/e2etests/testdata/asciitxtar/shape-container/standard.exp.txt
@@ -0,0 +1,20 @@
++----------+
+| a |
+| \-- \ |
+| \ \ |
+| /b / |
+| /__ / |
+| |
+| | |
++-----|----+
+ |
++-----|----+
+| c| |
+| v |
+| .-. |
+| |\-/| |
+| | | |
+| | d | |
+| \-/ |
+| |
++----------+
diff --git a/e2etests/testdata/asciitxtar/simple-sequence/extended.exp.txt b/e2etests/testdata/asciitxtar/simple-sequence/extended.exp.txt
index 419143dfd1..f64d2c58ae 100644
--- a/e2etests/testdata/asciitxtar/simple-sequence/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/simple-sequence/extended.exp.txt
@@ -1,11 +1,11 @@
-┌────────┐ ┌────────┐ ┌─────────┐
-│ alice │ │ bob │ │ carl │
-│ │ │ │ │ │
-└────────┘ └────────┘ └─────────┘
- │ │ │
- │─────hello────▶│ │
- │ │ │
- │◀────jello─────│ │
- │ │ │
- │──────────────bye────────────▶│
- │ │ │
+┌──────┐ ┌──────┐ ┌─────┐
+│alice │ │ bob │ │carl │
+│ │ │ │ │ │
+└──────┘ └──────┘ └─────┘
+ │ │ │
+ ───hello─▶│ │
+ │ │ │
+ │◀──jello── │
+ │ │ │
+ ─────────bye───────▶│
+ │ │ │
diff --git a/e2etests/testdata/asciitxtar/simple-sequence/sketch.exp.svg b/e2etests/testdata/asciitxtar/simple-sequence/sketch.exp.svg
index 7c0ef3442e..bb38c26f3b 100644
--- a/e2etests/testdata/asciitxtar/simple-sequence/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/simple-sequence/sketch.exp.svg
@@ -1,16 +1,16 @@
-alicebobcarl hellojellobye
-
-
-
-
+ .d2-2458794990 .fill-N1{fill:#0A0F25;}
+ .d2-2458794990 .fill-N2{fill:#676C7E;}
+ .d2-2458794990 .fill-N3{fill:#9499AB;}
+ .d2-2458794990 .fill-N4{fill:#CFD2DD;}
+ .d2-2458794990 .fill-N5{fill:#DEE1EB;}
+ .d2-2458794990 .fill-N6{fill:#EEF1F8;}
+ .d2-2458794990 .fill-N7{fill:#FFFFFF;}
+ .d2-2458794990 .fill-B1{fill:#0D32B2;}
+ .d2-2458794990 .fill-B2{fill:#0D32B2;}
+ .d2-2458794990 .fill-B3{fill:#E3E9FD;}
+ .d2-2458794990 .fill-B4{fill:#E3E9FD;}
+ .d2-2458794990 .fill-B5{fill:#EDF0FD;}
+ .d2-2458794990 .fill-B6{fill:#F7F8FE;}
+ .d2-2458794990 .fill-AA2{fill:#4A6FF3;}
+ .d2-2458794990 .fill-AA4{fill:#EDF0FD;}
+ .d2-2458794990 .fill-AA5{fill:#F7F8FE;}
+ .d2-2458794990 .fill-AB4{fill:#EDF0FD;}
+ .d2-2458794990 .fill-AB5{fill:#F7F8FE;}
+ .d2-2458794990 .stroke-N1{stroke:#0A0F25;}
+ .d2-2458794990 .stroke-N2{stroke:#676C7E;}
+ .d2-2458794990 .stroke-N3{stroke:#9499AB;}
+ .d2-2458794990 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2458794990 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2458794990 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2458794990 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2458794990 .stroke-B1{stroke:#0D32B2;}
+ .d2-2458794990 .stroke-B2{stroke:#0D32B2;}
+ .d2-2458794990 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2458794990 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2458794990 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2458794990 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2458794990 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2458794990 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2458794990 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2458794990 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2458794990 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2458794990 .background-color-N1{background-color:#0A0F25;}
+ .d2-2458794990 .background-color-N2{background-color:#676C7E;}
+ .d2-2458794990 .background-color-N3{background-color:#9499AB;}
+ .d2-2458794990 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2458794990 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2458794990 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2458794990 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2458794990 .background-color-B1{background-color:#0D32B2;}
+ .d2-2458794990 .background-color-B2{background-color:#0D32B2;}
+ .d2-2458794990 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2458794990 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2458794990 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2458794990 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2458794990 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2458794990 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2458794990 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2458794990 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2458794990 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2458794990 .color-N1{color:#0A0F25;}
+ .d2-2458794990 .color-N2{color:#676C7E;}
+ .d2-2458794990 .color-N3{color:#9499AB;}
+ .d2-2458794990 .color-N4{color:#CFD2DD;}
+ .d2-2458794990 .color-N5{color:#DEE1EB;}
+ .d2-2458794990 .color-N6{color:#EEF1F8;}
+ .d2-2458794990 .color-N7{color:#FFFFFF;}
+ .d2-2458794990 .color-B1{color:#0D32B2;}
+ .d2-2458794990 .color-B2{color:#0D32B2;}
+ .d2-2458794990 .color-B3{color:#E3E9FD;}
+ .d2-2458794990 .color-B4{color:#E3E9FD;}
+ .d2-2458794990 .color-B5{color:#EDF0FD;}
+ .d2-2458794990 .color-B6{color:#F7F8FE;}
+ .d2-2458794990 .color-AA2{color:#4A6FF3;}
+ .d2-2458794990 .color-AA4{color:#EDF0FD;}
+ .d2-2458794990 .color-AA5{color:#F7F8FE;}
+ .d2-2458794990 .color-AB4{color:#EDF0FD;}
+ .d2-2458794990 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2458794990);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2458794990);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2458794990);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2458794990);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2458794990);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2458794990);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2458794990);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2458794990);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>alicebobcarl hellojellobye
+
+
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/simple-sequence/standard.exp.txt b/e2etests/testdata/asciitxtar/simple-sequence/standard.exp.txt
index 476607b299..d86241b86b 100644
--- a/e2etests/testdata/asciitxtar/simple-sequence/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/simple-sequence/standard.exp.txt
@@ -1,11 +1,11 @@
-+--------+ +--------+ +---------+
-| alice | | bob | | carl |
-| | | | | |
-+--------+ +--------+ +---------+
- | | |
- |-----hello---->| |
- | | |
- |<----jello-----| |
- | | |
- |--------------bye------------>|
- | | |
++------+ +------+ +-----+
+|alice | | bob | |carl |
+| | | | | |
++------+ +------+ +-----+
+ | | |
+ ---hello->| |
+ | | |
+ |<--jello-- |
+ | | |
+ ---------bye------->|
+ | | |
diff --git a/e2etests/testdata/asciitxtar/sql-table/extended.exp.txt b/e2etests/testdata/asciitxtar/sql-table/extended.exp.txt
index baa8331595..02d2260a65 100644
--- a/e2etests/testdata/asciitxtar/sql-table/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/sql-table/extended.exp.txt
@@ -1,21 +1,23 @@
-┌─────────────────────────────┐
-│ costumes │
-│ │
-├─────────────────────────────┤
-│id int │
-│silliness int │
-│monster int │
-│last_updated timestamp │───┐
-│ │ │
-└─────────────────────────────┘ │
- │
- │ ┌─────────────────────────────┐
- │ │ monsters │
- │ │ │
- └───▶├─────────────────────────────┤
- │id int │
- │movie string │
- │weight int │
- │last_updated timestamp │
- │ │
- └─────────────────────────────┘
+┌───────────────────────────┐
+│ costumes │
+│ │
+├───────────────────────────┤
+│id int │
+│silliness int │
+│monster int │
+│last_updated timestamp │
+│ │
+│ │──┐
+│ │ │
+└───────────────────────────┘ │
+ │
+ │ ┌───────────────────────────┐
+ │ │ monsters │
+ │ │ │
+ │ ├───────────────────────────┤
+ └─▶│id int │
+ │movie string │
+ │weight int │
+ │last_updated timestamp │
+ │ │
+ └───────────────────────────┘
diff --git a/e2etests/testdata/asciitxtar/sql-table/sketch.exp.svg b/e2etests/testdata/asciitxtar/sql-table/sketch.exp.svg
index 70721d3c2e..69561d9055 100644
--- a/e2etests/testdata/asciitxtar/sql-table/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/sql-table/sketch.exp.svg
@@ -1,9 +1,9 @@
-costumesidintPKsillinessintmonsterintlast_updatedtimestampmonstersidintPKmoviestringweightintlast_updatedtimestamp
-
+ .d2-2293584073 .fill-N1{fill:#0A0F25;}
+ .d2-2293584073 .fill-N2{fill:#676C7E;}
+ .d2-2293584073 .fill-N3{fill:#9499AB;}
+ .d2-2293584073 .fill-N4{fill:#CFD2DD;}
+ .d2-2293584073 .fill-N5{fill:#DEE1EB;}
+ .d2-2293584073 .fill-N6{fill:#EEF1F8;}
+ .d2-2293584073 .fill-N7{fill:#FFFFFF;}
+ .d2-2293584073 .fill-B1{fill:#0D32B2;}
+ .d2-2293584073 .fill-B2{fill:#0D32B2;}
+ .d2-2293584073 .fill-B3{fill:#E3E9FD;}
+ .d2-2293584073 .fill-B4{fill:#E3E9FD;}
+ .d2-2293584073 .fill-B5{fill:#EDF0FD;}
+ .d2-2293584073 .fill-B6{fill:#F7F8FE;}
+ .d2-2293584073 .fill-AA2{fill:#4A6FF3;}
+ .d2-2293584073 .fill-AA4{fill:#EDF0FD;}
+ .d2-2293584073 .fill-AA5{fill:#F7F8FE;}
+ .d2-2293584073 .fill-AB4{fill:#EDF0FD;}
+ .d2-2293584073 .fill-AB5{fill:#F7F8FE;}
+ .d2-2293584073 .stroke-N1{stroke:#0A0F25;}
+ .d2-2293584073 .stroke-N2{stroke:#676C7E;}
+ .d2-2293584073 .stroke-N3{stroke:#9499AB;}
+ .d2-2293584073 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2293584073 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2293584073 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2293584073 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2293584073 .stroke-B1{stroke:#0D32B2;}
+ .d2-2293584073 .stroke-B2{stroke:#0D32B2;}
+ .d2-2293584073 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2293584073 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2293584073 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2293584073 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2293584073 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2293584073 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2293584073 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2293584073 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2293584073 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2293584073 .background-color-N1{background-color:#0A0F25;}
+ .d2-2293584073 .background-color-N2{background-color:#676C7E;}
+ .d2-2293584073 .background-color-N3{background-color:#9499AB;}
+ .d2-2293584073 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2293584073 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2293584073 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2293584073 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2293584073 .background-color-B1{background-color:#0D32B2;}
+ .d2-2293584073 .background-color-B2{background-color:#0D32B2;}
+ .d2-2293584073 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2293584073 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2293584073 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2293584073 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2293584073 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2293584073 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2293584073 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2293584073 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2293584073 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2293584073 .color-N1{color:#0A0F25;}
+ .d2-2293584073 .color-N2{color:#676C7E;}
+ .d2-2293584073 .color-N3{color:#9499AB;}
+ .d2-2293584073 .color-N4{color:#CFD2DD;}
+ .d2-2293584073 .color-N5{color:#DEE1EB;}
+ .d2-2293584073 .color-N6{color:#EEF1F8;}
+ .d2-2293584073 .color-N7{color:#FFFFFF;}
+ .d2-2293584073 .color-B1{color:#0D32B2;}
+ .d2-2293584073 .color-B2{color:#0D32B2;}
+ .d2-2293584073 .color-B3{color:#E3E9FD;}
+ .d2-2293584073 .color-B4{color:#E3E9FD;}
+ .d2-2293584073 .color-B5{color:#EDF0FD;}
+ .d2-2293584073 .color-B6{color:#F7F8FE;}
+ .d2-2293584073 .color-AA2{color:#4A6FF3;}
+ .d2-2293584073 .color-AA4{color:#EDF0FD;}
+ .d2-2293584073 .color-AA5{color:#F7F8FE;}
+ .d2-2293584073 .color-AB4{color:#EDF0FD;}
+ .d2-2293584073 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2293584073);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2293584073);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2293584073);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2293584073);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2293584073);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2293584073);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2293584073);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2293584073);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>costumesidintPKsillinessintmonsterintlast_updatedtimestampmonstersidintPKmoviestringweightintlast_updatedtimestamp
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/sql-table/standard.exp.txt b/e2etests/testdata/asciitxtar/sql-table/standard.exp.txt
index b83a033cb7..9a9f48582d 100644
--- a/e2etests/testdata/asciitxtar/sql-table/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/sql-table/standard.exp.txt
@@ -1,21 +1,23 @@
-+-----------------------------+
-| costumes |
-| |
-+-----------------------------+
-|id int |
-|silliness int |
-|monster int |
-|last_updated timestamp |---+
-| | |
-+-----------------------------+ |
- |
- | +-----------------------------+
- | | monsters |
- | | |
- +--->+-----------------------------+
- |id int |
- |movie string |
- |weight int |
- |last_updated timestamp |
- | |
- +-----------------------------+
++---------------------------+
+| costumes |
+| |
++---------------------------+
+|id int |
+|silliness int |
+|monster int |
+|last_updated timestamp |
+| |
+| |--+
+| | |
++---------------------------+ |
+ |
+ | +---------------------------+
+ | | monsters |
+ | | |
+ | +---------------------------+
+ +->|id int |
+ |movie string |
+ |weight int |
+ |last_updated timestamp |
+ | |
+ +---------------------------+
diff --git a/e2etests/testdata/asciitxtar/terminal-horizontal/extended.exp.txt b/e2etests/testdata/asciitxtar/terminal-horizontal/extended.exp.txt
index 338ab69607..dbad5dbddf 100644
--- a/e2etests/testdata/asciitxtar/terminal-horizontal/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/terminal-horizontal/extended.exp.txt
@@ -1,4 +1,4 @@
-┌──────┐ ┌────┐
-│ A │──────HELLO WORLD─────▶│ B │
-│ │ │ │
-└──────┘ └────┘
+┌──┐ ┌──┐
+│A │ │B │
+│ │───HELLO WORLD─▶│ │
+└──┘ └──┘
diff --git a/e2etests/testdata/asciitxtar/terminal-horizontal/sketch.exp.svg b/e2etests/testdata/asciitxtar/terminal-horizontal/sketch.exp.svg
index 6402bebf4e..01899efe3c 100644
--- a/e2etests/testdata/asciitxtar/terminal-horizontal/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/terminal-horizontal/sketch.exp.svg
@@ -1,23 +1,23 @@
-AB HELLO WORLD
-
-
+ .d2-2151200226 .fill-N1{fill:#000410;}
+ .d2-2151200226 .fill-N2{fill:#0000B8;}
+ .d2-2151200226 .fill-N3{fill:#9499AB;}
+ .d2-2151200226 .fill-N4{fill:#CFD2DD;}
+ .d2-2151200226 .fill-N5{fill:#C3DEF3;}
+ .d2-2151200226 .fill-N6{fill:#EEF1F8;}
+ .d2-2151200226 .fill-N7{fill:#FFFFFF;}
+ .d2-2151200226 .fill-B1{fill:#000410;}
+ .d2-2151200226 .fill-B2{fill:#0000E4;}
+ .d2-2151200226 .fill-B3{fill:#5AA4DC;}
+ .d2-2151200226 .fill-B4{fill:#E7E9EE;}
+ .d2-2151200226 .fill-B5{fill:#F5F6F9;}
+ .d2-2151200226 .fill-B6{fill:#FFFFFF;}
+ .d2-2151200226 .fill-AA2{fill:#008566;}
+ .d2-2151200226 .fill-AA4{fill:#45BBA5;}
+ .d2-2151200226 .fill-AA5{fill:#7ACCBD;}
+ .d2-2151200226 .fill-AB4{fill:#F1C759;}
+ .d2-2151200226 .fill-AB5{fill:#F9E088;}
+ .d2-2151200226 .stroke-N1{stroke:#000410;}
+ .d2-2151200226 .stroke-N2{stroke:#0000B8;}
+ .d2-2151200226 .stroke-N3{stroke:#9499AB;}
+ .d2-2151200226 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2151200226 .stroke-N5{stroke:#C3DEF3;}
+ .d2-2151200226 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2151200226 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2151200226 .stroke-B1{stroke:#000410;}
+ .d2-2151200226 .stroke-B2{stroke:#0000E4;}
+ .d2-2151200226 .stroke-B3{stroke:#5AA4DC;}
+ .d2-2151200226 .stroke-B4{stroke:#E7E9EE;}
+ .d2-2151200226 .stroke-B5{stroke:#F5F6F9;}
+ .d2-2151200226 .stroke-B6{stroke:#FFFFFF;}
+ .d2-2151200226 .stroke-AA2{stroke:#008566;}
+ .d2-2151200226 .stroke-AA4{stroke:#45BBA5;}
+ .d2-2151200226 .stroke-AA5{stroke:#7ACCBD;}
+ .d2-2151200226 .stroke-AB4{stroke:#F1C759;}
+ .d2-2151200226 .stroke-AB5{stroke:#F9E088;}
+ .d2-2151200226 .background-color-N1{background-color:#000410;}
+ .d2-2151200226 .background-color-N2{background-color:#0000B8;}
+ .d2-2151200226 .background-color-N3{background-color:#9499AB;}
+ .d2-2151200226 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2151200226 .background-color-N5{background-color:#C3DEF3;}
+ .d2-2151200226 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2151200226 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2151200226 .background-color-B1{background-color:#000410;}
+ .d2-2151200226 .background-color-B2{background-color:#0000E4;}
+ .d2-2151200226 .background-color-B3{background-color:#5AA4DC;}
+ .d2-2151200226 .background-color-B4{background-color:#E7E9EE;}
+ .d2-2151200226 .background-color-B5{background-color:#F5F6F9;}
+ .d2-2151200226 .background-color-B6{background-color:#FFFFFF;}
+ .d2-2151200226 .background-color-AA2{background-color:#008566;}
+ .d2-2151200226 .background-color-AA4{background-color:#45BBA5;}
+ .d2-2151200226 .background-color-AA5{background-color:#7ACCBD;}
+ .d2-2151200226 .background-color-AB4{background-color:#F1C759;}
+ .d2-2151200226 .background-color-AB5{background-color:#F9E088;}
+ .d2-2151200226 .color-N1{color:#000410;}
+ .d2-2151200226 .color-N2{color:#0000B8;}
+ .d2-2151200226 .color-N3{color:#9499AB;}
+ .d2-2151200226 .color-N4{color:#CFD2DD;}
+ .d2-2151200226 .color-N5{color:#C3DEF3;}
+ .d2-2151200226 .color-N6{color:#EEF1F8;}
+ .d2-2151200226 .color-N7{color:#FFFFFF;}
+ .d2-2151200226 .color-B1{color:#000410;}
+ .d2-2151200226 .color-B2{color:#0000E4;}
+ .d2-2151200226 .color-B3{color:#5AA4DC;}
+ .d2-2151200226 .color-B4{color:#E7E9EE;}
+ .d2-2151200226 .color-B5{color:#F5F6F9;}
+ .d2-2151200226 .color-B6{color:#FFFFFF;}
+ .d2-2151200226 .color-AA2{color:#008566;}
+ .d2-2151200226 .color-AA4{color:#45BBA5;}
+ .d2-2151200226 .color-AA5{color:#7ACCBD;}
+ .d2-2151200226 .color-AB4{color:#F1C759;}
+ .d2-2151200226 .color-AB5{color:#F9E088;}.appendix text.text{fill:#000410}.md{--color-fg-default:#000410;--color-fg-muted:#0000B8;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#000410;--color-border-muted:#0000E4;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0000E4;--color-accent-emphasis:#0000E4;--color-attention-subtle:#0000B8;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2151200226);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2151200226);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2151200226);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2151200226);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2151200226);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2151200226);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-AA5{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-AB4{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-AB5{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2151200226);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-darker-d2-2151200226);mix-blend-mode:lighten}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-normal-d2-2151200226);mix-blend-mode:color-burn}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2151200226);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2151200226);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>AB HELLO WORLD
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/terminal-horizontal/standard.exp.txt b/e2etests/testdata/asciitxtar/terminal-horizontal/standard.exp.txt
index bdb540bf3d..46dd2ed4fb 100644
--- a/e2etests/testdata/asciitxtar/terminal-horizontal/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/terminal-horizontal/standard.exp.txt
@@ -1,4 +1,4 @@
-+------+ +----+
-| A |------HELLO WORLD----->| B |
-| | | |
-+------+ +----+
++--+ +--+
+|A | |B |
+| |---HELLO WORLD->| |
++--+ +--+
diff --git a/e2etests/testdata/asciitxtar/three-down/extended.exp.txt b/e2etests/testdata/asciitxtar/three-down/extended.exp.txt
index c3b9a98499..b10bfd3ad0 100644
--- a/e2etests/testdata/asciitxtar/three-down/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/three-down/extended.exp.txt
@@ -1,22 +1,22 @@
- ┌──────────┐
- │ x │
- │ │
- └──────────┘
- │ │ │
- ┌───┘ │ └──┐
- │ │ │
- │ okay │
- │ │ │
- ▼ ▼ │
-┌──────┐ │
-│ y │ │
-│ │ │
-└──────┘ │
- │ │
- │ ┌───┘
- │ │
- ▼ ▼
- ┌──────┐
- │ z │
- │ │
- └──────┘
+ ┌────────┐
+ │ x │
+ │ │
+ └────────┘
+ │ │ │
+┌───┘ │ └───┐
+│ │ │
+│ okay │
+│ │ │
+└─┐ ┌─┘ │
+ ▼ ▼ │
+┌────┐ │
+│ y │ │
+│ │ │
+└────┘ │
+ │ │
+ └──┐ ┌───┘
+ ▼ ▼
+ ┌────┐
+ │ z │
+ │ │
+ └────┘
diff --git a/e2etests/testdata/asciitxtar/three-down/sketch.exp.svg b/e2etests/testdata/asciitxtar/three-down/sketch.exp.svg
index 40a842abf9..88191e5155 100644
--- a/e2etests/testdata/asciitxtar/three-down/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/three-down/sketch.exp.svg
@@ -1,16 +1,16 @@
-xyz okay
-
-
+ .d2-95692537 .fill-N1{fill:#0A0F25;}
+ .d2-95692537 .fill-N2{fill:#676C7E;}
+ .d2-95692537 .fill-N3{fill:#9499AB;}
+ .d2-95692537 .fill-N4{fill:#CFD2DD;}
+ .d2-95692537 .fill-N5{fill:#DEE1EB;}
+ .d2-95692537 .fill-N6{fill:#EEF1F8;}
+ .d2-95692537 .fill-N7{fill:#FFFFFF;}
+ .d2-95692537 .fill-B1{fill:#0D32B2;}
+ .d2-95692537 .fill-B2{fill:#0D32B2;}
+ .d2-95692537 .fill-B3{fill:#E3E9FD;}
+ .d2-95692537 .fill-B4{fill:#E3E9FD;}
+ .d2-95692537 .fill-B5{fill:#EDF0FD;}
+ .d2-95692537 .fill-B6{fill:#F7F8FE;}
+ .d2-95692537 .fill-AA2{fill:#4A6FF3;}
+ .d2-95692537 .fill-AA4{fill:#EDF0FD;}
+ .d2-95692537 .fill-AA5{fill:#F7F8FE;}
+ .d2-95692537 .fill-AB4{fill:#EDF0FD;}
+ .d2-95692537 .fill-AB5{fill:#F7F8FE;}
+ .d2-95692537 .stroke-N1{stroke:#0A0F25;}
+ .d2-95692537 .stroke-N2{stroke:#676C7E;}
+ .d2-95692537 .stroke-N3{stroke:#9499AB;}
+ .d2-95692537 .stroke-N4{stroke:#CFD2DD;}
+ .d2-95692537 .stroke-N5{stroke:#DEE1EB;}
+ .d2-95692537 .stroke-N6{stroke:#EEF1F8;}
+ .d2-95692537 .stroke-N7{stroke:#FFFFFF;}
+ .d2-95692537 .stroke-B1{stroke:#0D32B2;}
+ .d2-95692537 .stroke-B2{stroke:#0D32B2;}
+ .d2-95692537 .stroke-B3{stroke:#E3E9FD;}
+ .d2-95692537 .stroke-B4{stroke:#E3E9FD;}
+ .d2-95692537 .stroke-B5{stroke:#EDF0FD;}
+ .d2-95692537 .stroke-B6{stroke:#F7F8FE;}
+ .d2-95692537 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-95692537 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-95692537 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-95692537 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-95692537 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-95692537 .background-color-N1{background-color:#0A0F25;}
+ .d2-95692537 .background-color-N2{background-color:#676C7E;}
+ .d2-95692537 .background-color-N3{background-color:#9499AB;}
+ .d2-95692537 .background-color-N4{background-color:#CFD2DD;}
+ .d2-95692537 .background-color-N5{background-color:#DEE1EB;}
+ .d2-95692537 .background-color-N6{background-color:#EEF1F8;}
+ .d2-95692537 .background-color-N7{background-color:#FFFFFF;}
+ .d2-95692537 .background-color-B1{background-color:#0D32B2;}
+ .d2-95692537 .background-color-B2{background-color:#0D32B2;}
+ .d2-95692537 .background-color-B3{background-color:#E3E9FD;}
+ .d2-95692537 .background-color-B4{background-color:#E3E9FD;}
+ .d2-95692537 .background-color-B5{background-color:#EDF0FD;}
+ .d2-95692537 .background-color-B6{background-color:#F7F8FE;}
+ .d2-95692537 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-95692537 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-95692537 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-95692537 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-95692537 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-95692537 .color-N1{color:#0A0F25;}
+ .d2-95692537 .color-N2{color:#676C7E;}
+ .d2-95692537 .color-N3{color:#9499AB;}
+ .d2-95692537 .color-N4{color:#CFD2DD;}
+ .d2-95692537 .color-N5{color:#DEE1EB;}
+ .d2-95692537 .color-N6{color:#EEF1F8;}
+ .d2-95692537 .color-N7{color:#FFFFFF;}
+ .d2-95692537 .color-B1{color:#0D32B2;}
+ .d2-95692537 .color-B2{color:#0D32B2;}
+ .d2-95692537 .color-B3{color:#E3E9FD;}
+ .d2-95692537 .color-B4{color:#E3E9FD;}
+ .d2-95692537 .color-B5{color:#EDF0FD;}
+ .d2-95692537 .color-B6{color:#F7F8FE;}
+ .d2-95692537 .color-AA2{color:#4A6FF3;}
+ .d2-95692537 .color-AA4{color:#EDF0FD;}
+ .d2-95692537 .color-AA5{color:#F7F8FE;}
+ .d2-95692537 .color-AB4{color:#EDF0FD;}
+ .d2-95692537 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-95692537);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-95692537);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-95692537);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-95692537);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-95692537);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-95692537);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-95692537);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-95692537);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>xyz okay
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/three-down/standard.exp.txt b/e2etests/testdata/asciitxtar/three-down/standard.exp.txt
index d0868b4228..d6f9412640 100644
--- a/e2etests/testdata/asciitxtar/three-down/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/three-down/standard.exp.txt
@@ -1,22 +1,22 @@
- +----------+
- | x |
- | |
- +----------+
- | | |
- +---+ | +--+
- | | |
- | okay |
- | | |
- v v |
-+------+ |
-| y | |
-| | |
-+------+ |
- | |
- | +---+
- | |
- v v
- +------+
- | z |
- | |
- +------+
+ +--------+
+ | x |
+ | |
+ +--------+
+ | | |
++---+ | +---+
+| | |
+| okay |
+| | |
++-+ +-+ |
+ v v |
++----+ |
+| y | |
+| | |
++----+ |
+ | |
+ +--+ +---+
+ v v
+ +----+
+ | z |
+ | |
+ +----+
diff --git a/e2etests/testdata/asciitxtar/three-right/extended.exp.txt b/e2etests/testdata/asciitxtar/three-right/extended.exp.txt
index 770e96827c..e676129699 100644
--- a/e2etests/testdata/asciitxtar/three-right/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/three-right/extended.exp.txt
@@ -1,8 +1,11 @@
-┌────┐ ┌───────────────────▶┌────┐
-│ │ │ │ │
-│ │────┘ │ y │
-│ x │──────────okay──────────▶│ │──────▶┌────┐
-│ │ └────┘ │ z │
-│ │────┐ ┌──▶│ │
-│ │ │ │ └────┘
-└────┘ └─────────────────────────────┘
+┌──┐ ┌───────┐ ┌──┐
+│ │ │ └▶│ │
+│ │─┘ │y │─┐
+│ │ ┌▶│ │ │ ┌──┐
+│x │ │ │ │ └▶│ │
+│ │───okay──┘ └──┘ │z │
+│ │ ┌▶│ │
+│ │─┐ │ │ │
+│ │ │ │ └──┘
+└──┘ │ │
+ └──────────────┘
diff --git a/e2etests/testdata/asciitxtar/three-right/sketch.exp.svg b/e2etests/testdata/asciitxtar/three-right/sketch.exp.svg
index b0ecb4942e..836cbcaa8f 100644
--- a/e2etests/testdata/asciitxtar/three-right/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/three-right/sketch.exp.svg
@@ -1,16 +1,16 @@
-xyz okay
-
-
+ .d2-1229092529 .fill-N1{fill:#0A0F25;}
+ .d2-1229092529 .fill-N2{fill:#676C7E;}
+ .d2-1229092529 .fill-N3{fill:#9499AB;}
+ .d2-1229092529 .fill-N4{fill:#CFD2DD;}
+ .d2-1229092529 .fill-N5{fill:#DEE1EB;}
+ .d2-1229092529 .fill-N6{fill:#EEF1F8;}
+ .d2-1229092529 .fill-N7{fill:#FFFFFF;}
+ .d2-1229092529 .fill-B1{fill:#0D32B2;}
+ .d2-1229092529 .fill-B2{fill:#0D32B2;}
+ .d2-1229092529 .fill-B3{fill:#E3E9FD;}
+ .d2-1229092529 .fill-B4{fill:#E3E9FD;}
+ .d2-1229092529 .fill-B5{fill:#EDF0FD;}
+ .d2-1229092529 .fill-B6{fill:#F7F8FE;}
+ .d2-1229092529 .fill-AA2{fill:#4A6FF3;}
+ .d2-1229092529 .fill-AA4{fill:#EDF0FD;}
+ .d2-1229092529 .fill-AA5{fill:#F7F8FE;}
+ .d2-1229092529 .fill-AB4{fill:#EDF0FD;}
+ .d2-1229092529 .fill-AB5{fill:#F7F8FE;}
+ .d2-1229092529 .stroke-N1{stroke:#0A0F25;}
+ .d2-1229092529 .stroke-N2{stroke:#676C7E;}
+ .d2-1229092529 .stroke-N3{stroke:#9499AB;}
+ .d2-1229092529 .stroke-N4{stroke:#CFD2DD;}
+ .d2-1229092529 .stroke-N5{stroke:#DEE1EB;}
+ .d2-1229092529 .stroke-N6{stroke:#EEF1F8;}
+ .d2-1229092529 .stroke-N7{stroke:#FFFFFF;}
+ .d2-1229092529 .stroke-B1{stroke:#0D32B2;}
+ .d2-1229092529 .stroke-B2{stroke:#0D32B2;}
+ .d2-1229092529 .stroke-B3{stroke:#E3E9FD;}
+ .d2-1229092529 .stroke-B4{stroke:#E3E9FD;}
+ .d2-1229092529 .stroke-B5{stroke:#EDF0FD;}
+ .d2-1229092529 .stroke-B6{stroke:#F7F8FE;}
+ .d2-1229092529 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-1229092529 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-1229092529 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-1229092529 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-1229092529 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-1229092529 .background-color-N1{background-color:#0A0F25;}
+ .d2-1229092529 .background-color-N2{background-color:#676C7E;}
+ .d2-1229092529 .background-color-N3{background-color:#9499AB;}
+ .d2-1229092529 .background-color-N4{background-color:#CFD2DD;}
+ .d2-1229092529 .background-color-N5{background-color:#DEE1EB;}
+ .d2-1229092529 .background-color-N6{background-color:#EEF1F8;}
+ .d2-1229092529 .background-color-N7{background-color:#FFFFFF;}
+ .d2-1229092529 .background-color-B1{background-color:#0D32B2;}
+ .d2-1229092529 .background-color-B2{background-color:#0D32B2;}
+ .d2-1229092529 .background-color-B3{background-color:#E3E9FD;}
+ .d2-1229092529 .background-color-B4{background-color:#E3E9FD;}
+ .d2-1229092529 .background-color-B5{background-color:#EDF0FD;}
+ .d2-1229092529 .background-color-B6{background-color:#F7F8FE;}
+ .d2-1229092529 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-1229092529 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-1229092529 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-1229092529 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-1229092529 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-1229092529 .color-N1{color:#0A0F25;}
+ .d2-1229092529 .color-N2{color:#676C7E;}
+ .d2-1229092529 .color-N3{color:#9499AB;}
+ .d2-1229092529 .color-N4{color:#CFD2DD;}
+ .d2-1229092529 .color-N5{color:#DEE1EB;}
+ .d2-1229092529 .color-N6{color:#EEF1F8;}
+ .d2-1229092529 .color-N7{color:#FFFFFF;}
+ .d2-1229092529 .color-B1{color:#0D32B2;}
+ .d2-1229092529 .color-B2{color:#0D32B2;}
+ .d2-1229092529 .color-B3{color:#E3E9FD;}
+ .d2-1229092529 .color-B4{color:#E3E9FD;}
+ .d2-1229092529 .color-B5{color:#EDF0FD;}
+ .d2-1229092529 .color-B6{color:#F7F8FE;}
+ .d2-1229092529 .color-AA2{color:#4A6FF3;}
+ .d2-1229092529 .color-AA4{color:#EDF0FD;}
+ .d2-1229092529 .color-AA5{color:#F7F8FE;}
+ .d2-1229092529 .color-AB4{color:#EDF0FD;}
+ .d2-1229092529 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1229092529);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1229092529);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1229092529);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1229092529);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1229092529);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1229092529);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1229092529);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1229092529);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>xyz okay
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/three-right/standard.exp.txt b/e2etests/testdata/asciitxtar/three-right/standard.exp.txt
index 7b27398718..749c88fa9d 100644
--- a/e2etests/testdata/asciitxtar/three-right/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/three-right/standard.exp.txt
@@ -1,8 +1,11 @@
-+----+ +------------------->+----+
-| | | | |
-| |----+ | y |
-| x |----------okay---------->| |------>+----+
-| | +----+ | z |
-| |----+ +-->| |
-| | | | +----+
-+----+ +-----------------------------+
++--+ +-------+ +--+
+| | | +>| |
+| |-+ |y |-+
+| | +>| | | +--+
+|x | | | | +>| |
+| |---okay--+ +--+ |z |
+| | +>| |
+| |-+ | | |
+| | | | +--+
++--+ | |
+ +--------------+
diff --git a/e2etests/testdata/asciitxtar/uml-class/extended.exp.txt b/e2etests/testdata/asciitxtar/uml-class/extended.exp.txt
index 803a13c9d2..79d8d23621 100644
--- a/e2etests/testdata/asciitxtar/uml-class/extended.exp.txt
+++ b/e2etests/testdata/asciitxtar/uml-class/extended.exp.txt
@@ -1,26 +1,26 @@
- ┌────┐
- │ x │
- │ │
- └────┘
- │
- ▼
-┌────────────────────────────────────────────────┐
-│ D2 Parser │
-│ │
-├────────────────────────────────────────────────┤
-│+reader io.RuneReader │
-│+readerPos d2ast.Position │
-│-lookahead []rune │
-├────────────────────────────────────────────────┤
-│#peekn(n int) (s string, eof bool) │
-│+peek() (r rune, eof bool) │
-│+rewind() void │
-│+commit() void │
-│ │
-└────────────────────────────────────────────────┘
- │
- ▼
- ┌────┐
- │ y │
- │ │
- └────┘
+ ┌──┐
+ │x │
+ │ │
+ └──┘
+ │
+ ▼
+┌─────────────────────────────────────┐
+│ D2 Parser │
+│ │
+├─────────────────────────────────────┤
+│+reader io.RuneReader │
+│+readerPos d2ast.Position │
+│-lookahead []rune │
+├─────────────────────────────────────┤
+│#peekn(n int) (s string, eof bool) │
+│+peek() (r rune, eof bool) │
+│+rewind() void │
+│+commit() void │
+│ │
+└─────────────────────────────────────┘
+ │
+ ▼
+ ┌──┐
+ │y │
+ │ │
+ └──┘
diff --git a/e2etests/testdata/asciitxtar/uml-class/sketch.exp.svg b/e2etests/testdata/asciitxtar/uml-class/sketch.exp.svg
index 3d3e38a88b..78bb8e028a 100644
--- a/e2etests/testdata/asciitxtar/uml-class/sketch.exp.svg
+++ b/e2etests/testdata/asciitxtar/uml-class/sketch.exp.svg
@@ -1,16 +1,16 @@
-xD2 Parser+readerio.RuneReader+readerPosd2ast.Position-lookahead[]rune#peekn(n int)(s string, eof bool)+peek()(r rune, eof bool)+rewind()void+commit()voidy
-
+ .d2-2387687579 .fill-N1{fill:#0A0F25;}
+ .d2-2387687579 .fill-N2{fill:#676C7E;}
+ .d2-2387687579 .fill-N3{fill:#9499AB;}
+ .d2-2387687579 .fill-N4{fill:#CFD2DD;}
+ .d2-2387687579 .fill-N5{fill:#DEE1EB;}
+ .d2-2387687579 .fill-N6{fill:#EEF1F8;}
+ .d2-2387687579 .fill-N7{fill:#FFFFFF;}
+ .d2-2387687579 .fill-B1{fill:#0D32B2;}
+ .d2-2387687579 .fill-B2{fill:#0D32B2;}
+ .d2-2387687579 .fill-B3{fill:#E3E9FD;}
+ .d2-2387687579 .fill-B4{fill:#E3E9FD;}
+ .d2-2387687579 .fill-B5{fill:#EDF0FD;}
+ .d2-2387687579 .fill-B6{fill:#F7F8FE;}
+ .d2-2387687579 .fill-AA2{fill:#4A6FF3;}
+ .d2-2387687579 .fill-AA4{fill:#EDF0FD;}
+ .d2-2387687579 .fill-AA5{fill:#F7F8FE;}
+ .d2-2387687579 .fill-AB4{fill:#EDF0FD;}
+ .d2-2387687579 .fill-AB5{fill:#F7F8FE;}
+ .d2-2387687579 .stroke-N1{stroke:#0A0F25;}
+ .d2-2387687579 .stroke-N2{stroke:#676C7E;}
+ .d2-2387687579 .stroke-N3{stroke:#9499AB;}
+ .d2-2387687579 .stroke-N4{stroke:#CFD2DD;}
+ .d2-2387687579 .stroke-N5{stroke:#DEE1EB;}
+ .d2-2387687579 .stroke-N6{stroke:#EEF1F8;}
+ .d2-2387687579 .stroke-N7{stroke:#FFFFFF;}
+ .d2-2387687579 .stroke-B1{stroke:#0D32B2;}
+ .d2-2387687579 .stroke-B2{stroke:#0D32B2;}
+ .d2-2387687579 .stroke-B3{stroke:#E3E9FD;}
+ .d2-2387687579 .stroke-B4{stroke:#E3E9FD;}
+ .d2-2387687579 .stroke-B5{stroke:#EDF0FD;}
+ .d2-2387687579 .stroke-B6{stroke:#F7F8FE;}
+ .d2-2387687579 .stroke-AA2{stroke:#4A6FF3;}
+ .d2-2387687579 .stroke-AA4{stroke:#EDF0FD;}
+ .d2-2387687579 .stroke-AA5{stroke:#F7F8FE;}
+ .d2-2387687579 .stroke-AB4{stroke:#EDF0FD;}
+ .d2-2387687579 .stroke-AB5{stroke:#F7F8FE;}
+ .d2-2387687579 .background-color-N1{background-color:#0A0F25;}
+ .d2-2387687579 .background-color-N2{background-color:#676C7E;}
+ .d2-2387687579 .background-color-N3{background-color:#9499AB;}
+ .d2-2387687579 .background-color-N4{background-color:#CFD2DD;}
+ .d2-2387687579 .background-color-N5{background-color:#DEE1EB;}
+ .d2-2387687579 .background-color-N6{background-color:#EEF1F8;}
+ .d2-2387687579 .background-color-N7{background-color:#FFFFFF;}
+ .d2-2387687579 .background-color-B1{background-color:#0D32B2;}
+ .d2-2387687579 .background-color-B2{background-color:#0D32B2;}
+ .d2-2387687579 .background-color-B3{background-color:#E3E9FD;}
+ .d2-2387687579 .background-color-B4{background-color:#E3E9FD;}
+ .d2-2387687579 .background-color-B5{background-color:#EDF0FD;}
+ .d2-2387687579 .background-color-B6{background-color:#F7F8FE;}
+ .d2-2387687579 .background-color-AA2{background-color:#4A6FF3;}
+ .d2-2387687579 .background-color-AA4{background-color:#EDF0FD;}
+ .d2-2387687579 .background-color-AA5{background-color:#F7F8FE;}
+ .d2-2387687579 .background-color-AB4{background-color:#EDF0FD;}
+ .d2-2387687579 .background-color-AB5{background-color:#F7F8FE;}
+ .d2-2387687579 .color-N1{color:#0A0F25;}
+ .d2-2387687579 .color-N2{color:#676C7E;}
+ .d2-2387687579 .color-N3{color:#9499AB;}
+ .d2-2387687579 .color-N4{color:#CFD2DD;}
+ .d2-2387687579 .color-N5{color:#DEE1EB;}
+ .d2-2387687579 .color-N6{color:#EEF1F8;}
+ .d2-2387687579 .color-N7{color:#FFFFFF;}
+ .d2-2387687579 .color-B1{color:#0D32B2;}
+ .d2-2387687579 .color-B2{color:#0D32B2;}
+ .d2-2387687579 .color-B3{color:#E3E9FD;}
+ .d2-2387687579 .color-B4{color:#E3E9FD;}
+ .d2-2387687579 .color-B5{color:#EDF0FD;}
+ .d2-2387687579 .color-B6{color:#F7F8FE;}
+ .d2-2387687579 .color-AA2{color:#4A6FF3;}
+ .d2-2387687579 .color-AA4{color:#EDF0FD;}
+ .d2-2387687579 .color-AA5{color:#F7F8FE;}
+ .d2-2387687579 .color-AB4{color:#EDF0FD;}
+ .d2-2387687579 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2387687579);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2387687579);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2387687579);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2387687579);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2387687579);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2387687579);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2387687579);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2387687579);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>xD2 Parser+readerio.RuneReader+readerPosd2ast.Position-lookahead[]rune#peekn(n int)(s string, eof bool)+peek()(r rune, eof bool)+rewind()void+commit()voidy
+
\ No newline at end of file
diff --git a/e2etests/testdata/asciitxtar/uml-class/standard.exp.txt b/e2etests/testdata/asciitxtar/uml-class/standard.exp.txt
index d0bf7db581..e0ad08bff3 100644
--- a/e2etests/testdata/asciitxtar/uml-class/standard.exp.txt
+++ b/e2etests/testdata/asciitxtar/uml-class/standard.exp.txt
@@ -1,26 +1,26 @@
- +----+
- | x |
- | |
- +----+
- |
- v
-+------------------------------------------------+
-| D2 Parser |
-| |
-+------------------------------------------------+
-|+reader io.RuneReader |
-|+readerPos d2ast.Position |
-|-lookahead []rune |
-+------------------------------------------------+
-|#peekn(n int) (s string, eof bool) |
-|+peek() (r rune, eof bool) |
-|+rewind() void |
-|+commit() void |
-| |
-+------------------------------------------------+
- |
- v
- +----+
- | y |
- | |
- +----+
+ +--+
+ |x |
+ | |
+ +--+
+ |
+ v
++-------------------------------------+
+| D2 Parser |
+| |
++-------------------------------------+
+|+reader io.RuneReader |
+|+readerPos d2ast.Position |
+|-lookahead []rune |
++-------------------------------------+
+|#peekn(n int) (s string, eof bool) |
+|+peek() (r rune, eof bool) |
+|+rewind() void |
+|+commit() void |
+| |
++-------------------------------------+
+ |
+ v
+ +--+
+ |y |
+ | |
+ +--+
diff --git a/lib/textmeasure/textmeasure.go b/lib/textmeasure/textmeasure.go
index 81c8fa5ba1..5711ebe65d 100644
--- a/lib/textmeasure/textmeasure.go
+++ b/lib/textmeasure/textmeasure.go
@@ -101,6 +101,9 @@ type Ruler struct {
// when drawing text also union Ruler.bounds with Dot
boundsWithDot bool
+
+ // isASCII indicates this ruler should use 1x1 measurements for ASCII rendering
+ isASCII bool
}
// New creates a new Ruler capable of drawing runes contained in the provided atlas. Orig and Dot
@@ -155,6 +158,9 @@ func NewRuler() (*Ruler, error) {
}
func (r *Ruler) HasFontFamilyLoaded(fontFamily *d2fonts.FontFamily) bool {
+ if r.isASCII {
+ return true
+ }
for _, fontStyle := range d2fonts.FontStyles {
font := d2fonts.Font{
Family: *fontFamily,
@@ -226,6 +232,9 @@ func (t *Ruler) scaleUnicode(w float64, font d2fonts.Font, s string) float64 {
}
func (t *Ruler) MeasureMono(font d2fonts.Font, s string) (width, height int) {
+ if t.isASCII {
+ return t.measureASCII(s)
+ }
originalBoundsWithDot := t.boundsWithDot
t.boundsWithDot = true
width, height = t.Measure(font, s)
@@ -234,12 +243,19 @@ func (t *Ruler) MeasureMono(font d2fonts.Font, s string) (width, height int) {
}
func (t *Ruler) Measure(font d2fonts.Font, s string) (width, height int) {
+ if t.isASCII {
+ return t.measureASCII(s)
+ }
w, h := t.MeasurePrecise(font, s)
w = t.scaleUnicode(w, font, s)
return int(math.Ceil(w)), int(math.Ceil(h))
}
func (t *Ruler) MeasurePrecise(font d2fonts.Font, s string) (width, height float64) {
+ if t.isASCII {
+ w, h := t.measureASCII(s)
+ return float64(w), float64(h)
+ }
if _, ok := t.atlases[font]; !ok {
t.addFontSize(font)
}
@@ -319,3 +335,48 @@ func (ruler *Ruler) spaceWidth(font d2fonts.Font) float64 {
spaceRune, _ := utf8.DecodeRuneInString(" ")
return ruler.atlases[font].glyph(spaceRune).advance
}
+
+// NewASCIIRuler creates a fake ruler for ASCII rendering that measures each character as 1x1
+func NewASCIIRuler() (*Ruler, error) {
+ origin := geo.NewPoint(0, 0)
+ return &Ruler{
+ Orig: origin,
+ Dot: origin.Copy(),
+ LineHeightFactor: 1.,
+ lineHeights: make(map[d2fonts.Font]float64),
+ tabWidths: make(map[d2fonts.Font]float64),
+ atlases: make(map[d2fonts.Font]*atlas),
+ ttfs: make(map[d2fonts.Font]*truetype.Font),
+ isASCII: true,
+ }, nil
+}
+
+// measureASCII returns 1x1 measurement for each character in ASCII rendering
+func (r *Ruler) measureASCII(s string) (width, height int) {
+ if s == "" {
+ return 0, 0
+ }
+
+ lines := strings.Split(s, "\n")
+ maxWidth := 0
+ for _, line := range lines {
+ lineWidth := 0
+ for _, ch := range line {
+ if ch == '\t' {
+ lineWidth += TAB_SIZE
+ } else {
+ lineWidth++
+ }
+ }
+ if lineWidth > maxWidth {
+ maxWidth = lineWidth
+ }
+ }
+
+ return maxWidth, len(lines)
+}
+
+// IsASCII returns true if this ruler is in ASCII mode
+func (r *Ruler) IsASCII() bool {
+ return r.isASCII
+}