X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Fbits%2FStringUtils.java;h=fffce206875cf663480d2041aac121f88b58d01a;hb=9892fdaafb368227aa9630ab3abe6ed10bb1d001;hp=a98756ed22950a5f3eb359f36f24438448078d0e;hpb=656c0dddc7c0faddd62d373f22916107d322429e;p=fanfix.git diff --git a/src/jexer/bits/StringUtils.java b/src/jexer/bits/StringUtils.java index a98756e..fffce20 100644 --- a/src/jexer/bits/StringUtils.java +++ b/src/jexer/bits/StringUtils.java @@ -80,14 +80,14 @@ public class StringUtils { // We have just transitioned from a word to // whitespace. See if we have enough space to add // the word to the line. - if (word.length() + line.length() > n) { + if (width(word.toString()) + width(line.toString()) > n) { // This word will exceed the line length. Wrap // at it instead. result.add(line.toString()); line = new StringBuilder(); } if ((word.toString().startsWith(" ")) - && (line.length() == 0) + && (width(line.toString()) == 0) ) { line.append(word.substring(1)); } else { @@ -112,14 +112,14 @@ public class StringUtils { } } // for (int j = 0; j < rawLines[i].length(); j++) - if (word.length() + line.length() > n) { + if (width(word.toString()) + width(line.toString()) > n) { // This word will exceed the line length. Wrap at it // instead. result.add(line.toString()); line = new StringBuilder(); } if ((word.toString().startsWith(" ")) - && (line.length() == 0) + && (width(line.toString()) == 0) ) { line.append(word.substring(1)); } else { @@ -148,7 +148,7 @@ public class StringUtils { List lines = left(str, n); for (String line: lines) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < n - line.length(); i++) { + for (int i = 0; i < n - width(line); i++) { sb.append(' '); } sb.append(line); @@ -175,8 +175,8 @@ public class StringUtils { List lines = left(str, n); for (String line: lines) { StringBuilder sb = new StringBuilder(); - int l = (n - line.length()) / 2; - int r = n - line.length() - l; + int l = (n - width(line)) / 2; + int r = n - width(line) - l; for (int i = 0; i < l; i++) { sb.append(' '); } @@ -404,4 +404,95 @@ public class StringUtils { return result.toString(); } + /** + * Determine display width of a Unicode code point. + * + * @param ch the code point, can be char + * @return the number of text cell columns required to display this code + * point, one of 0, 1, or 2 + */ + public static int width(final int ch) { + /* + * This routine is a modified version of mk_wcwidth() available + * at: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + * + * The combining characters list has been omitted from this + * implementation. Hopefully no users will be impacted. + */ + + // 8-bit control characters: width 0 + if (ch == 0) { + return 0; + } + if ((ch < 32) || ((ch >= 0x7f) && (ch < 0xa0))) { + return 0; + } + + // All others: either 1 or 2 + if ((ch >= 0x1100) + && ((ch <= 0x115f) + // Hangul Jamo init. consonants + || (ch == 0x2329) + || (ch == 0x232a) + // CJK ... Yi + || ((ch >= 0x2e80) && (ch <= 0xa4cf) && (ch != 0x303f)) + // Hangul Syllables + || ((ch >= 0xac00) && (ch <= 0xd7a3)) + // CJK Compatibility Ideographs + || ((ch >= 0xf900) && (ch <= 0xfaff)) + // Vertical forms + || ((ch >= 0xfe10) && (ch <= 0xfe19)) + // CJK Compatibility Forms + || ((ch >= 0xfe30) && (ch <= 0xfe6f)) + // Fullwidth Forms + || ((ch >= 0xff00) && (ch <= 0xff60)) + || ((ch >= 0xffe0) && (ch <= 0xffe6)) + || ((ch >= 0x20000) && (ch <= 0x2fffd)) + || ((ch >= 0x30000) && (ch <= 0x3fffd)) + // emoji + || ((ch >= 0x1f004) && (ch <= 0x1fffd)) + ) + ) { + return 2; + } + return 1; + } + + /** + * Determine display width of a string. This ASSUMES that no characters + * are combining. Hopefully no users will be impacted. + * + * @param str the string + * @return the number of text cell columns required to display this string + */ + public static int width(final String str) { + int n = 0; + for (int i = 0; i < str.length();) { + int ch = str.codePointAt(i); + n += width(ch); + i += Character.charCount(ch); + } + return n; + } + + /** + * Check if character is in the CJK range. + * + * @param ch character to check + * @return true if this character is in the CJK range + */ + public static boolean isCjk(final int ch) { + return ((ch >= 0x2e80) && (ch <= 0x9fff)); + } + + /** + * Check if character is in the emoji range. + * + * @param ch character to check + * @return true if this character is in the emoji range + */ + public static boolean isEmoji(final int ch) { + return ((ch >= 0x1f004) && (ch <= 0x1fffd)); + } + }