@@ -160,6 +160,34 @@ stacktower render examples/real/yargs.json -t nodelink -o yargs.svg
160160stacktower render examples/real/flask.json -t tower,nodelink -o flask
161161```
162162
163+ #### Output Formats
164+
165+ ``` bash
166+ # SVG output (default)
167+ stacktower render examples/real/flask.json -o flask.svg
168+
169+ # JSON layout export (for external tools or re-rendering)
170+ stacktower render examples/real/flask.json -f json -o flask.json
171+
172+ # PDF output (requires librsvg: brew install librsvg)
173+ stacktower render examples/real/flask.json -f pdf -o flask.pdf
174+
175+ # PNG output with 2x scale (requires librsvg)
176+ stacktower render examples/real/flask.json -f png -o flask.png
177+
178+ # Multiple formats at once (outputs flask.svg, flask.json, flask.pdf)
179+ stacktower render examples/real/flask.json -f svg,json,pdf -o flask
180+
181+ # Combine multiple types and formats
182+ stacktower render examples/real/flask.json -t tower,nodelink -f svg,json
183+ ```
184+
185+ Output path behavior:
186+ - ** No ` -o ` ** : Derives from input (` input.json ` → ` input.<format> ` )
187+ - ** Single format** : Uses exact path (` -o out.svg ` → ` out.svg ` )
188+ - ** Multiple formats** : Strips extension, adds format (` -o out.svg -f svg,json ` → ` out.svg ` , ` out.json ` )
189+ - ** Multiple types** : Adds type suffix (` -t tower,nodelink ` → ` out_tower.svg ` , ` out_nodelink.svg ` )
190+
163191### Included Examples
164192
165193The repository ships with pre-parsed graphs so you can experiment immediately:
@@ -200,8 +228,9 @@ stacktower render examples/test/diamond.json -o diamond.svg
200228
201229| Flag | Description |
202230| ------| -------------|
203- | ` -o ` , ` --output ` | Output file or base path for multiple types |
204- | ` -t ` , ` --type ` | Visualization type: ` tower ` (default), ` nodelink ` |
231+ | ` -o ` , ` --output ` | Output file or base path for multiple types/formats |
232+ | ` -t ` , ` --type ` | Visualization type(s): ` tower ` (default), ` nodelink ` (comma-separated) |
233+ | ` -f ` , ` --format ` | Output format(s): ` svg ` (default), ` json ` , ` pdf ` , ` png ` (comma-separated) |
205234| ` --normalize ` | Apply graph normalization: break cycles, remove transitive edges, assign layers, subdivide long edges (default: true) |
206235
207236#### Tower Options
@@ -436,6 +465,70 @@ func (p *MyLockParser) Parse(path string, opts deps.Options) (*deps.ManifestResu
436465
437466The ` deps.Registry ` handles concurrent fetching with configurable depth/node limits and metadata enrichment automatically.
438467
468+ ### Adding a New Output Format
469+
470+ Output formats are implemented as "sinks" in ` pkg/render/tower/sink/ ` . Each sink takes a computed ` layout.Layout ` and renders it to bytes.
471+
472+ 1 . ** Create a sink file** in ` pkg/render/tower/sink/<format>.go ` :
473+
474+ ``` go
475+ package sink
476+
477+ import " github.com/matzehuels/stacktower/pkg/render/tower/layout"
478+
479+ type MyFormatOption func (*myFormatRenderer)
480+
481+ type myFormatRenderer struct {
482+ // Configuration fields
483+ }
484+
485+ func WithMyFormatOption (val string ) MyFormatOption {
486+ return func (r *myFormatRenderer) { r.option = val }
487+ }
488+
489+ func RenderMyFormat (l layout .Layout , opts ...MyFormatOption ) ([]byte , error ) {
490+ r := myFormatRenderer{}
491+ for _ , opt := range opts {
492+ opt (&r)
493+ }
494+
495+ // Access layout data:
496+ // - l.FrameWidth, l.FrameHeight: canvas dimensions
497+ // - l.MarginX, l.MarginY: margins
498+ // - l.Blocks: map[string]Block with position data
499+ // - l.RowOrders: node ordering per row
500+
501+ // Generate output bytes
502+ return []byte (" ..." ), nil
503+ }
504+ ```
505+
506+ 2 . ** Register in CLI** in ` internal/cli/render.go ` :
507+
508+ ``` go
509+ // Add to validFormats map
510+ var validFormats = map [string ]bool {
511+ " svg" : true , " json" : true , " pdf" : true , " png" : true ,
512+ " myformat" : true , // Add your format
513+ }
514+
515+ // Add case in renderTower()
516+ switch format {
517+ case " myformat" :
518+ logger.Info (" Rendering tower as MyFormat" )
519+ return sink.RenderMyFormat (l, buildMyFormatOpts (g, opts)...)
520+ // ...
521+ }
522+ ```
523+
524+ The existing sinks provide examples:
525+ - ** ` svg.go ` ** : Full-featured SVG with styles, interactivity, and popups
526+ - ** ` json.go ` ** : Layout data export for external tools (round-trip capable)
527+ - ** ` pdf.go ` ** : Wrapper that converts SVG via ` rsvg-convert `
528+ - ** ` png.go ` ** : Wrapper that converts SVG via ` rsvg-convert ` with scaling
529+
530+ The JSON format is designed as a complete serialization—it includes all render options (` style ` , ` seed ` , ` randomize ` , ` merged ` ) and node flags (` auxiliary ` , ` synthetic ` ) needed to reproduce the exact visual output.
531+
439532## Development
440533
441534``` bash
0 commit comments