diff --git a/d2cli/main.go b/d2cli/main.go index 1330b7de83..bb4eec08d7 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -912,7 +912,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts Charset: charsetType, } asciiArtist := d2ascii.NewASCIIartist() - ascii, err := asciiArtist.Render(diagram, renderOpts) + ascii, err := asciiArtist.Render(ctx, diagram, renderOpts) if err != nil { return ascii, err } diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go index 61f2e1eb76..57bf87e725 100644 --- a/d2js/d2wasm/functions.go +++ b/d2js/d2wasm/functions.go @@ -416,6 +416,7 @@ func Render(args []js.Value) (interface{}, error) { return nil, &WASMError{Message: "ASCII rendering does not support multi-board targets", Code: 400} } + ctx := log.WithDefault(context.Background()) artist := d2ascii.NewASCIIartist() asciiOpts := &d2ascii.RenderOpts{} if input.Opts.Scale != nil { @@ -435,7 +436,7 @@ func Render(args []js.Value) (interface{}, error) { } asciiOpts.Charset = charsetType - out, err := artist.Render(diagram, asciiOpts) + out, err := artist.Render(ctx, diagram, asciiOpts) if err != nil { return nil, &WASMError{Message: fmt.Sprintf("ASCII render failed: %s", err.Error()), Code: 500} } diff --git a/d2js/js/package-lock.json b/d2js/js/package-lock.json index 2e51dc3c6d..3aea3623de 100644 --- a/d2js/js/package-lock.json +++ b/d2js/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@terrastruct/d2", - "version": "0.1.32", + "version": "0.1.33", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@terrastruct/d2", - "version": "0.1.32", + "version": "0.1.33", "license": "MPL-2.0", "dependencies": { "pako": "^2.1.0" diff --git a/d2js/js/package.json b/d2js/js/package.json index d0166d0373..fd2f24e00a 100644 --- a/d2js/js/package.json +++ b/d2js/js/package.json @@ -2,7 +2,7 @@ "name": "@terrastruct/d2", "author": "Terrastruct, Inc.", "description": "D2.js is a wrapper around the WASM build of D2, the modern text-to-diagram language.", - "version": "0.1.32", + "version": "0.1.33", "repository": { "type": "git", "url": "git+https://github.com/terrastruct/d2.git", diff --git a/d2renderers/d2ascii/asciiroute/asciiroute.go b/d2renderers/d2ascii/asciiroute/asciiroute.go index 493951a4b4..7f83cc5a8c 100644 --- a/d2renderers/d2ascii/asciiroute/asciiroute.go +++ b/d2renderers/d2ascii/asciiroute/asciiroute.go @@ -1,13 +1,15 @@ package asciiroute import ( - "fmt" + "context" + "log/slog" "math" "strings" "oss.terrastruct.com/d2/d2renderers/d2ascii/asciicanvas" "oss.terrastruct.com/d2/d2renderers/d2ascii/charset" "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/log" ) const ( @@ -45,52 +47,47 @@ type RouteDrawer interface { GetScale() float64 GetBoundaryForShape(s d2target.Shape) (Point, Point) CalibrateXY(x, y float64) (float64, float64) + GetContext() context.Context } func DrawRoute(rd RouteDrawer, conn d2target.Connection) { routes := conn.Route label := conn.Label + ctx := rd.GetContext() - fmt.Printf("[D2ASCII] Starting edge route for connection %s -> %s\n", conn.Src, conn.Dst) - fmt.Printf("[D2ASCII] Initial route points (%d points):\n", len(routes)) + log.Debug(ctx, "starting edge route", slog.String("src", conn.Src), slog.String("dst", conn.Dst)) + log.Debug(ctx, "initial route points", slog.Int("count", len(routes))) for i, pt := range routes { - fmt.Printf("[D2ASCII] Point %d: (%.2f, %.2f)\n", i, pt.X, pt.Y) + log.Debug(ctx, "route point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y)) } frmShapeBoundary, toShapeBoundary := getConnectionBoundaries(rd, conn.Src, conn.Dst) - fmt.Printf("[D2ASCII] Source boundary: TL(%d,%d) BR(%d,%d)\n", - frmShapeBoundary.TL.X, frmShapeBoundary.TL.Y, - frmShapeBoundary.BR.X, frmShapeBoundary.BR.Y) - fmt.Printf("[D2ASCII] Dest boundary: TL(%d,%d) BR(%d,%d)\n", - toShapeBoundary.TL.X, toShapeBoundary.TL.Y, - toShapeBoundary.BR.X, toShapeBoundary.BR.Y) + 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)) - routes = processRoute(rd, routes, frmShapeBoundary, toShapeBoundary) + routes = processRoute(ctx, rd, routes, frmShapeBoundary, toShapeBoundary) turnDir := calculateTurnDirections(routes) - fmt.Printf("[D2ASCII] Turn directions calculated: %d turns\n", len(turnDir)) + log.Debug(ctx, "turn directions calculated", slog.Int("count", len(turnDir))) for key, dir := range turnDir { - fmt.Printf("[D2ASCII] Turn at %s: direction %s\n", key, dir) + log.Debug(ctx, "turn direction", slog.String("key", key), slog.String("dir", dir)) } var labelPos *RouteLabelPosition if strings.TrimSpace(label) != "" { labelPos = calculateBestLabelPosition(rd, routes, label) if labelPos != nil { - fmt.Printf("[D2ASCII] Label position calculated: segment %d, pos (%d, %d), maxDiff %.2f\n", - labelPos.I, labelPos.X, labelPos.Y, labelPos.MaxDiff) + log.Debug(ctx, "label position calculated", slog.Int("segmentIndex", labelPos.I), slog.Int("x", labelPos.X), slog.Int("y", labelPos.Y), slog.Float64("maxDiff", labelPos.MaxDiff)) } } corners, arrows := getCharacterMaps(rd) - fmt.Printf("[D2ASCII] Drawing %d segments\n", len(routes)-1) + log.Debug(ctx, "drawing segments", slog.Int("count", len(routes)-1)) for i := 1; i < len(routes); i++ { - fmt.Printf("[D2ASCII] Drawing segment %d: (%.2f,%.2f) -> (%.2f,%.2f)\n", - i-1, routes[i-1].X, routes[i-1].Y, routes[i].X, routes[i].Y) - drawSegmentBetweenPoints(rd, routes[i-1], routes[i], i, conn, corners, arrows, turnDir, frmShapeBoundary, toShapeBoundary, labelPos, label) + log.Debug(ctx, "drawing segment", slog.Int("index", i-1), slog.Float64("x1", routes[i-1].X), slog.Float64("y1", routes[i-1].Y), slog.Float64("x2", routes[i].X), slog.Float64("y2", routes[i].Y)) + drawSegmentBetweenPoints(ctx, rd, routes[i-1], routes[i], i, conn, corners, arrows, turnDir, frmShapeBoundary, toShapeBoundary, labelPos, label) } - fmt.Printf("[D2ASCII] Edge route completed for %s -> %s\n", conn.Src, conn.Dst) + log.Debug(ctx, "edge route completed", slog.String("src", conn.Src), slog.String("dst", conn.Dst)) } func getCharacterMaps(rd RouteDrawer) (corners, arrows map[string]string) { diff --git a/d2renderers/d2ascii/asciiroute/drawing.go b/d2renderers/d2ascii/asciiroute/drawing.go index 2c5ce8c1cf..4ccb786785 100644 --- a/d2renderers/d2ascii/asciiroute/drawing.go +++ b/d2renderers/d2ascii/asciiroute/drawing.go @@ -1,33 +1,35 @@ package asciiroute import ( + "context" "fmt" + "log/slog" "math" "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/log" ) -func drawSegmentBetweenPoints(rd RouteDrawer, start, end *geo.Point, segmentIndex int, conn d2target.Connection, +func drawSegmentBetweenPoints(ctx context.Context, rd RouteDrawer, start, end *geo.Point, segmentIndex int, conn d2target.Connection, corners, arrows, turnDir map[string]string, frmBoundary, toBoundary Boundary, labelPos *RouteLabelPosition, label string) { ax, ay := start.X, start.Y cx, cy := end.X, end.Y - fmt.Printf("[D2ASCII] Drawing segment %d: (%.2f,%.2f) -> (%.2f,%.2f)\n", - segmentIndex-1, ax, ay, cx, cy) + log.Debug(ctx, "drawing segment", slog.Int("index", segmentIndex-1), slog.Float64("x1", ax), slog.Float64("y1", ay), slog.Float64("x2", cx), slog.Float64("y2", cy)) sx := cx - ax sy := cy - ay step := math.Max(math.Abs(sx), math.Abs(sy)) if step == 0 { - fmt.Printf("[D2ASCII] Zero-length segment, skipping\n") + log.Debug(ctx, "zero-length segment, skipping") return } sx /= step sy /= step - fmt.Printf("[D2ASCII] Step vector: (%.2f, %.2f), total steps: %.0f\n", sx, sy, step) + log.Debug(ctx, "step vector", slog.Float64("x", sx), slog.Float64("y", sy), slog.Float64("steps", step)) fx, fy := ax, ay attempt := 0 @@ -38,9 +40,9 @@ func drawSegmentBetweenPoints(rd RouteDrawer, start, end *geo.Point, segmentInde attempt++ if x == int(math.Round(cx)) && y == int(math.Round(cy)) || attempt == MaxRouteAttempts { if attempt == MaxRouteAttempts { - fmt.Printf("[D2ASCII] Max route attempts (%d) reached\n", MaxRouteAttempts) + log.Debug(ctx, "max route attempts reached", slog.Int("attempts", MaxRouteAttempts)) } else { - fmt.Printf("[D2ASCII] Reached segment endpoint at (%d, %d)\n", x, y) + log.Debug(ctx, "reached segment endpoint", slog.Int("x", x), slog.Int("y", y)) } break } @@ -49,13 +51,13 @@ func drawSegmentBetweenPoints(rd RouteDrawer, start, end *geo.Point, segmentInde // Skip if out of bounds or contains alphanumeric character if !isInBounds(rd, x, y) { - fmt.Printf("[D2ASCII] Position (%d, %d) out of bounds, skipping\n", x, y) + log.Debug(ctx, "position out of bounds, skipping", slog.Int("x", x), slog.Int("y", y)) fx += sx fy += sy continue } if containsAlphaNumeric(rd, x, y) { - fmt.Printf("[D2ASCII] Position (%d, %d) contains alphanumeric, skipping\n", x, y) + log.Debug(ctx, "position contains alphanumeric, skipping", slog.Int("x", x), slog.Int("y", y)) fx += sx fy += sy continue @@ -85,17 +87,17 @@ func drawRoutePoint(rd RouteDrawer, x, y int, sx, sy float64, segmentIndex, rout // Check for corners first if char, ok := corners[turnDir[key]]; ok { - fmt.Printf("[D2ASCII] Drawing corner at (%d, %d): '%s' (direction: %s)\n", x, y, char, turnDir[key]) + log.Debug(rd.GetContext(), "drawing corner", slog.Int("x", x), slog.Int("y", y), slog.String("char", char), slog.String("direction", turnDir[key])) canvas.Set(x, y, char) return } // Check for destination arrow if segmentIndex == routeLen-1 && x == int(math.Round(cx)) && y == int(math.Round(cy)) && conn.DstArrow != d2target.NoArrowhead { - fmt.Printf("[D2ASCII] Drawing destination arrow at (%d, %d)\n", x, y) + log.Debug(rd.GetContext(), "drawing destination arrow", slog.Int("x", x), slog.Int("y", y)) drawArrowhead(rd, x, y, sx, sy, arrows) if conn.DstLabel != nil { - fmt.Printf("[D2ASCII] Drawing destination label: %s\n", conn.DstLabel.Label) + log.Debug(rd.GetContext(), "drawing destination label", slog.String("label", conn.DstLabel.Label)) drawDestinationLabel(rd, conn.DstLabel.Label, cx, cy, sx, sy) } return @@ -103,23 +105,22 @@ func drawRoutePoint(rd RouteDrawer, x, y int, sx, sy float64, segmentIndex, rout // Check for source arrow if segmentIndex == 1 && x == int(math.Round(ax)) && y == int(math.Round(ay)) && conn.SrcArrow != d2target.NoArrowhead { - fmt.Printf("[D2ASCII] Drawing source arrow at (%d, %d)\n", x, y) + log.Debug(rd.GetContext(), "drawing source arrow", slog.Int("x", x), slog.Int("y", y)) arrowKey := fmt.Sprintf("%d%d", geo.Sign(sx)*-1, geo.Sign(sy)*-1) canvas.Set(x, y, arrows[arrowKey]) if conn.SrcLabel != nil { - fmt.Printf("[D2ASCII] Drawing source label: %s\n", conn.SrcLabel.Label) + log.Debug(rd.GetContext(), "drawing source label", slog.String("label", conn.SrcLabel.Label)) drawSourceLabel(rd, conn.SrcLabel.Label, ax, cy, cx, sx, sy) } return } // Default: draw route segment - fmt.Printf("[D2ASCII] Drawing route segment at (%d, %d), existing: '%s'\n", - x, y, existingChar) - drawRouteSegment(rd, x, y, sx, sy, frmBoundary, toBoundary) + log.Debug(rd.GetContext(), "drawing route segment", slog.Int("x", x), slog.Int("y", y), slog.String("existing", string(existingChar))) + drawRouteSegment(rd.GetContext(), rd, x, y, sx, sy, frmBoundary, toBoundary) } -func drawRouteSegment(rd RouteDrawer, x, y int, sx, sy float64, frmBoundary, toBoundary Boundary) { +func drawRouteSegment(ctx context.Context, rd RouteDrawer, x, y int, sx, sy float64, frmBoundary, toBoundary Boundary) { if !isInBounds(rd, x, y) { return } @@ -129,56 +130,53 @@ func drawRouteSegment(rd RouteDrawer, x, y int, sx, sy float64, frmBoundary, toB overWrite := existingChar != " " if sx == 0 { // Vertical line - fmt.Printf("[D2ASCII] Drawing vertical segment at (%d, %d), overwrite=%t, existing='%s'\n", - x, y, overWrite, existingChar) - drawVerticalSegment(rd, x, y, sy, overWrite, frmBoundary, toBoundary) + log.Debug(ctx, "drawing vertical segment", slog.Int("x", x), slog.Int("y", y), slog.Bool("overwrite", overWrite), slog.String("existing", string(existingChar))) + drawVerticalSegment(ctx, rd, x, y, sy, overWrite, frmBoundary, toBoundary) } else { // Horizontal line - fmt.Printf("[D2ASCII] Drawing horizontal segment at (%d, %d), overwrite=%t, existing='%s'\n", - x, y, overWrite, existingChar) - drawHorizontalSegment(rd, x, y, sx, overWrite, frmBoundary, toBoundary) + log.Debug(ctx, "drawing horizontal segment", slog.Int("x", x), slog.Int("y", y), slog.Bool("overwrite", overWrite), slog.String("existing", string(existingChar))) + drawHorizontalSegment(ctx, rd, x, y, sx, overWrite, frmBoundary, toBoundary) } newChar := canvas.Get(x, y) if newChar != existingChar { - fmt.Printf("[D2ASCII] Character placed: '%s' -> '%s' at (%d, %d)\n", - existingChar, newChar, x, y) + log.Debug(ctx, "character placed", slog.String("from", string(existingChar)), slog.String("to", string(newChar)), slog.Int("x", x), slog.Int("y", y)) } } -func drawVerticalSegment(rd RouteDrawer, x, y int, sy float64, overWrite bool, frmBoundary, toBoundary Boundary) { +func drawVerticalSegment(ctx context.Context, rd RouteDrawer, x, y int, sy float64, overWrite bool, frmBoundary, toBoundary Boundary) { canvas := rd.GetCanvas() chars := rd.GetChars() if overWrite && shouldDrawTJunction(rd, x, y, frmBoundary, toBoundary, true) { if sy > 0 { - fmt.Printf("[D2ASCII] Drawing T-junction down at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing T-junction down", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.TDown()) } else { - fmt.Printf("[D2ASCII] Drawing T-junction up at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing T-junction up", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.TUp()) } } else if overWrite && shouldSkipOverwrite(rd, x, y, frmBoundary, toBoundary) { - fmt.Printf("[D2ASCII] Skipping overwrite at (%d, %d)\n", x, y) + log.Debug(ctx, "skipping overwrite", slog.Int("x", x), slog.Int("y", y)) } else { - fmt.Printf("[D2ASCII] Drawing vertical line at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing vertical line", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.Vertical()) } } -func drawHorizontalSegment(rd RouteDrawer, x, y int, sx float64, overWrite bool, frmBoundary, toBoundary Boundary) { +func drawHorizontalSegment(ctx context.Context, rd RouteDrawer, x, y int, sx float64, overWrite bool, frmBoundary, toBoundary Boundary) { canvas := rd.GetCanvas() chars := rd.GetChars() if overWrite && shouldDrawTJunction(rd, x, y, frmBoundary, toBoundary, false) { if sx > 0 { - fmt.Printf("[D2ASCII] Drawing T-junction right at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing T-junction right", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.TRight()) } else { - fmt.Printf("[D2ASCII] Drawing T-junction left at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing T-junction left", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.TLeft()) } } else { - fmt.Printf("[D2ASCII] Drawing horizontal line at (%d, %d)\n", x, y) + log.Debug(ctx, "drawing horizontal line", slog.Int("x", x), slog.Int("y", y)) canvas.Set(x, y, chars.Horizontal()) } } diff --git a/d2renderers/d2ascii/asciiroute/routing.go b/d2renderers/d2ascii/asciiroute/routing.go index a14c05dc62..113d8147b4 100644 --- a/d2renderers/d2ascii/asciiroute/routing.go +++ b/d2renderers/d2ascii/asciiroute/routing.go @@ -1,14 +1,17 @@ package asciiroute import ( + "context" "fmt" + "log/slog" "math" "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/log" ) -func processRoute(rd RouteDrawer, routes []*geo.Point, fromBoundary, toBoundary Boundary) []*geo.Point { - fmt.Printf("[D2ASCII] Processing route with %d points\n", len(routes)) +func processRoute(ctx context.Context, rd RouteDrawer, routes []*geo.Point, fromBoundary, toBoundary Boundary) []*geo.Point { + log.Debug(ctx, "processing route", slog.Int("points", len(routes))) // Create a deep copy of routes to avoid modifying the original routesCopy := make([]*geo.Point, len(routes)) @@ -16,46 +19,46 @@ func processRoute(rd RouteDrawer, routes []*geo.Point, fromBoundary, toBoundary routesCopy[i] = &geo.Point{X: pt.X, Y: pt.Y} } - fmt.Printf("[D2ASCII] Step 1: Merging collinear route segments\n") + log.Debug(ctx, "step 1: merging collinear route segments") beforeMerge := len(routesCopy) routesCopy = mergeRoutes(routesCopy) - fmt.Printf("[D2ASCII] Merged from %d to %d points\n", beforeMerge, len(routesCopy)) + log.Debug(ctx, "merged points", slog.Int("before", beforeMerge), slog.Int("after", len(routesCopy))) for i, pt := range routesCopy { - fmt.Printf("[D2ASCII] After merge point %d: (%.2f, %.2f)\n", i, pt.X, pt.Y) + log.Debug(ctx, "after merge point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y)) } - fmt.Printf("[D2ASCII] Step 2: Calibrating coordinates to ASCII grid\n") - calibrateRoutes(rd, routesCopy) + log.Debug(ctx, "step 2: calibrating coordinates to ASCII grid") + calibrateRoutes(ctx, rd, routesCopy) for i, pt := range routesCopy { - fmt.Printf("[D2ASCII] Calibrated point %d: (%.2f, %.2f)\n", i, pt.X, pt.Y) + log.Debug(ctx, "calibrated point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y)) } // Force all route segments to be horizontal or vertical (after calibration) - fmt.Printf("[D2ASCII] Step 3: Forcing horizontal/vertical segments\n") + log.Debug(ctx, "step 3: forcing horizontal/vertical segments") beforeForce := len(routesCopy) routesCopy = forceHorizontalVerticalRoute(routesCopy) - fmt.Printf("[D2ASCII] Adjusted from %d to %d points\n", beforeForce, len(routesCopy)) + log.Debug(ctx, "adjusted points", slog.Int("before", beforeForce), slog.Int("after", len(routesCopy))) for i, pt := range routesCopy { - fmt.Printf("[D2ASCII] After H/V force point %d: (%.2f, %.2f)\n", i, pt.X, pt.Y) + log.Debug(ctx, "after h/v force point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y)) } // Adjust route endpoints to avoid overlapping with existing characters if len(routesCopy) >= 2 { - fmt.Printf("[D2ASCII] Step 4: Adjusting start point to avoid overlaps\n") + log.Debug(ctx, "step 4: adjusting start point to avoid overlaps") startBefore := fmt.Sprintf("(%.2f, %.2f)", routesCopy[0].X, routesCopy[0].Y) - adjustRouteStartPoint(rd, routesCopy, fromBoundary) - fmt.Printf("[D2ASCII] Start point: %s -> (%.2f, %.2f)\n", startBefore, routesCopy[0].X, routesCopy[0].Y) + adjustRouteStartPoint(ctx, rd, routesCopy, fromBoundary) + log.Debug(ctx, "start point adjusted", slog.String("before", startBefore), slog.Float64("afterX", routesCopy[0].X), slog.Float64("afterY", routesCopy[0].Y)) - fmt.Printf("[D2ASCII] Step 5: Adjusting end point to avoid overlaps\n") + log.Debug(ctx, "step 5: adjusting end point to avoid overlaps") endIdx := len(routesCopy) - 1 endBefore := fmt.Sprintf("(%.2f, %.2f)", routesCopy[endIdx].X, routesCopy[endIdx].Y) - routesCopy = adjustRouteEndPoint(rd, routesCopy, toBoundary) - fmt.Printf("[D2ASCII] End point: %s -> (%.2f, %.2f)\n", endBefore, routesCopy[endIdx].X, routesCopy[endIdx].Y) + routesCopy = adjustRouteEndPoint(ctx, rd, routesCopy, toBoundary) + log.Debug(ctx, "end point adjusted", slog.String("before", endBefore), slog.Float64("afterX", routesCopy[endIdx].X), slog.Float64("afterY", routesCopy[endIdx].Y)) } - fmt.Printf("[D2ASCII] Final processed route (%d points):\n", len(routesCopy)) + log.Debug(ctx, "final processed route", slog.Int("points", len(routesCopy))) for i, pt := range routesCopy { - fmt.Printf("[D2ASCII] Final point %d: (%.2f, %.2f)\n", i, pt.X, pt.Y) + log.Debug(ctx, "final point", slog.Int("index", i), slog.Float64("x", pt.X), slog.Float64("y", pt.Y)) } return routesCopy @@ -76,15 +79,12 @@ func forceHorizontalVerticalRoute(routes []*geo.Point) []*geo.Point { deltaY := math.Abs(curr.Y - prev.Y) if deltaX > 0.5 && deltaY > 0.5 { - fmt.Printf("[D2ASCII] Found diagonal segment %d: (%.2f,%.2f) -> (%.2f,%.2f), deltaX=%.2f, deltaY=%.2f\n", - i-1, prev.X, prev.Y, curr.X, curr.Y, deltaX, deltaY) hasDiagonals = true break } } if !hasDiagonals { - fmt.Printf("[D2ASCII] No diagonal segments found, keeping route as-is\n") return routes } @@ -101,10 +101,6 @@ func forceHorizontalVerticalRoute(routes []*geo.Point) []*geo.Point { if deltaX > 0.5 && deltaY > 0.5 { // Break diagonal into horizontal then vertical intermediate := &geo.Point{X: curr.X, Y: prev.Y} - fmt.Printf("[D2ASCII] Breaking diagonal: (%.2f,%.2f) -> (%.2f,%.2f) into H: (%.2f,%.2f) -> (%.2f,%.2f) and V: (%.2f,%.2f) -> (%.2f,%.2f)\n", - prev.X, prev.Y, curr.X, curr.Y, - prev.X, prev.Y, intermediate.X, intermediate.Y, - intermediate.X, intermediate.Y, curr.X, curr.Y) newRoutes = append(newRoutes, intermediate) } @@ -130,12 +126,11 @@ func getConnectionBoundaries(rd RouteDrawer, srcID, dstID string) (frmShapeBound return } -func calibrateRoutes(rd RouteDrawer, routes []*geo.Point) { +func calibrateRoutes(ctx context.Context, rd RouteDrawer, routes []*geo.Point) { for i := range routes { origX, origY := routes[i].X, routes[i].Y routes[i].X, routes[i].Y = rd.CalibrateXY(routes[i].X, routes[i].Y) - fmt.Printf("[D2ASCII] Calibrate point %d: (%.2f, %.2f) -> (%.2f, %.2f)\n", - i, origX, origY, routes[i].X, routes[i].Y) + log.Debug(ctx, "calibrate point", slog.Int("index", i), slog.Float64("origX", origX), slog.Float64("origY", origY), slog.Float64("newX", routes[i].X), slog.Float64("newY", routes[i].Y)) } } @@ -182,7 +177,7 @@ func calculateTurnDirections(routes []*geo.Point) map[string]string { return turnDir } -func adjustRouteStartPoint(rd RouteDrawer, routes []*geo.Point, fromBoundary Boundary) { +func adjustRouteStartPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Point, fromBoundary Boundary) { if len(routes) < 2 { return } @@ -192,13 +187,12 @@ func adjustRouteStartPoint(rd RouteDrawer, routes []*geo.Point, fromBoundary Bou secondX := routes[1].X secondY := routes[1].Y - fmt.Printf("[D2ASCII] Adjusting start point: (%.2f, %.2f) -> (%.2f, %.2f)\n", - firstX, firstY, secondX, secondY) + log.Debug(ctx, "adjusting start point", slog.Float64("firstX", firstX), slog.Float64("firstY", firstY), slog.Float64("secondX", secondX), slog.Float64("secondY", 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))) { - fmt.Printf("[D2ASCII] Start point inside source boundary, moving along vector\n") + log.Debug(ctx, "start point inside source boundary, moving along vector") vectorX := secondX - firstX vectorY := secondY - firstY @@ -206,7 +200,7 @@ func adjustRouteStartPoint(rd RouteDrawer, routes []*geo.Point, fromBoundary Bou if length > 0 { vectorX /= length vectorY /= length - fmt.Printf("[D2ASCII] Movement vector: (%.2f, %.2f)\n", vectorX, vectorY) + log.Debug(ctx, "movement vector", slog.Float64("x", vectorX), slog.Float64("y", vectorY)) steps := 0 for fromBoundary.Contains(int(math.Round(routes[0].X)), int(math.Round(routes[0].Y))) { @@ -214,45 +208,44 @@ func adjustRouteStartPoint(rd RouteDrawer, routes []*geo.Point, fromBoundary Bou routes[0].Y += vectorY steps++ } - fmt.Printf("[D2ASCII] Moved %d steps to exit boundary: (%.2f, %.2f)\n", - steps, routes[0].X, routes[0].Y) + log.Debug(ctx, "moved to exit boundary", slog.Int("steps", steps), slog.Float64("x", routes[0].X), slog.Float64("y", routes[0].Y)) } return } // Determine line direction and keep shifting until empty space if math.Abs(firstY-secondY) < 0.1 { // Horizontal line - fmt.Printf("[D2ASCII] Horizontal line detected\n") + log.Debug(ctx, "horizontal line detected") deltaX := 0.0 if secondX > firstX { deltaX = 1.0 // Shift start point towards second point (right) - fmt.Printf("[D2ASCII] Shifting start point right\n") + log.Debug(ctx, "shifting start point right") } else if secondX < firstX { deltaX = -1.0 // Shift start point towards second point (left) - fmt.Printf("[D2ASCII] Shifting start point left\n") + log.Debug(ctx, "shifting start point left") } if deltaX != 0 { - shiftPointUntilEmpty(rd, &routes[0].X, &routes[0].Y, deltaX, 0) + shiftPointUntilEmpty(ctx, rd, &routes[0].X, &routes[0].Y, deltaX, 0) } } else if math.Abs(firstX-secondX) < 0.1 { // Vertical line - fmt.Printf("[D2ASCII] Vertical line detected\n") + log.Debug(ctx, "vertical line detected") deltaY := 0.0 if secondY > firstY { deltaY = 1.0 // Shift start point towards second point (down) - fmt.Printf("[D2ASCII] Shifting start point down\n") + log.Debug(ctx, "shifting start point down") } else if secondY < firstY { deltaY = -1.0 // Shift start point towards second point (up) - fmt.Printf("[D2ASCII] Shifting start point up\n") + log.Debug(ctx, "shifting start point up") } if deltaY != 0 { - shiftPointUntilEmpty(rd, &routes[0].X, &routes[0].Y, 0, deltaY) + shiftPointUntilEmpty(ctx, rd, &routes[0].X, &routes[0].Y, 0, deltaY) } } } -func adjustRouteEndPoint(rd RouteDrawer, routes []*geo.Point, toBoundary Boundary) []*geo.Point { +func adjustRouteEndPoint(ctx context.Context, rd RouteDrawer, routes []*geo.Point, toBoundary Boundary) []*geo.Point { if len(routes) < 2 { return routes } @@ -265,8 +258,7 @@ func adjustRouteEndPoint(rd RouteDrawer, routes []*geo.Point, toBoundary Boundar secondLastX := routes[secondLastIdx].X secondLastY := routes[secondLastIdx].Y - fmt.Printf("[D2ASCII] Adjusting end point: (%.2f, %.2f) <- (%.2f, %.2f)\n", - lastX, lastY, secondLastX, secondLastY) + log.Debug(ctx, "adjusting end point", slog.Float64("lastX", lastX), slog.Float64("lastY", lastY), slog.Float64("secondLastX", secondLastX), slog.Float64("secondLastY", secondLastY)) lastXInt := int(math.Round(lastX)) lastYInt := int(math.Round(lastY)) @@ -274,7 +266,7 @@ func adjustRouteEndPoint(rd RouteDrawer, routes []*geo.Point, toBoundary Boundar // Check if end point is inside the to boundary // Move along the vector of the last segment until outside the boundary if so if toBoundary.Contains(lastXInt, lastYInt) { - fmt.Printf("[D2ASCII] End point inside dest boundary, moving along vector\n") + log.Debug(ctx, "end point inside dest boundary, moving along vector") vectorX := lastX - secondLastX vectorY := lastY - secondLastY @@ -282,7 +274,7 @@ func adjustRouteEndPoint(rd RouteDrawer, routes []*geo.Point, toBoundary Boundar if length > 0 { vectorX /= length vectorY /= length - fmt.Printf("[D2ASCII] Movement vector: (%.2f, %.2f)\n", vectorX, vectorY) + log.Debug(ctx, "movement vector", slog.Float64("x", vectorX), slog.Float64("y", vectorY)) steps := 0 for toBoundary.Contains(int(math.Round(routes[lastIdx].X)), int(math.Round(routes[lastIdx].Y))) { @@ -290,47 +282,46 @@ func adjustRouteEndPoint(rd RouteDrawer, routes []*geo.Point, toBoundary Boundar routes[lastIdx].Y -= vectorY steps++ } - fmt.Printf("[D2ASCII] Moved %d steps to exit boundary: (%.2f, %.2f)\n", - steps, routes[lastIdx].X, routes[lastIdx].Y) + log.Debug(ctx, "moved to exit boundary", slog.Int("steps", steps), slog.Float64("x", routes[lastIdx].X), slog.Float64("y", routes[lastIdx].Y)) } return routes } // Determine line direction and keep shifting until empty space if math.Abs(lastY-secondLastY) < 0.1 { // Horizontal line - fmt.Printf("[D2ASCII] Horizontal line detected\n") + log.Debug(ctx, "horizontal line detected") deltaX := 0.0 if secondLastX > lastX { deltaX = 1.0 // Shift end point towards second-to-last point (right) - fmt.Printf("[D2ASCII] Shifting end point right\n") + log.Debug(ctx, "shifting end point right") } else if secondLastX < lastX { deltaX = -1.0 // Shift end point towards second-to-last point (left) - fmt.Printf("[D2ASCII] Shifting end point left\n") + log.Debug(ctx, "shifting end point left") } if deltaX != 0 { - shiftPointUntilEmpty(rd, &routes[lastIdx].X, &routes[lastIdx].Y, deltaX, 0) + shiftPointUntilEmpty(ctx, rd, &routes[lastIdx].X, &routes[lastIdx].Y, deltaX, 0) } } else if math.Abs(lastX-secondLastX) < 0.1 { // Vertical line - fmt.Printf("[D2ASCII] Vertical line detected\n") + log.Debug(ctx, "vertical line detected") deltaY := 0.0 if secondLastY > lastY { deltaY = 1.0 // Shift end point towards second-to-last point (down) - fmt.Printf("[D2ASCII] Shifting end point down\n") + log.Debug(ctx, "shifting end point down") } else if secondLastY < lastY { deltaY = -1.0 // Shift end point towards second-to-last point (up) - fmt.Printf("[D2ASCII] Shifting end point up\n") + log.Debug(ctx, "shifting end point up") } if deltaY != 0 { - shiftPointUntilEmpty(rd, &routes[lastIdx].X, &routes[lastIdx].Y, 0, deltaY) + shiftPointUntilEmpty(ctx, rd, &routes[lastIdx].X, &routes[lastIdx].Y, 0, deltaY) } } return routes } -func shiftPointUntilEmpty(rd RouteDrawer, x, y *float64, deltaX, deltaY float64) { +func shiftPointUntilEmpty(ctx context.Context, rd RouteDrawer, x, y *float64, deltaX, deltaY float64) { canvas := rd.GetCanvas() startX, startY := *x, *y steps := 0 @@ -340,17 +331,15 @@ func shiftPointUntilEmpty(rd RouteDrawer, x, y *float64, deltaX, deltaY float64) if canvas.IsInBounds(xi, yi) { char := canvas.Get(xi, yi) if char == " " { - fmt.Printf("[D2ASCII] Found empty space after %d steps: (%.2f, %.2f) -> (%.2f, %.2f)\n", - steps, startX, startY, *x, *y) + log.Debug(ctx, "found empty space", slog.Int("steps", steps), slog.Float64("startX", startX), slog.Float64("startY", startY), slog.Float64("x", *x), slog.Float64("y", *y)) break // Found empty space } - fmt.Printf("[D2ASCII] Position (%d, %d) occupied by '%s', shifting by (%.2f, %.2f)\n", - xi, yi, string(char), deltaX, deltaY) + log.Debug(ctx, "position occupied, shifting", slog.Int("x", xi), slog.Int("y", yi), slog.String("char", string(char)), slog.Float64("deltaX", deltaX), slog.Float64("deltaY", deltaY)) *x += deltaX *y += deltaY steps++ } else { - fmt.Printf("[D2ASCII] Position (%d, %d) out of bounds, stopping\n", xi, yi) + log.Debug(ctx, "position out of bounds, stopping", slog.Int("x", xi), slog.Int("y", yi)) break // Out of bounds } } diff --git a/d2renderers/d2ascii/asciishapes/asciishapes.go b/d2renderers/d2ascii/asciishapes/asciishapes.go index e782a0bc26..1dde0ee577 100644 --- a/d2renderers/d2ascii/asciishapes/asciishapes.go +++ b/d2renderers/d2ascii/asciishapes/asciishapes.go @@ -1,12 +1,14 @@ package asciishapes import ( - "fmt" + "context" + "log/slog" "math" "strings" "oss.terrastruct.com/d2/d2renderers/d2ascii/asciicanvas" "oss.terrastruct.com/d2/d2renderers/d2ascii/charset" + "oss.terrastruct.com/d2/lib/log" ) type Context struct { @@ -15,6 +17,7 @@ type Context struct { FW float64 FH float64 Scale float64 + Ctx context.Context } const ( @@ -33,35 +36,33 @@ func (ctx *Context) Calibrate(x, y, w, h float64) (int, int, int, int) { wC := int(math.Round((w / ctx.FW) * ctx.Scale)) hC := int(math.Round((h / ctx.FH) * ctx.Scale)) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Calibrate: (%.0f,%.0f) %.0fx%.0f -> (%d,%d) %dx%d [FW=%.2f, FH=%.2f, Scale=%.2f]\033[0m\n", - x, y, w, h, xC, yC, wC, hC, ctx.FW, ctx.FH, ctx.Scale) + log.Debug(ctx.Ctx, "calibrate", slog.Float64("origX", x), slog.Float64("origY", y), slog.Float64("origW", w), slog.Float64("origH", h), slog.Int("x", xC), slog.Int("y", yC), slog.Int("w", wC), slog.Int("h", hC), slog.Float64("FW", ctx.FW), slog.Float64("FH", ctx.FH), slog.Float64("Scale", ctx.Scale)) return xC, yC, wC, hC } -func LabelY(y1, y2, h int, label, labelPosition string) int { +func LabelY(ctx context.Context, y1, y2, h int, label, labelPosition string) int { ly := -1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Label Y calculation: bounds=%d-%d, height=%d, position='%s'\033[0m\n", - y1, y2, h, labelPosition) + log.Debug(ctx, "label Y calculation", slog.Int("y1", y1), slog.Int("y2", y2), slog.Int("height", h), slog.String("position", labelPosition)) if strings.Contains(labelPosition, "OUTSIDE") { if strings.Contains(labelPosition, "BOTTOM") { ly = y2 + 1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Outside bottom: y=%d\033[0m\n", ly) + log.Debug(ctx, "label position outside bottom", slog.Int("y", ly)) } else if strings.Contains(labelPosition, "TOP") { ly = y1 - 1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Outside top: y=%d\033[0m\n", ly) + log.Debug(ctx, "label position outside top", slog.Int("y", ly)) } } else { if strings.Contains(labelPosition, "TOP") { ly = y1 + 1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Inside top: y=%d\033[0m\n", ly) + log.Debug(ctx, "label position inside top", slog.Int("y", ly)) } else if strings.Contains(labelPosition, "MIDDLE") { ly = y1 + h/2 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Inside middle: y=%d\033[0m\n", ly) + log.Debug(ctx, "label position inside middle", slog.Int("y", ly)) } else if strings.Contains(labelPosition, "BOTTOM") { ly = y2 - 1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Inside bottom: y=%d\033[0m\n", ly) + log.Debug(ctx, "label position inside bottom", slog.Int("y", ly)) } } return ly @@ -69,43 +70,39 @@ func LabelY(y1, y2, h int, label, labelPosition string) int { func DrawShapeLabel(ctx *Context, x1, y1, x2, y2, width, height int, label, labelPosition string) { if label == "" { - fmt.Printf("\033[36m[D2ASCII-SHAPE] No label to draw\033[0m\n") + log.Debug(ctx.Ctx, "no label to draw") return } - fmt.Printf("\033[36m[D2ASCII-SHAPE] Drawing label '%s' in bounds (%d,%d)-(%d,%d) [%dx%d]\033[0m\n", - label, x1, y1, x2, y2, width, height) + log.Debug(ctx.Ctx, "drawing shape label", slog.String("label", label), slog.Int("x1", x1), slog.Int("y1", y1), slog.Int("x2", x2), slog.Int("y2", y2), slog.Int("width", width), slog.Int("height", height)) - ly := LabelY(y1, y2, height, label, labelPosition) + ly := LabelY(ctx.Ctx, y1, y2, height, label, labelPosition) lx := x1 + (width-len(label))/2 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Label position calculated: (%d, %d)\033[0m\n", lx, ly) + log.Debug(ctx.Ctx, "label position calculated", slog.Int("x", lx), slog.Int("y", ly)) ctx.Canvas.DrawLabel(lx, ly, label) } func AdjustWidthForLabel(ctx *Context, x, y, w, h float64, width int, label string) int { if label == "" { - fmt.Printf("\033[36m[D2ASCII-SHAPE] No label, keeping width: %d\033[0m\n", width) + log.Debug(ctx.Ctx, "no label, keeping width", slog.Int("width", width)) return width } originalWidth := width availableSpace := width - len(label) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Width adjustment for label '%s' (%d chars): width=%d, available=%d\033[0m\n", - label, len(label), width, availableSpace) + log.Debug(ctx.Ctx, "width adjustment for label", slog.String("label", label), slog.Int("chars", len(label)), slog.Int("width", width), slog.Int("available", availableSpace)) if availableSpace < MinLabelPadding { width = len(label) + MinLabelPadding - fmt.Printf("\033[36m[D2ASCII-SHAPE] Insufficient space, expanding: %d -> %d (min padding=%d)\033[0m\n", - originalWidth, width, MinLabelPadding) + log.Debug(ctx.Ctx, "insufficient space, expanding width", slog.Int("original", originalWidth), slog.Int("new", width), slog.Int("minPadding", MinLabelPadding)) return width } if availableSpace%2 == 1 { width = width - 1 - fmt.Printf("\033[36m[D2ASCII-SHAPE] Odd spacing, adjusting for centering: %d -> %d\033[0m\n", - originalWidth, width) + log.Debug(ctx.Ctx, "odd spacing, adjusting for centering", slog.Int("original", originalWidth), slog.Int("new", width)) return width } - fmt.Printf("\033[36m[D2ASCII-SHAPE] Width unchanged: %d\033[0m\n", width) + log.Debug(ctx.Ctx, "width unchanged", slog.Int("width", width)) return width } diff --git a/d2renderers/d2ascii/asciishapes/callout.go b/d2renderers/d2ascii/asciishapes/callout.go index c85f2014c5..7c6ddef942 100644 --- a/d2renderers/d2ascii/asciishapes/callout.go +++ b/d2renderers/d2ascii/asciishapes/callout.go @@ -34,7 +34,7 @@ func DrawCallout(ctx *Context, x, y, w, h float64, label, labelPosition string) } if label != "" { - ly := LabelY(y1, y2, body, label, labelPosition) + ly := LabelY(ctx.Ctx, y1, y2, body, label, labelPosition) lx := x1 + (iw-len(label))/2 ctx.Canvas.DrawLabel(lx, ly, label) } diff --git a/d2renderers/d2ascii/asciishapes/cylinder.go b/d2renderers/d2ascii/asciishapes/cylinder.go index 12ae80f516..c7b6907049 100644 --- a/d2renderers/d2ascii/asciishapes/cylinder.go +++ b/d2renderers/d2ascii/asciishapes/cylinder.go @@ -36,7 +36,7 @@ func DrawCylinder(ctx *Context, x, y, w, h float64, label, labelPosition string) } if label != "" { - ly := LabelY(y1+1, y2, hi, label, labelPosition) + ly := LabelY(ctx.Ctx, y1+1, y2, hi, label, labelPosition) lx := x1 + (wi-len(label))/2 ctx.Canvas.DrawLabel(lx, ly, label) } diff --git a/d2renderers/d2ascii/asciishapes/diamond.go b/d2renderers/d2ascii/asciishapes/diamond.go index 181687c8fc..f0150acfca 100644 --- a/d2renderers/d2ascii/asciishapes/diamond.go +++ b/d2renderers/d2ascii/asciishapes/diamond.go @@ -47,7 +47,7 @@ func DrawDiamond(ctx *Context, x, y, w, h float64, label, labelPosition string) } if label != "" { - ly := LabelY(y1, y2, ih, label, labelPosition) + ly := LabelY(ctx.Ctx, y1, y2, ih, label, labelPosition) lx := x1 + (iw-len(label))/2 ctx.Canvas.DrawLabel(lx, ly, label) } diff --git a/d2renderers/d2ascii/asciishapes/document.go b/d2renderers/d2ascii/asciishapes/document.go index 557f769b11..ec660de501 100644 --- a/d2renderers/d2ascii/asciishapes/document.go +++ b/d2renderers/d2ascii/asciishapes/document.go @@ -54,7 +54,7 @@ func DrawDocument(ctx *Context, x, y, w, h float64, label, labelPosition string) } if label != "" { - ly := LabelY(y1, y2, ih-2, label, labelPosition) + ly := LabelY(ctx.Ctx, y1, y2, ih-2, label, labelPosition) lx := x1 + (iw-len(label))/2 ctx.Canvas.DrawLabel(lx, ly, label) } diff --git a/d2renderers/d2ascii/asciishapes/rectangle.go b/d2renderers/d2ascii/asciishapes/rectangle.go index 552466b86a..b29c843f66 100644 --- a/d2renderers/d2ascii/asciishapes/rectangle.go +++ b/d2renderers/d2ascii/asciishapes/rectangle.go @@ -2,12 +2,14 @@ package asciishapes import ( "fmt" + "log/slog" "strings" + + "oss.terrastruct.com/d2/lib/log" ) func DrawRect(ctx *Context, x, y, w, h float64, label, labelPosition, symbol string, preserveHeight ...bool) { - fmt.Printf("\033[36m[D2ASCII-SHAPE] DrawRect: (%.0f,%.0f) %.0fx%.0f, label='%s', symbol='%s'\033[0m\n", - x, y, w, h, label, symbol) + log.Debug(ctx.Ctx, "drawing rectangle", slog.Float64("x", x), slog.Float64("y", y), slog.Float64("w", w), slog.Float64("h", h), slog.String("label", label), slog.String("symbol", symbol)) x1, y1, wC, hC := ctx.Calibrate(x, y, w, h) originalHC := hC @@ -16,18 +18,15 @@ func DrawRect(ctx *Context, x, y, w, h float64, label, labelPosition, symbol str if hC > 2 { hC-- y1++ - fmt.Printf("\033[36m[D2ASCII-SHAPE] Height adjustment for label centering: %d -> %d, y1: %d -> %d\033[0m\n", - originalHC, hC, y1-1, y1) + log.Debug(ctx.Ctx, "height adjustment for label centering", slog.Int("original", originalHC), slog.Int("new", hC), slog.Int("oldY1", y1-1), slog.Int("newY1", y1)) } else { hC++ - fmt.Printf("\033[36m[D2ASCII-SHAPE] Height expanded for small shape: %d -> %d\033[0m\n", - originalHC, hC) + log.Debug(ctx.Ctx, "height expanded for small shape", slog.Int("original", originalHC), slog.Int("new", hC)) } } wC = AdjustWidthForLabel(ctx, x, y, w, h, wC, label) x2, y2 := x1+wC, y1+hC - fmt.Printf("\033[36m[D2ASCII-SHAPE] Final draw bounds: (%d,%d) to (%d,%d) [%dx%d] (actual shape area)\033[0m\n", - x1, y1, x2, y2, wC, hC) + log.Debug(ctx.Ctx, "final draw bounds", slog.Int("x1", x1), slog.Int("y1", y1), slog.Int("x2", x2), slog.Int("y2", y2), slog.Int("w", wC), slog.Int("h", hC)) corners := map[string]string{ fmt.Sprintf("%d_%d", x1, y1): ctx.Chars.TopLeftCorner(), fmt.Sprintf("%d_%d", x2, y1): ctx.Chars.TopRightCorner(), @@ -53,7 +52,7 @@ func DrawRect(ctx *Context, x, y, w, h float64, label, labelPosition, symbol str charsDrawn++ } } - fmt.Printf("\033[36m[D2ASCII-SHAPE] Drew %d border characters\033[0m\n", charsDrawn) + log.Debug(ctx.Ctx, "drew border characters", slog.Int("count", charsDrawn)) DrawShapeLabel(ctx, x1, y1, x2, y2, wC, hC, label, labelPosition) } diff --git a/d2renderers/d2ascii/asciishapes/step.go b/d2renderers/d2ascii/asciishapes/step.go index e01c22f0b8..6c85c22785 100644 --- a/d2renderers/d2ascii/asciishapes/step.go +++ b/d2renderers/d2ascii/asciishapes/step.go @@ -23,7 +23,7 @@ func DrawStep(ctx *Context, x, y, w, h float64, label, labelPosition string) { } if label != "" { - ly := LabelY(y1, y2, ih, label, labelPosition) + ly := LabelY(ctx.Ctx, y1, y2, ih, label, labelPosition) lx := x1 + (iw-len(label))/2 ctx.Canvas.DrawLabel(lx, ly, label) } diff --git a/d2renderers/d2ascii/d2ascii.go b/d2renderers/d2ascii/d2ascii.go index e1a6d575c7..124e2e9c4b 100644 --- a/d2renderers/d2ascii/d2ascii.go +++ b/d2renderers/d2ascii/d2ascii.go @@ -1,14 +1,18 @@ +// Set DEBUG_ASCII=1 environment variable to enable verbose ASCII rendering debug logs. package d2ascii import ( - "fmt" + "context" + "log/slog" "math" + "os" "oss.terrastruct.com/d2/d2renderers/d2ascii/asciicanvas" "oss.terrastruct.com/d2/d2renderers/d2ascii/asciiroute" "oss.terrastruct.com/d2/d2renderers/d2ascii/asciishapes" "oss.terrastruct.com/d2/d2renderers/d2ascii/charset" "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/log" ) const ( @@ -32,6 +36,7 @@ type ASCIIartist struct { tcurve string SCALE float64 diagram d2target.Diagram + ctx context.Context } type RenderOpts struct { Scale *float64 @@ -47,34 +52,29 @@ func NewBoundary(tl, br Point) *Boundary { } func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) { - fmt.Printf("\033[36m[D2ASCII-SHAPE] GetBoundary for shape %s (%s)\033[0m\n", s.ID, s.Type) // 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) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Original dimensions: (%.0f,%.0f) %.0fx%.0f\033[0m\n", - posX, posY, width, height) if s.Multiple { - fmt.Printf("\033[36m[D2ASCII-SHAPE] Multiple shape, expanding boundary by %d\033[0m\n", d2target.MULTIPLE_OFFSET) posX -= d2target.MULTIPLE_OFFSET // Move left to include shadow area width += d2target.MULTIPLE_OFFSET // Include shadow width height += d2target.MULTIPLE_OFFSET // Include shadow height - fmt.Printf("\033[36m[D2ASCII-SHAPE] Expanded dimensions: (%.0f,%.0f) %.0fx%.0f\033[0m\n", - posX, posY, width, height) } // Use the same calibration logic as the drawing functions - ctx := &asciishapes.Context{ + shapeCtx := &asciishapes.Context{ Canvas: a.canvas, Chars: a.chars, FW: a.FW, FH: a.FH, Scale: a.SCALE, + Ctx: a.ctx, } - x1, y1, wC, hC := ctx.Calibrate(posX, posY, width, height) + x1, y1, wC, hC := shapeCtx.Calibrate(posX, posY, width, height) // Apply the same width adjustments as the drawing code preserveWidth := hasConnectionsAtRightEdge(s, a.diagram.Connections, a.FW) @@ -84,7 +84,7 @@ func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) { if availableSpace >= asciishapes.MinLabelPadding && availableSpace%2 == 1 { // Adjust the original width before recalibrating width += float64(int(a.FW / a.SCALE)) - x1, y1, wC, hC = ctx.Calibrate(posX, posY, width, height) + x1, y1, wC, hC = shapeCtx.Calibrate(posX, posY, width, height) } } @@ -99,13 +99,10 @@ func (a *ASCIIartist) GetBoundary(s d2target.Shape) (Point, Point) { } // Apply the same width adjustments as DrawRect for labels - wC = asciishapes.AdjustWidthForLabel(ctx, posX, posY, width, height, wC, s.Label) + wC = asciishapes.AdjustWidthForLabel(shapeCtx, posX, posY, width, height, wC, s.Label) x2, y2 := x1+wC, y1+hC - fmt.Printf("\033[36m[D2ASCII-SHAPE] Boundary matches actual draw bounds: (%d,%d)-(%d,%d) [%dx%d]\033[0m\n", - x1, y1, x2, y2, wC, hC) - return Point{X: x1, Y: y1}, Point{X: x2, Y: y2} } @@ -115,6 +112,7 @@ func (a *ASCIIartist) GetDiagram() *d2target.Diagram { return &a.diagram } func (a *ASCIIartist) GetFontWidth() float64 { return a.FW } func (a *ASCIIartist) GetFontHeight() float64 { return a.FH } func (a *ASCIIartist) GetScale() float64 { return a.SCALE } +func (a *ASCIIartist) GetContext() context.Context { return a.ctx } func (a *ASCIIartist) GetBoundaryForShape(s d2target.Shape) (asciiroute.Point, asciiroute.Point) { p1, p2 := a.GetBoundary(s) return asciiroute.Point{X: p1.X, Y: p1.Y}, asciiroute.Point{X: p2.X, Y: p2.Y} @@ -220,10 +218,15 @@ func (a *ASCIIartist) calculateExtendedBounds(diagram *d2target.Diagram) (tl, br return tl, br } -func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { +func (a *ASCIIartist) Render(ctx context.Context, diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) { if opts == nil { opts = &RenderOpts{} } + + if os.Getenv("DEBUG_ASCII") == "" { + ctx = log.Leveled(ctx, slog.LevelInfo) + } + a.ctx = ctx chars := a.chars if opts.Charset == charset.ASCII { chars = charset.New(charset.ASCII) @@ -262,44 +265,40 @@ func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byt } } padding := maxLabelLen + asciishapes.MinLabelPadding - fmt.Printf("\033[36m[D2ASCII-SHAPE] Canvas padding calculated: maxLabelLen=%d, padding=%d\033[0m\n", maxLabelLen, padding) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Creating canvas: %dx%d (base: %dx%d + padding)\033[0m\n", w+padding+1, h+padding+1, w, h) + 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) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Processing %d shapes with offset (%d, %d)\033[0m\n", len(diagram.Shapes), xOffset, yOffset) + 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 { - fmt.Printf("\033[36m[D2ASCII-SHAPE] Shape %d (%s): %s at (%.0f,%.0f) size %.0fx%.0f\033[0m\n", - i, shape.ID, shape.Type, float64(shape.Pos.X), float64(shape.Pos.Y), float64(shape.Width), float64(shape.Height)) + log.Debug(ctx, "processing shape", slog.Int("index", i), slog.String("id", shape.ID), slog.String("type", shape.Type), slog.Float64("x", float64(shape.Pos.X)), slog.Float64("y", float64(shape.Pos.Y)), slog.Float64("width", float64(shape.Width)), slog.Float64("height", float64(shape.Height))) originalX, originalY := shape.Pos.X, shape.Pos.Y shape.Pos.X += xOffset shape.Pos.Y += yOffset - fmt.Printf("\033[36m[D2ASCII-SHAPE] Position adjusted: (%.0f,%.0f) -> (%d,%d)\033[0m\n", - float64(originalX), float64(originalY), shape.Pos.X, shape.Pos.Y) + log.Debug(ctx, "position adjusted", slog.Float64("originalX", float64(originalX)), slog.Float64("originalY", float64(originalY)), slog.Int("newX", shape.Pos.X), slog.Int("newY", shape.Pos.Y)) preserveWidth := hasConnectionsAtRightEdge(shape, diagram.Connections, a.FW) preserveHeight := hasConnectionsAtTopEdge(shape, diagram.Connections, a.FH) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Right edge connections detected: %t\033[0m\n", preserveWidth) + log.Debug(ctx, "edge connections", slog.Bool("preserveWidth", preserveWidth)) - ctx := &asciishapes.Context{ + shapeCtx := &asciishapes.Context{ Canvas: a.canvas, Chars: a.chars, FW: a.FW, FH: a.FH, Scale: a.SCALE, + Ctx: ctx, } originalWidth := shape.Width if preserveWidth && shape.Label != "" { wC := int(math.Round((float64(shape.Width) / a.FW) * a.SCALE)) availableSpace := wC - len(shape.Label) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Width preservation check: calibrated=%d, label=%d chars, available=%d\033[0m\n", - wC, len(shape.Label), availableSpace) + log.Debug(ctx, "width preservation check", slog.Int("calibrated", wC), slog.Int("labelChars", len(shape.Label)), slog.Int("available", availableSpace)) if availableSpace >= asciishapes.MinLabelPadding && availableSpace%2 == 1 { shape.Width += int(a.FW / a.SCALE) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Width adjusted for even spacing: %d -> %d\033[0m\n", - originalWidth, shape.Width) + log.Debug(ctx, "width adjusted", slog.Int("originalWidth", originalWidth), slog.Int("newWidth", shape.Width)) } } @@ -310,7 +309,7 @@ func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byt drawHeight := float64(shape.Height) if shape.Multiple { - fmt.Printf("\033[36m[D2ASCII-SHAPE] Multiple shape adjustments: offset=%d\033[0m\n", d2target.MULTIPLE_OFFSET) + 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 @@ -318,46 +317,41 @@ func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byt // Expand size to fill entire multiple effect area drawWidth += d2target.MULTIPLE_OFFSET // Include shadow width drawHeight += d2target.MULTIPLE_OFFSET // Include shadow height - fmt.Printf("\033[36m[D2ASCII-SHAPE] Multiple dimensions: (%.0f,%.0f) %.0fx%.0f -> (%.0f,%.0f) %.0fx%.0f\033[0m\n", - float64(shape.Pos.X), float64(shape.Pos.Y), float64(shape.Width), float64(shape.Height), - drawX, drawY, drawWidth, drawHeight) + 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)) } - fmt.Printf("\033[36m[D2ASCII-SHAPE] Final draw parameters: (%.0f,%.0f) %.0fx%.0f, label='%s'\033[0m\n", - drawX, drawY, drawWidth, drawHeight, shape.Label) + 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)) - fmt.Printf("\033[36m[D2ASCII-SHAPE] Drawing shape type: %s\033[0m\n", shape.Type) + log.Debug(ctx, "drawing shape", slog.String("type", shape.Type)) switch shape.Type { case d2target.ShapeRectangle: - fmt.Printf("\033[36m[D2ASCII-SHAPE] -> DrawRect\033[0m\n") - asciishapes.DrawRect(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, "", preserveHeight) + asciishapes.DrawRect(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, "", preserveHeight) case d2target.ShapeSquare: - fmt.Printf("\033[36m[D2ASCII-SHAPE] -> DrawRect (square)\033[0m\n") - asciishapes.DrawRect(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, "", preserveHeight) + asciishapes.DrawRect(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, "", preserveHeight) case d2target.ShapePage: - asciishapes.DrawPage(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawPage(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeHexagon: - asciishapes.DrawHex(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawHex(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapePerson: - asciishapes.DrawPerson(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawPerson(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeStoredData: - asciishapes.DrawStoredData(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawStoredData(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeCylinder: - asciishapes.DrawCylinder(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawCylinder(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapePackage: - asciishapes.DrawPackage(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawPackage(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeParallelogram: - asciishapes.DrawParallelogram(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawParallelogram(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeQueue: - asciishapes.DrawQueue(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawQueue(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeStep: - asciishapes.DrawStep(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawStep(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeCallout: - asciishapes.DrawCallout(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawCallout(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeDocument: - asciishapes.DrawDocument(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawDocument(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) case d2target.ShapeDiamond: - asciishapes.DrawDiamond(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) + asciishapes.DrawDiamond(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition) default: symbol := "" switch shape.Type { @@ -370,7 +364,7 @@ func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byt default: symbol = "" } - asciishapes.DrawRect(ctx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, symbol, preserveHeight) + asciishapes.DrawRect(shapeCtx, drawX, drawY, drawWidth, drawHeight, shape.Label, shape.LabelPosition, symbol, preserveHeight) } } for _, conn := range diagram.Connections { @@ -393,8 +387,6 @@ func (a *ASCIIartist) Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byt func (a *ASCIIartist) calibrateXY(x, y float64) (float64, float64) { xC := float64(math.Round((x / a.FW) * a.SCALE)) yC := float64(math.Round((y / a.FH) * a.SCALE)) - fmt.Printf("[D2ASCII] CalibrateXY: (%.2f, %.2f) -> (%.2f, %.2f) [FW=%.2f, FH=%.2f, SCALE=%.2f]\n", - x, y, xC, yC, a.FW, a.FH, a.SCALE) return xC, yC } @@ -457,8 +449,6 @@ func hasConnectionsAtTopEdge(shape d2target.Shape, connections []d2target.Connec // Check if horizontal segment connects to shape's top edge if math.Abs(segmentY-shapeTop) < tolerance && segmentRight >= shapeLeft && segmentLeft <= shapeRight { - fmt.Printf("\033[36m[D2ASCII-SHAPE] Found horizontal top connection: segment Y=%.2f vs shape Y=%.2f\033[0m\n", - segmentY, shapeTop) return true } } diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 2ae844603e..99d555b555 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -208,7 +208,7 @@ func runASCIITxtarTest(t *testing.T, tc testCase) { Scale: renderOpts.Scale, Charset: charset.Unicode, } - extendedBytes, err := extendedAsciiArtist.Render(diagram, extendedRenderOpts) + extendedBytes, err := extendedAsciiArtist.Render(ctx, diagram, extendedRenderOpts) assert.Success(t, err) err3 = diff.Testdata(filepath.Join(outputDir, "extended"), ".txt", extendedBytes) @@ -219,7 +219,7 @@ func runASCIITxtarTest(t *testing.T, tc testCase) { Scale: renderOpts.Scale, Charset: charset.ASCII, } - standardBytes, err := standardAsciiArtist.Render(diagram, standardRenderOpts) + standardBytes, err := standardAsciiArtist.Render(ctx, diagram, standardRenderOpts) assert.Success(t, err) err4 = diff.Testdata(filepath.Join(outputDir, "standard"), ".txt", standardBytes)