or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdmain-package.mdrenderer-package.mdtw-package.mdtwcache-package.mdtwwarp-package.mdtwwidth-package.md
tile.json

twwarp-package.mddocs/

twwarp Package

The twwarp package provides text wrapping algorithms for optimal line breaking in terminal tables. It implements a Knuth-Plass-inspired minimal raggedness algorithm for high-quality text layout and supports Unicode-aware word segmentation using UAX#29.

Package Information

  • Package Name: github.com/olekukonko/tablewriter/pkg/twwarp
  • Package Type: Go
  • Language: Go
  • Installation: Included with tablewriter module
  • Import Path: github.com/olekukonko/tablewriter/pkg/twwarp

Core Import

import "github.com/olekukonko/tablewriter/pkg/twwarp"

Overview

The twwarp package is designed for text wrapping within table cells, providing:

  • UAX#29 word segmentation - Unicode-aware word splitting
  • Minimal raggedness algorithm - Knuth-Plass-inspired line breaking for optimal text layout
  • Space preservation - Options to maintain leading and trailing whitespace
  • Display width awareness - Uses actual terminal display width (not byte/rune count)
  • Automatic width adjustment - Increases line limit when words exceed the specified width

The package uses display width calculations from the twwidth package to handle ANSI escape sequences, East Asian characters, and combining characters correctly.

Basic Usage

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Simple text wrapping
    text := "This is a long sentence that needs to be wrapped to fit within a specific width"
    lines, actualWidth := twwarp.WrapString(text, 30)

    for _, line := range lines {
        fmt.Printf("[%s]\n", line)
    }
    fmt.Printf("Actual width used: %d\n", actualWidth)

    // Output:
    // [This is a long sentence that]
    // [needs to be wrapped to fit]
    // [within a specific width]
    // Actual width used: 30
}

Capabilities

Word Splitting

Splits a string into words using Unicode-aware word boundary detection (UAX#29).

func SplitWords(s string) []string

Parameters:

  • s string - Input string to split into words

Returns:

  • []string - Slice of words (whitespace removed)

Description:

SplitWords performs Unicode-aware word segmentation by identifying word boundaries according to Unicode Annex #29 (UAX#29). It splits text at spaces and returns only the non-whitespace word tokens. This is the foundational function used by the higher-level wrapping functions.

The function is optimized for performance with pre-allocated capacity estimates and does not allocate memory for spaces.

Usage Example:

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    text := "Hello world! How are you?"
    words := twwarp.SplitWords(text)
    fmt.Printf("Words: %v\n", words)
    // Output: Words: [Hello world! How are you?]

    // Handles multiple spaces
    text2 := "Multiple    spaces   between    words"
    words2 := twwarp.SplitWords(text2)
    fmt.Printf("Words: %v\n", words2)
    // Output: Words: [Multiple spaces between words]

    // Handles Unicode
    text3 := "日本語 English 混合"
    words3 := twwarp.SplitWords(text3)
    fmt.Printf("Words: %v\n", words3)
    // Output: Words: [日本語 English 混合]
}

Basic Text Wrapping

Wraps a string into lines with minimal raggedness, optimal for most text wrapping needs.

func WrapString(s string, lim int) ([]string, int)

Parameters:

  • s string - Input string to wrap
  • lim int - Maximum display width per line (in terminal columns)

Returns:

  • []string - Wrapped lines
  • int - Adjusted width limit (may be increased if a word exceeds lim)

Description:

WrapString is the primary text wrapping function that breaks text into lines using a minimal raggedness algorithm. It:

  1. Splits the input string into words using SplitWords
  2. Calculates display width for each word using twwidth.Width
  3. Automatically increases the line limit if any single word exceeds it
  4. Uses the Knuth-Plass-inspired algorithm (WrapWords) to minimize raggedness
  5. Joins wrapped word groups with spaces

The function handles edge cases:

  • Single space strings return []string{" "}
  • Empty word lists return []string{""}
  • Leading and trailing spaces are NOT preserved (use WrapStringWithSpaces for that)

Minimal Raggedness Algorithm:

The algorithm minimizes the total "raggedness" across all lines, where raggedness is measured as the squared difference between line length and the target width. This produces visually pleasing text layout by avoiding very short lines followed by very long lines.

