// 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 {
}
} // 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 {
List<String> 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);
List<String> 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(' ');
}
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));
+ }
+
}