Zig-native implementation of the Unicode Bidirectional Algorithm (UAX #9).
Codepoint-level API with explicit allocator passing and no global state.
Why:
I needed bidi support for RTL text layout in Ghostty, and there wasn’t a pure Zig option. FriBidi and ICU work well, but they add C dependencies and don’t match Zig’s allocator-first style.
Key points:
Full conformance in current suite: BidiTest failed=0, BidiCharacterTest failed=0
Competitive performance vs FriBidi/ICU across benchmark profiles
Scratch APIs for allocation-light render loops
Terminal-oriented resolveVisualLayout for one-call line layout
zabadi was actually the first thing I came across when looking into this space! Ended up going a different route though , needed codepoint input directly (terminals already have them decoded, felt weird to re-encode to UTF-8) and wanted scratch APIs that don’t allocate per call for rendering loops.
Added zabadi to the benchmark harness btw - different tradeoffs for different use cases.
Docs/examples: fair , focused too much on getting conformance and performance, but docs on the list, will reply here once they are out.
Embedded allocator: the newer scratch APIs don’t do this. the older result types store it for convenient deinit(), could revisit.
Scratch vs non-scratch,: scratch reuses buffers across calls so we get zero allocations in steady state. matters for hot loops like terminal rendering (ghostty). Non-scratch is simpler if you just need a one-off call. Felt like both were worth having.
Flags padding: it’s leftover from early fribidi-inspired design, no reason to keep. needs cleaning up .