Usage Example:

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Basic wrapping
    text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
    lines, width := twwarp.WrapString(text, 20)

    fmt.Printf("Wrapped to width %d:\n", width)
    for i, line := range lines {
        fmt.Printf("Line %d: [%s]\n", i+1, line)
    }
    // Output:
    // Wrapped to width 20:
    // Line 1: [Lorem ipsum dolor]
    // Line 2: [sit amet,]
    // Line 3: [consectetur]
    // Line 4: [adipiscing elit]

    // Automatic width adjustment for long words
    text2 := "This contains supercalifragilisticexpialidocious word"
    lines2, width2 := twwarp.WrapString(text2, 20)

    fmt.Printf("\nAdjusted width from 20 to %d:\n", width2)
    for i, line := range lines2 {
        fmt.Printf("Line %d: [%s]\n", i+1, line)
    }
    // Output:
    // Adjusted width from 20 to 34:
    // Line 1: [This contains]
    // Line 2: [supercalifragilisticexpialidocious]
    // Line 3: [word]
}

Text Wrapping with Space Preservation

Wraps text while preserving leading and trailing spaces, essential for formatted output.

func WrapStringWithSpaces(s string, lim int) ([]string, int)

Parameters:

  • s string - Input string to wrap
  • lim int - Maximum display width per line (in terminal columns)

Returns:

  • []string - Wrapped lines with preserved spacing
  • int - Adjusted width limit (may be increased if a word exceeds lim)

Description:

WrapStringWithSpaces provides space-aware text wrapping that preserves leading and trailing whitespace in the input string. This is critical for maintaining formatting in logs, code output, and other structured text.

The function:

  1. Identifies and preserves leading spaces (before first non-space character)
  2. Identifies and preserves trailing spaces (after last non-space character)
  3. Wraps the core content using the minimal raggedness algorithm
  4. Adds leading spaces to the first line
  5. Adds trailing spaces to the last line
  6. Handles all-whitespace strings specially

Special Cases:

  • All spaces shorter than limit: Returns the string as-is
  • All spaces longer than limit: Truncates to the display width limit
  • Empty strings: Returns []string{""}
  • Very long words: Automatically increases the width limit

This function is used internally by the tablewriter logging library to format log messages with consistent indentation and spacing.

Usage Example:

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Text with leading spaces (indentation)
    text := "    This is an indented paragraph that needs wrapping"
    lines, width := twwarp.WrapStringWithSpaces(text, 25)

    fmt.Printf("Wrapped with preserved leading spaces:\n")
    for i, line := range lines {
        fmt.Printf("Line %d: [%s]\n", i+1, line)
    }
    // Output:
    // Wrapped with preserved leading spaces:
    // Line 1: [    This is an indented]
    // Line 2: [paragraph that needs]
    // Line 3: [wrapping]

    // Text with trailing spaces
    text2 := "Some text   "
    lines2, _ := twwarp.WrapStringWithSpaces(text2, 30)

    fmt.Printf("\nWith trailing spaces:\n")
    for i, line := range lines2 {
        fmt.Printf("Line %d: [%s] (length: %d)\n", i+1, line, len(line))
    }
    // Output:
    // With trailing spaces:
    // Line 1: [Some text   ] (length: 12)

    // All-whitespace string
    spaces := "        "
    lines3, _ := twwarp.WrapStringWithSpaces(spaces, 10)

    fmt.Printf("\nAll spaces:\n")
    for i, line := range lines3 {
        fmt.Printf("Line %d: [%s] (length: %d)\n", i+1, line, len(line))
    }
    // Output:
    // All spaces:
    // Line 1: [        ] (length: 8)

    // Both leading and trailing spaces
    text3 := "  Content with spaces  "
    lines4, _ := twwarp.WrapStringWithSpaces(text3, 15)

    fmt.Printf("\nBoth leading and trailing:\n")
    for i, line := range lines4 {
        fmt.Printf("Line %d: [%s]\n", i+1, line)
    }
    // Output:
    // Both leading and trailing:
    // Line 1: [  Content with]
    // Line 2: [spaces  ]
}

Low-Level Line Breaking

Advanced line-breaking algorithm for custom text layout with fine-grained control.

func WrapWords(words []string, spc, lim, pen int) [][]string

Parameters:

  • words []string - Pre-split list of words to wrap
  • spc int - Display width of space between words (typically 1)
  • lim int - Maximum line width in terminal columns
  • pen int - Penalty for lines exceeding the limit (typically 100000)

Returns:

  • [][]string - Groups of words per line

Description:

WrapWords implements the low-level Knuth-Plass-inspired line-breaking algorithm used by WrapString and WrapStringWithSpaces. It provides direct control over the text wrapping process for advanced use cases.

Algorithm Details:

