Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Commit

Permalink
Merge pull request #9 from bcho/fix/cjk-width
Browse files Browse the repository at this point in the history
Fix miscalculation of cjk characters.
  • Loading branch information
Robpol86 committed Aug 24, 2015
2 parents 3b7987d + dc9ae96 commit 14b9dd4
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 6 deletions.
41 changes: 35 additions & 6 deletions terminaltables.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import re
import struct
import sys
import unicodedata

try:
import fcntl
Expand All @@ -37,6 +38,33 @@
__version__ = '1.2.0'


def _get_width(string):
"""Get the real width of unicode string.
Positional arguments:
string -- string.
Returns:
String width.
"""
if hasattr(string, 'value_no_colors'):
# Colorclass instance.
string = string.value_no_colors

if isinstance(string, str) and hasattr(string, 'decode'):
# Convert to unicode.
string = string.decode('u8')

width = 0
for char in string:
if unicodedata.east_asian_width(char) in ('F', 'W'):
width = width + 2
else:
width = width + 1

return width


def _align_and_pad(input_, align, width, height, lpad, rpad):
"""Align a string with center/rjust/ljust and adds additional padding.
Expand All @@ -57,12 +85,13 @@ def _align_and_pad(input_, align, width, height, lpad, rpad):
lines.append('')

# Align.

if align == 'center':
aligned = '\n'.join(l.center(width) for l in lines)
aligned = '\n'.join(l.center(width + len(l) - _get_width(l)) for l in lines)
elif align == 'right':
aligned = '\n'.join(l.rjust(width) for l in lines)
aligned = '\n'.join(l.rjust(width + len(l) - _get_width(l)) for l in lines)
else:
aligned = '\n'.join(l.ljust(width) for l in lines)
aligned = '\n'.join(l.ljust(width + len(l) - _get_width(l)) for l in lines)

# Pad.
padded = '\n'.join((' ' * lpad) + l + (' ' * rpad) for l in aligned.splitlines() or [''])
Expand Down Expand Up @@ -208,7 +237,7 @@ def column_widths(self):
for i in range(len(row)):
if not row[i]:
continue
widths[i] = max(widths[i], len(max(row[i].splitlines(), key=len)))
widths[i] = max(widths[i], _get_width(max(row[i].splitlines(), key=len)))

return widths

Expand Down Expand Up @@ -250,14 +279,14 @@ def table(self):

# Append top border.
max_title = sum(column_widths) + ((len(column_widths) - 1) if self.inner_column_border else 0)
if self.outer_border and self.title and len(self.title) <= max_title:
if self.outer_border and self.title and _get_width(self.title) <= max_title:
pseudo_row = _convert_row(['h' * w for w in column_widths],
'l', 't' if self.inner_column_border else '', 'r')
pseudo_row_key = dict(h=self.CHAR_HORIZONTAL, l=self.CHAR_CORNER_UPPER_LEFT, t=self.CHAR_INTERSECT_TOP,
r=self.CHAR_CORNER_UPPER_RIGHT)
pseudo_row_re = re.compile('({0})'.format('|'.join(pseudo_row_key.keys())))
substitute = lambda s: pseudo_row_re.sub(lambda x: pseudo_row_key[x.string[x.start():x.end()]], s)
row = substitute(pseudo_row[:1]) + self.title + substitute(pseudo_row[1 + len(self.title):])
row = substitute(pseudo_row[:1]) + self.title + substitute(pseudo_row[1 + _get_width(self.title):])
final_table_data.append(row)
elif self.outer_border:
row = _convert_row([self.CHAR_HORIZONTAL * w for w in column_widths],
Expand Down
19 changes: 19 additions & 0 deletions tests/test_get_width.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# coding: utf-8
"""Tests for character length calculating."""

from colorclass import Color

from terminaltables import _get_width


def test_get_width():
"""Test characters width."""
testcases = [
('hello, world', 12),
('世界你好', 8),
('hello 世界', 10),
(Color(u'{autoblue}蓝色{/autoblue}'), 4),
]

for string, expected_length in testcases:
assert _get_width(string) == expected_length

0 comments on commit 14b9dd4

Please sign in to comment.