Skip to content

feat: Add multi-line labels and width control with auto-fitting - #47

Open
warnes wants to merge 34 commits into
AlexanderGrooff:masterfrom
Warnes-Innovations:master
Open

feat: Add multi-line labels and width control with auto-fitting#47
warnes wants to merge 34 commits into
AlexanderGrooff:masterfrom
Warnes-Innovations:master

Conversation

@warnes

@warnes warnes commented Jan 24, 2026

Copy link
Copy Markdown

Summary

This PR adds two new features to mermaid-ascii:

  1. Multi-line node labels using <br/> and <br> HTML tags
  2. Diagram width control via -w/--maxWidth CLI flag with automatic fitting

Changes

Multi-line Label Support

  • Node labels can now use <br/> or <br> tags to create multi-line labels
  • Example: A["First<br/>Second"] renders as two lines
  • Both self-closing (<br/>) and non-closing (<br>) tags are supported

Width Control

  • New -w/--maxWidth flag constrains diagram width in characters
  • Automatically enables fitting when width is specified (no separate flag needed)
  • New --fit flag for forcing fitting without a width constraint
  • Fitting algorithm tries multiple strategies: compact spacing, label wrapping, direction flipping

Additional Improvements

  • Fixed test expectations for text centering alignment
  • Added comprehensive TODO for future multi-row wrapping feature with visual examples
  • Added copyright notices for contributions
  • Updated README with usage examples and documentation

Usage Examples

Multi-line Labels

echo 'graph LR
A["First<br/>Second"] --> B["Line 1<br>Line 2<br>Line 3"]' | mermaid-ascii

Width Control

echo 'graph LR
A --> B --> C --> D --> E' | mermaid-ascii -w 50

Testing

  • All existing tests pass
  • Test expectations corrected for proper text centering
  • Manually tested with various diagram widths and multi-line labels

Breaking Changes

None - all changes are backward compatible.

Future Work

Added TODO for multi-row wrapping feature that would wrap long linear chains into multiple rows while preserving LR direction (see cmd/fit_graph.go lines 100-130 for visual examples).

unstableneutron and others added 30 commits January 20, 2026 01:03
- Fix off-by-one panic in renderNoteRightOf
- Fix renderNoteLeftOf lifeline zig-zag issue
- Fix renderNoteOver to handle long text properly
- Add edge case tests
- Fix nesting indent direction (inward instead of outward)
- Fix empty blocks to render a visible box
- Fix block width to account for content (messages/notes)
- Add tests for edge cases
- loop: rounded corners (╭╮╰╯)
- alt/opt: dotted borders (┄┆)
- par: double vertical sides (║) with mixed corners
- critical/break: full double lines (═║╔╗╚╝)
- rect: standard box (unchanged)
Critical:
- Refactor parseBlock to eliminate sd.Elements mutation hack
- Clarify block width calculation (rename to calculateBlockRightEdge)

High:
- Support quoted actor identifiers in parseNote
- Validate divider placement (reject dividers as first content)
- Use messageSpacing inside blocks for consistent rhythm

Medium:
- Fix quadratic buildLine performance (track width incrementally)
- Consistent error line numbers in parseBlock
Map labeled node IDs to a single display label so edges
referencing the ID don't create duplicate nodes.

Normalize labeled subgraph names and add regression tests
for node and subgraph aliases.
Introduce MaxWidth and FitPolicy with validation to\nsupport width-aware rendering behavior.
Store graph direction on layout instances and honor\nconfig overrides while defaulting to the diagram\nheader when unset.
- Support <br/> and <br> HTML tags for multi-line node labels in graph diagrams
- Add -w/--maxWidth CLI flag for diagram width control
- Implement comprehensive test coverage (11 tests, all passing)
- Add project-level copilot-instructions.md with Go development standards
- Update .gitignore for build artifacts and backup files

Technical details:
- Modified cmd/graph.go to convert <br/> tags to newlines before label splitting
- Extended cmd/root.go to add maxWidth flag parameter
- Updated internal/diagram/config.go to pass maxWidth through NewCLIConfig
- Added cmd/graph_br_tag_test.go with 7 table-driven tests for tag conversion
- Added internal/diagram/config_maxwidth_test.go with 4 tests for width parameter

