module TermBuf::Unicode

Overview

Character property lookup for the terminal buffer: display width, grapheme cluster break class, and the two auxiliary properties UAX #29 needs.

Every code point falls into exactly one range of Tables::RANGE_START, so a lookup is a binary search with no miss case. Code points below FAST_LIMIT skip the search entirely via a direct-indexed table built at startup, which covers Latin, Greek, Cyrillic, Hebrew, Arabic, the combining diacriticals, and most of the Indic scripts.

Defined in:

termbuf/unicode/grapheme.cr
termbuf/unicode/overrides.cr
termbuf/unicode/policy.cr
termbuf/unicode/tables.cr
termbuf/unicode/utf8.cr
termbuf/unicode/width.cr

Constant Summary

EMOJI_PRESENTATION = '️'

Variation selector 16, forcing emoji presentation and so a width of two.

FAST_LIMIT = 4096

Code points below this are resolved by direct index rather than search.

TEXT_PRESENTATION = '︎'

Variation selector 15, forcing text presentation and so a width of one.

ZERO_WIDTH_JOINER = '\u200D'

Joins emoji into one cluster, and on most terminals into one glyph.

Class Method Summary

Class Method Detail

def self.ambiguous?(char : Char) : Bool #

Whether char is East Asian Ambiguous, and so rendered at a width the terminal's configuration decides.


[View source]
def self.ambiguous_width : Int32 #

Cells an East Asian Ambiguous character takes. Reads through to .policy, which is where the answer lives now that it can be measured rather than assumed.


[View source]
def self.char_width(char : Char, ambiguous : Int32 = ambiguous_width) : Int32 #

Number of terminal cells char occupies on its own, ignoring any grapheme cluster it may belong to. Control characters report zero; they are never stored in the buffer.


[View source]
def self.conjunct_class(char : Char) : Tables::Incb #

Indic conjunct break class of char, used by rule GB9c.


[View source]
def self.each_grapheme(string : String, policy : WidthPolicy = Unicode.policy, & : Grapheme -> ) : Nil #

Yields each extended grapheme cluster of string in order, without allocating: a Grapheme locates its cluster by byte offset.


[View source]
def self.grapheme_class(char : Char) : Tables::Gcb #

Grapheme cluster break class of char (UAX #29).


[View source]
def self.graphemes(string : String) : Array(String) #

All extended grapheme clusters of string, as separate strings. Convenient for specs and one-off work; the buffer uses .each_grapheme instead.


[View source]
def self.pictographic?(char : Char) : Bool #

Whether char has the Extended_Pictographic property, used by rule GB11.


[View source]
def self.policy : WidthPolicy #

How clusters are measured when no policy is given. Buffer carries its own, set from what the terminal said when asked; this is for casual callers and for .char_width, which has no cluster to consult.


[View source]
def self.policy=(policy : WidthPolicy) #

How clusters are measured when no policy is given. Buffer carries its own, set from what the terminal said when asked; this is for casual callers and for .char_width, which has no cluster to consult.


[View source]
def self.properties(codepoint : Int32) : UInt16 #

Packed property word for codepoint.


[View source]
def self.string_width(string : String, policy : WidthPolicy = Unicode.policy) : Int32 #

Total number of terminal cells string occupies, under policy.


[View source]
def self.utf8_continuation?(byte : UInt8) : Bool #

Whether byte continues a character rather than beginning one.


[View source]
def self.utf8_length(lead : UInt8) : Int32 #

How many bytes the character starting with lead takes, or zero if lead is not a lead byte at all.

The over-long and surrogate ranges are excluded, so a byte this accepts begins a character that can exist.


[View source]
def self.utf8_prefix(bytes : Bytes) : Int32 #

How much of bytes forms whole characters.

A write can end anywhere, including the middle of a character, so the tail this leaves out has to be held until the rest of it arrives.


[View source]