The algorithm uses dynamic programming to minimize total "raggedness" across all lines:

  1. Cost Calculation: For each possible line break, calculates the cost as the squared difference between line length and the limit
  2. Penalty Application: Lines exceeding the limit receive an additional penalty (default: 100,000)
  3. Optimal Path: Selects the sequence of line breaks that minimizes total cost
  4. Greedy Fallback: When all words fit on the last line, uses a greedy approach

Cost Function:

cost = (limit - line_length)² + cost_of_remaining_text
if line_length > limit:
    cost += penalty

This produces visually balanced text by avoiding extreme differences in line length.

Usage Example:

package main

import (
    "fmt"
    "strings"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Pre-split words
    words := []string{"The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"}

    // Standard wrapping: 1 space, 20 column limit, 100000 penalty
    lines := twwarp.WrapWords(words, 1, 20, 100000)

    fmt.Println("Standard wrapping:")
    for i, line := range lines {
        joined := strings.Join(line, " ")
        fmt.Printf("Line %d: [%s] (width: %d)\n", i+1, joined, len(joined))
    }
    // Output:
    // Standard wrapping:
    // Line 1: [The quick brown fox] (width: 19)
    // Line 2: [jumps over the lazy] (width: 19)
    // Line 3: [dog] (width: 3)

    // Custom spacing: 2 spaces between words
    lines2 := twwarp.WrapWords(words, 2, 20, 100000)

    fmt.Println("\nWith 2-space separation:")
    for i, line := range lines2 {
        joined := strings.Join(line, "  ") // Join with 2 spaces
        fmt.Printf("Line %d: [%s]\n", i+1, joined)
    }
    // Output:
    // With 2-space separation:
    // Line 1: [The  quick  brown]
    // Line 2: [fox  jumps  over]
    // Line 3: [the  lazy  dog]

    // Low penalty allows longer lines
    lines3 := twwarp.WrapWords(words, 1, 20, 10)

    fmt.Println("\nWith low penalty (allows longer lines):")
    for i, line := range lines3 {
        joined := strings.Join(line, " ")
        fmt.Printf("Line %d: [%s] (width: %d)\n", i+1, joined, len(joined))
    }
    // Output may show different breaking pattern with longer lines

    // Very tight limit forces more breaks
    lines4 := twwarp.WrapWords(words, 1, 10, 100000)

    fmt.Println("\nWith tight 10-column limit:")
    for i, line := range lines4 {
        joined := strings.Join(line, " ")
        fmt.Printf("Line %d: [%s] (width: %d)\n", i+1, joined, len(joined))
    }
    // Output:
    // With tight 10-column limit:
    // Line 1: [The quick] (width: 9)
    // Line 2: [brown fox] (width: 9)
    // Line 3: [jumps over] (width: 10)
    // Line 4: [the lazy] (width: 8)
    // Line 5: [dog] (width: 3)
}

Algorithm Details

Knuth-Plass-Inspired Minimal Raggedness

The text wrapping algorithm is inspired by the Knuth-Plass line breaking algorithm used in TeX, adapted for terminal output:

1. Dynamic Programming Approach

The algorithm uses dynamic programming to find optimal line breaks:

For each word position i:
  cost[i] = minimum cost to wrap words from i to end
  nbrk[i] = index of first word on next line

2. Cost Calculation

For each potential line from word i to word j:

phraseLength = sum(word_widths[i:j]) + (j-i-1) * space_width
difference = limit - phraseLength
cost = difference² + cost[j]  // Squared difference for raggedness

if phraseLength > limit:
    cost += penalty  // Penalize lines that exceed limit

3. Optimal Path Selection

The algorithm selects the line breaks that minimize total cost:

i = 0
while i < n:
    lines = append(lines, words[i:nbrk[i]])
    i = nbrk[i]

4. Why Squared Difference?

Using squared difference (rather than absolute difference) heavily penalizes very short or very long lines, producing more uniform line lengths. For example:

  • Line 5 columns short: cost = 25
  • Line 10 columns short: cost = 100 (4x worse, not 2x)

This creates visually balanced text with consistent line lengths.

5. Automatic Width Adjustment

Before running the algorithm, the implementation checks if any single word exceeds the limit and increases the limit accordingly. This ensures the algorithm always succeeds.

UAX#29 Word Segmentation

The SplitWords function uses Unicode Annex #29 word boundary detection, which correctly handles:

  • Multiple scripts: Latin, CJK, Arabic, Hebrew, etc.
  • Punctuation: Keeps punctuation with adjacent words
  • Apostrophes: "don't" is one word, not two
  • Abbreviations: "U.S.A." handled correctly
  • Emoji: Treats emoji as word boundaries

This is more sophisticated than simple space-splitting and produces better results for international text.

Common Use Cases

Table Cell Text Wrapping

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Simulate wrapping text in a 30-column table cell
    cellContent := "This is a long description that needs to fit in a table cell"
    maxCellWidth := 30

    lines, _ := twwarp.WrapString(cellContent, maxCellWidth)

    fmt.Println("Table cell content:")
    fmt.Println("┌" + strings.Repeat("─", maxCellWidth) + "┐")
    for _, line := range lines {
        fmt.Printf("│%-30s│\n", line)
    }
    fmt.Println("└" + strings.Repeat("─", maxCellWidth) + "┘")
}

Log Message Formatting

package main

import (
    "fmt"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Wrap log messages with preserved indentation
    logMessage := "    [INFO] This is a very long log message that needs to be wrapped while preserving the indentation level"

    lines, _ := twwarp.WrapStringWithSpaces(logMessage, 60)

    for _, line := range lines {
        fmt.Println(line)
    }
    // Output maintains the 4-space indentation on all lines
}

Multi-Column Text Layout

package main

import (
    "fmt"
    "strings"
    "github.com/olekukonko/tablewriter/pkg/twwarp"
)

func main() {
    // Create a two-column layout
    leftText := "This is the left column content that will be wrapped to fit"
    rightText := "This is the right column content that will also be wrapped"

    colWidth := 25

    leftLines, _ := twwarp.WrapString(leftText, colWidth)
    rightLines, _ := twwarp.WrapString(rightText, colWidth)

    // Pad to equal heights
    maxHeight := len(leftLines)
    if len(rightLines) > maxHeight {
        maxHeight = len(rightLines)
    }

    for i := 0; i < maxHeight; i++ {
        left := ""
        right := ""

        if i < len(leftLines) {
            left = leftLines[i]
        }
        if i < len(rightLines) {
            right = rightLines[i]
        }

        fmt.Printf("%-25s | %-25s\n", left, right)
    }
}

Performance Considerations

Display Width Calculation

The package uses twwidth.Width() which is optimized with an LRU cache. For repeated strings (common in tables), width calculation is O(1) after the first call.

Memory Allocation

  • SplitWords pre-allocates capacity based on estimated word count
  • The dynamic programming arrays in WrapWords are allocated once per call
  • No string copying except for the final result

Complexity

  • SplitWords: O(n) where n is string length
  • WrapWords: O(n²) where n is word count (dynamic programming)
  • WrapString: O(n + w²) where n is string length, w is word count

For typical table cell content (< 100 words), performance is excellent.

Edge Cases

Empty or Whitespace Strings

// Empty string
lines, _ := twwarp.WrapString("", 20)
// Returns: []string{""}

// Single space
lines, _ := twwarp.WrapString(" ", 20)
// Returns: []string{" "}

// All spaces (short)
lines, _ := twwarp.WrapStringWithSpaces("    ", 20)
// Returns: []string{"    "}

// All spaces (long)
lines, _ := twwarp.WrapStringWithSpaces("                      ", 10)
// Returns: []string{"          "} (truncated to 10 columns)

Unicode and Emoji

// East Asian characters (2 columns each)
lines, width := twwarp.WrapString("你好世界", 5)
// width = 8 (auto-adjusted), lines = ["你好世界"] (8 columns)

// Emoji (typically 2 columns)
lines, _ := twwarp.WrapString("Hello 😀 World", 10)
// Returns: ["Hello 😀", "World"]

// Combining characters
lines, _ := twwarp.WrapString("café", 5)
// Returns: ["café"] (correctly measures é as 1 column)

Very Long Words

// Word longer than limit
text := "supercalifragilisticexpialidocious"
lines, adjustedWidth := twwarp.WrapString(text, 10)
// adjustedWidth = 34 (length of word)
// lines = ["supercalifragilisticexpialidocious"]

Thread Safety

All functions in the twwarp package are thread-safe:

  • No mutable shared state
  • Uses only local variables and parameters
  • The underlying twwidth.Width() function uses a thread-safe LRU cache

Multiple goroutines can safely call wrapping functions concurrently.

Dependencies

The twwarp package depends on:

  • github.com/clipperhouse/uax29/v2/graphemes - UAX#29 grapheme cluster segmentation
  • github.com/olekukonko/tablewriter/pkg/twwidth - Display width calculation

These dependencies are automatically installed when you import tablewriter.

Related Packages

  • twwidth - Display width calculation with ANSI support and caching
  • tw - Core tablewriter types and interfaces
  • tablewriter - Main table rendering package that uses twwarp for cell wrapping