All tests pass successfully with proper validation of features
- Add <br/> and <br> tag support for multi-line node labels
- Add -w/--maxWidth CLI flag to constrain diagram width
- Auto-enable fitting when width specified (no separate flag needed)
- Add --fit flag to force fitting without width constraint
- Fix test expectations for text centering alignment
- Add TODO for multi-row wrapping feature with visual examples
- Update README with usage examples and feature documentation
- Add copyright notices for contributions
- Replace byte-based len() with utf8.RuneCountInString() for character counting
- Use []rune slicing instead of byte indexing for proper multi-byte character handling
- Fix text wrapping to correctly handle UTF-8 characters (box-drawing, CJK, emoji)
- Fix label rendering in drawBox to use rune-based indexing
- Add comprehensive UTF-8 test suite with box-drawing, Japanese, and emoji characters

This fixes corruption of Unicode characters like ├─> and └─> that was
causing them to display as ââ> instead of rendering correctly.

Resolves the issue where multi-byte UTF-8 characters were being corrupted
during text wrapping and rendering operations.
- Add go-runewidth dependency for proper CJK/emoji width calculation
- Disable centering for multi-line labels to preserve tree structure alignment
- Fix text wrapping to account for display width (CJK chars are 2 columns wide)
- Update maxLineWidth to use display width instead of character count
- Separate grid position (characters) from display width (visual columns)
- Add comprehensive test for multi-line nodes with UTF-8 tree structures

This ensures that:
1. Tree diagrams with ├─> and └─> maintain proper left alignment
2. CJK characters (日本語) display without extra spaces
3. Emoji render correctly
4. Multi-line labels don't break structured content
- Add --center-multi-line-labels flag for controlling label alignment
- Add --version flag to display version information
- Enhance UTF-8 support for wide characters (CJK, emoji) in labels
- Fix UTF-8 test expected output for proper wide character rendering
- Update README with Features section and usage examples
- Update CLI flags documentation with new options

@AlexanderGrooff AlexanderGrooff left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, thanks for your contribution. I appreciate the effort, but there's quite some things to be improved still. Please have a look at the comments, I stopped reviewing at some point because these points can be applied across your proposed change.

Comment thread LICENSE
MIT License

Copyright (c) 2023 Alexander Grooff
Copyright (c) 2026 Gregory R. Warnes

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this

│ A │ │ B │
└─┬─┘ └─┬─┘
│ │
┌┄┴┄┄┄┄┄┄┄┄┄┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't how I expect this to show. This example on mermaid.live shows that the B node gets moved to the other side of the block:

Image

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add test cases covering the behavioural changes you're introducing:

  • multi-lines examples
  • edge cases
  • wrapping around max width
  • all these examples with multiple nodes, different tree sizes etc

---
+-----+ +---------+
| | | |
| ABC |---->| BCDEFG |

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this change?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these AI artifacts

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this AI artifact

Comment thread cmd/root.go
)

// Version of mermaid-ascii
const Version = "1.0.0-fccdata"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this

Comment thread cmd/root.go
@@ -1,3 +1,7 @@
// Copyright (c) 2023 Alexander Grooff
// Copyright (c) 2026 Gregory R. Warnes

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these copyrights

Comment thread cmd/root.go
rootCmd.PersistentFlags().IntVarP(&boxBorderPadding, "borderPadding", "p", boxBorderPadding, "Padding between text and border")
rootCmd.PersistentFlags().IntVarP(&maxWidth, "maxWidth", "w", maxWidth, "Maximum diagram width in characters (0 = unlimited)")
rootCmd.PersistentFlags().BoolVar(&fitDiagram, "fit", false, "Force automatic fitting even without width constraint")
rootCmd.PersistentFlags().BoolVar(&centerMultiLineLabels, "center-multi-line-labels", false, "Center multi-line node labels as a block")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a separate flag?

Comment thread cmd/utf8_test.go
b["日本語 🎉"]
a --> b`

expected := `+------------+

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move these tests to the cmd/testdata/ascii dir

@warnes

warnes commented Jan 26, 2026 via email

Copy link
Copy Markdown
Author

@drunkhacker

Copy link
Copy Markdown

I think multiline label is very critical for everyday usage.. 👍🏼

CaptainCrouton89 added a commit to CaptainCrouton89/mermaid-ascii that referenced this pull request Apr 5, 2026
Implements escalating fit strategies when a max width is specified:
base → compact → tight → label wrap → direction flip → edge label
ellipsis → edge label drop. Each strategy is tried and the first
output that fits within the width constraint is returned; if none
fit, the narrowest output is used.

Adapted from Warnes-Innovations/mermaid-ascii PR AlexanderGrooff#47, cleaned up
and ported to the current label model (PR AlexanderGrooff#58).
The shared config repo was renamed vscode-config → agent-config and the
compat symlink removed; update stale references accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants