string_width

Measure

line, line_with, dimensions, dimensions_with

Layout

limit, limit_with, position, position_with, align, align_with, tabs_to_spaces, tabs_to_spaces_with

Options Builder

new, ambiguous_as_wide, count_ansi_codes, mode_2027, mode_2027_ext, mode_wcwidth, at_tab_offset, with_tab_width

Advanced

fold, fold_with, fold_raw, is_ansi_component

Types

Control the text or box alignment on the horizontal axis.

pub type Alignment {
  Left
  Center
  Right
}

Constructors

  • Left
  • Center
  • Right

Options to change the default behaviour of the functions in this library. If you are unsure what to do here, the defaults should work great!

pub opaque type Options

The measured pieces of a string, passed to you by fold and fold_with

pub type Piece {
  Piece(piece: String, row: Int, column: Int, width: Int)
}

Constructors

  • Piece(piece: String, row: Int, column: Int, width: Int)

Control the box alignment on the vertical axis.

pub type Placement {
  Top
  Middle
  Bottom
}

Constructors

  • Top
  • Middle
  • Bottom

A custom type making it easy to differentiate the width (columns) and height (rows).

pub type Size {
  Size(rows: Int, columns: Int)
}

Constructors

  • Size(rows: Int, columns: Int)

Functions

pub fn align(
  str: String,
  to max_width: Int,
  align alignment: Alignment,
  with space: String,
) -> String

Align each line horizontally, using a spacer character or string as a filler. Each line of the return value will be at least max_width wide.

If a line is already longer than the max width, it will not be changed. If the space strings width does not evenly divide the missing amount of columns, the extra spacer will overflow the max width.

Examples

align("hello", to: 10, align: Left, with: " ")
// --> "hello     "

align("12345", to: 8, align: Right, with: "0")
// --> "00012345"

align(" Welcome ", to: 20, align: Center, with: "==")
// --> "====== Welcome ======" // (len = 21)
pub fn align_with(
  str: String,
  to max_width: Int,
  align alignment: Alignment,
  using options: Options,
  with space: String,
) -> String

Like align, bu use custom options for measure.

pub fn ambiguous_as_wide(options: Options) -> Options

Some characters are marked by Unicode as “ambiguous”, meaning they may occupy 1 or 2 cells, depending on the context, current language, selected font, surrounding text, and more.

Unicode recommends treating these characters as narrow by default, but you can change this behaviour using this option.

pub fn at_tab_offset(
  options: Options,
  tab_offset: Int,
) -> Options

Define an offset for tab stop calculations. (Default: 0)

If the text you print doesn’t start at the first column, but is instead indented somehow, you can set this option to this number to make sure tab stops are correctly calculated.

pub fn count_ansi_codes(options: Options) -> Options

Do not ignore ansi escape sequences, and count them as regular characters.

You can enable this option as an optimisation if you are sure that your string doesn’t contain any ansi escape codes.

pub fn dimensions(str: String) -> Size

The required number of rows and columns to display the given text.

The number of rows is equal to the number of lines, while the number of columns represents the maximum line width.

Examples

dimensions("안녕하세요")
// --> Size(rows: 1, columns: 10)

dimensions("hello,\n안녕하세요")
// --> Size(rows: 2, columns: 10)
pub fn dimensions_with(str: String, options: Options) -> Size

Like dimensions, but use custom options.

pub fn fold(
  over str: String,
  from state: a,
  with fun: fn(a, Piece) -> a,
) -> a

A higher-level fold that keeps track of the position inside of the string.

Handles tabs and newlines, and always passes full grapheme clusters, regardless of options. Concatenating the graphemes produces the original string.

Intended to be used as a basis for custom layout algorithms.

pub fn fold_raw(
  over string: String,
  using options: Options,
  from state: a,
  with fun: fn(a, String, Int) -> a,
) -> a

A low-level fold that iterate over the measured components of a string. Components are either graphemes or codepoints depending on the mode option, or other undivisible sequences, like ANSI escape codes.

This function does not by itself keep track of any additional state, and doesn’t for example handle newlines or tabs like fold would. If you know that your algorithm is safe and won’t break up graphemes (or maybe this is acceptable), you can use this fold variant instead as a performance optimisation.

Concatenating all components is guaranteed to produce the original string.

Examples

// A slower string_width.line implementation that doesn't handle tabs
fold_raw("hello", new(), from: 0, with: fn(so_far, _chr, width) { width + so_far })
// --> 5
// truncate a string after 50 characters, but keep all ansi sequences.
use #(total, acc), chr, width <- fold_raw(input, new(), from: #(0, ""))
case total >= 50 {
  True -> case chr {
    "\u{1b}" <> _ -> #(total, acc <> chr)
     _ -> #(total, acc)
  }

  False -> #(total + width, acc <> chr)
}
pub fn fold_with(
  over str: String,
  using options: Options,
  from state: a,
  with fun: fn(a, Piece) -> a,
) -> a

A higher-level fold that keeps track of the position inside of the string

Handles tabs and newlines, and always passes full grapheme clusters, regardless of options. Concatenating the graphemes produces the original string.

Intended to be used as a basis for custom layout algorithms.

pub fn is_ansi_component(str: String, options: Options) -> Bool

Returns true if a given component string recieved in fold is an ANSI escape sequence.

pub fn limit(
  str: String,
  to max_size: Size,
  ellipsis ellipsis: String,
) -> String

Limit the dimensions of a string, either by wrapping on white space characters, or by truncating the last line and appending an ellipsis.

The lines of the resulting string are left aligned and are at most columns wide. If a single word is longer than the maximum allowed line width, the word is broken into 2 pieces at the grapheme level. If the string gets truncated, this function will keep collecting ANSI sequences to make sure all formatting is preserved.

A commonly used ellipsis character is "…", also known as U+2026 HORIZONTAL ELLIPSIS.

Examples

limit("Hello World", Size(rows: 1, columns: 10), ellipsis: "...")
// --> "Hello W..."

limit("Hello World", Size(rows: 2, columns: 5), ellipsis: "...")
// --> "Hello\nWorld"
pub fn limit_with(
  str: String,
  to max_size: Size,
  using options: Options,
  ellipsis ellipsis: String,
) -> String

Like limit, but also customise the options used for measuring.

pub fn line(str: String) -> Int

Get the number of columns required to print a line in a terminal.

If multiple lines are given, the length of the longest line is returned.

Examples

line("äöüè")
// --> 4

line("안녕하세요")
// --> 10

line("👩‍👩‍👦‍👦")
// --> 8

line("\u{1B}[31mhello\u{1B}[39m")
// --> 5
pub fn line_with(str: String, options: Options) -> Int

Like line, but use custom options.

Example

let options =
  new()
  |> mode

line_with("👩‍👩‍👦‍👦", options)
// --> 2
pub fn mode_2027(options: Options) -> Options

Measure grapheme clusters instead of individual code points.

Most terminal emulators do not handle grapheme clusters well and will instead show their decomposition. To make sure a given string always fits even on those terminals, the functions in this package will copy this behaviour as well.

If you enable this option, the returned numbers will more accurately represent the width of a string on a website or in an editor.

NOTE: Grapheme support in terminals is highly experimental and subject to ongoing discussion! When working on CLIs or TUIs, you do not want to use this option most of the time.

See also Grapheme Clusters and Terminal Emulators for a better explanation on how terminals behave.

pub fn mode_2027_ext(options: Options) -> Options

Measure grapheme clusters instead of individual code points, and always treat grapheme clusters with more than one non-modifier character as wide.

This is a custom extension to Unicode Core. Many single grapheme clusters would currently still render as wide glyphs in many contexts, since font shaping would not be able to collapse them into a single narrow “ligature”.

This mode is an additional heuristic that tries to handle more such cases.

See mode_2027 for more explanation on the implications.

pub fn mode_wcwidth(options: Options) -> Options

The default mode and the mode suitable for most terminals.

Measures individual code points, similar to the libc wcwidth function that almost all of these terminals use.

Note that except for fold_raw, this library will still always process grapheme clusters as a unit.

pub fn new() -> Options

Start building up new options.

pub fn position(
  str: String,
  in bounding_box: Size,
  align alignment: Alignment,
  place placement: Placement,
  with space: String,
) -> String

Position the string area inside a bigger box without changing text alignment. The box will be filled with the space character.

If a line is already bigger than the maximum width, it will not be changed. If there are more lines than the maximum amount of rows, the extra lines will still be kept. If the space strings’ width does not evenly divide the missing amount of columns, the extra spacer will overflow the max width.

Examples

position("X", in: Size(3, 3), align: Center, place: Middle, with: "o")
// --> "ooo\noXo\nooo"
pub fn position_with(
  str: String,
  in bounding_box: Size,
  horizontal alignment: Alignment,
  vertical placement: Placement,
  using options: Options,
  with space: String,
) -> String

Like position, but use custom options to measure each line.

pub fn tabs_to_spaces(str: String) -> String

Replace all tab characters found in the string with the amount of spaces that this tab would have had otherwise.

This makes it safe to prepend to a line, without changing the spacing produced by tabs anymore.

Examples

tabs_to_spaces("Hello\tWorld")
// --> "Hello   World" // 3 spaces
pub fn tabs_to_spaces_with(
  str: String,
  using options: Options,
) -> String

Like tabs_to_spaces, but customise the opttions as well.

NB: You can use at_tab_offset and with_tab_width to change the measure of tab characters inside the string!

let options = new() |> with_tab_width(4)
tabs_to_spaces_with("hi\tcutie~", options)
// --> "hi  cutie~"
pub fn with_tab_width(
  options: Options,
  tab_width: Int,
) -> Options

Change the number of columns between tab stops. (Default: 8)

Whenever a tab character \t is encountered, the current column number will be rounded up to the next multiple of this number.

Search Document