package runewidth
import (
"os"
"strings"
"github.com/rivo/uniseg"
)
var (
EastAsianWidth bool
StrictEmojiNeutral bool = true
DefaultCondition = &Condition {
EastAsianWidth : false ,
StrictEmojiNeutral : true ,
}
)
func init() {
handleEnv ()
}
func handleEnv() {
env := os .Getenv ("RUNEWIDTH_EASTASIAN" )
if env == "" {
EastAsianWidth = IsEastAsian ()
} else {
EastAsianWidth = env == "1"
}
if DefaultCondition .EastAsianWidth != EastAsianWidth {
DefaultCondition .EastAsianWidth = EastAsianWidth
if len (DefaultCondition .combinedLut ) > 0 {
DefaultCondition .combinedLut = DefaultCondition .combinedLut [:0 ]
CreateLUT ()
}
}
}
type interval struct {
first rune
last rune
}
type table []interval
func inTables(r rune , ts ...table ) bool {
for _ , t := range ts {
if inTable (r , t ) {
return true
}
}
return false
}
func inTable(r rune , t table ) bool {
if r < t [0 ].first {
return false
}
bot := 0
top := len (t ) - 1
for top >= bot {
mid := (bot + top ) >> 1
switch {
case t [mid ].last < r :
bot = mid + 1
case t [mid ].first > r :
top = mid - 1
default :
return true
}
}
return false
}
var private = table {
{0x00E000 , 0x00F8FF }, {0x0F0000 , 0x0FFFFD }, {0x100000 , 0x10FFFD },
}
var nonprint = table {
{0x0000 , 0x001F }, {0x007F , 0x009F }, {0x00AD , 0x00AD },
{0x070F , 0x070F }, {0x180B , 0x180E }, {0x200B , 0x200F },
{0x2028 , 0x202E }, {0x206A , 0x206F }, {0xD800 , 0xDFFF },
{0xFEFF , 0xFEFF }, {0xFFF9 , 0xFFFB }, {0xFFFE , 0xFFFF },
}
type Condition struct {
combinedLut []byte
EastAsianWidth bool
StrictEmojiNeutral bool
}
func NewCondition () *Condition {
return &Condition {
EastAsianWidth : EastAsianWidth ,
StrictEmojiNeutral : StrictEmojiNeutral ,
}
}
func (c *Condition ) RuneWidth (r rune ) int {
if r < 0 || r > 0x10FFFF {
return 0
}
if len (c .combinedLut ) > 0 {
return int (c .combinedLut [r >>1 ]>>(uint (r &1 )*4 )) & 3
}
if !c .EastAsianWidth {
switch {
case r < 0x20 :
return 0
case (r >= 0x7F && r <= 0x9F ) || r == 0xAD :
return 0
case r < 0x300 :
return 1
case inTable (r , narrow ):
return 1
case inTables (r , nonprint , combining ):
return 0
case inTable (r , doublewidth ):
return 2
default :
return 1
}
} else {
switch {
case inTables (r , nonprint , combining ):
return 0
case inTable (r , narrow ):
return 1
case inTables (r , ambiguous , doublewidth ):
return 2
case !c .StrictEmojiNeutral && inTables (r , ambiguous , emoji , narrow ):
return 2
default :
return 1
}
}
}
func (c *Condition ) CreateLUT () {
const max = 0x110000
lut := c .combinedLut
if len (c .combinedLut ) != 0 {
c .combinedLut = nil
} else {
lut = make ([]byte , max /2 )
}
for i := range lut {
i32 := int32 (i * 2 )
x0 := c .RuneWidth (i32 )
x1 := c .RuneWidth (i32 + 1 )
lut [i ] = uint8 (x0 ) | uint8 (x1 )<<4
}
c .combinedLut = lut
}
func (c *Condition ) StringWidth (s string ) (width int ) {
g := uniseg .NewGraphemes (s )
for g .Next () {
var chWidth int
for _ , r := range g .Runes () {
chWidth = c .RuneWidth (r )
if chWidth > 0 {
break
}
}
width += chWidth
}
return
}
func (c *Condition ) Truncate (s string , w int , tail string ) string {
if c .StringWidth (s ) <= w {
return s
}
w -= c .StringWidth (tail )
var width int
pos := len (s )
g := uniseg .NewGraphemes (s )
for g .Next () {
var chWidth int
for _ , r := range g .Runes () {
chWidth = c .RuneWidth (r )
if chWidth > 0 {
break
}
}
if width +chWidth > w {
pos , _ = g .Positions ()
break
}
width += chWidth
}
return s [:pos ] + tail
}
func (c *Condition ) TruncateLeft (s string , w int , prefix string ) string {
if c .StringWidth (s ) <= w {
return prefix
}
var width int
pos := len (s )
g := uniseg .NewGraphemes (s )
for g .Next () {
var chWidth int
for _ , r := range g .Runes () {
chWidth = c .RuneWidth (r )
if chWidth > 0 {
break
}
}
if width +chWidth > w {
if width < w {
_, pos = g .Positions ()
prefix += strings .Repeat (" " , width +chWidth -w )
} else {
pos , _ = g .Positions ()
}
break
}
width += chWidth
}
return prefix + s [pos :]
}
func (c *Condition ) Wrap (s string , w int ) string {
width := 0
out := ""
for _ , r := range s {
cw := c .RuneWidth (r )
if r == '\n' {
out += string (r )
width = 0
continue
} else if width +cw > w {
out += "\n"
width = 0
out += string (r )
width += cw
continue
}
out += string (r )
width += cw
}
return out
}
func (c *Condition ) FillLeft (s string , w int ) string {
width := c .StringWidth (s )
count := w - width
if count > 0 {
b := make ([]byte , count )
for i := range b {
b [i ] = ' '
}
return string (b ) + s
}
return s
}
func (c *Condition ) FillRight (s string , w int ) string {
width := c .StringWidth (s )
count := w - width
if count > 0 {
b := make ([]byte , count )
for i := range b {
b [i ] = ' '
}
return s + string (b )
}
return s
}
func RuneWidth (r rune ) int {
return DefaultCondition .RuneWidth (r )
}
func IsAmbiguousWidth (r rune ) bool {
return inTables (r , private , ambiguous )
}
func IsNeutralWidth (r rune ) bool {
return inTable (r , neutral )
}
func StringWidth (s string ) (width int ) {
return DefaultCondition .StringWidth (s )
}
func Truncate (s string , w int , tail string ) string {
return DefaultCondition .Truncate (s , w , tail )
}
func TruncateLeft (s string , w int , prefix string ) string {
return DefaultCondition .TruncateLeft (s , w , prefix )
}
func Wrap (s string , w int ) string {
return DefaultCondition .Wrap (s , w )
}
func FillLeft (s string , w int ) string {
return DefaultCondition .FillLeft (s , w )
}
func FillRight (s string , w int ) string {
return DefaultCondition .FillRight (s , w )
}
func CreateLUT () {
if len (DefaultCondition .combinedLut ) > 0 {
return
}
DefaultCondition .CreateLUT ()
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .