/// <summary> /// 返回指定字符的宽度,字符串遍历的顺序是从后至前的。 /// </summary> /// <param name="ch">要获取宽度的字符。</param> /// <returns><paramref name="ch"/> 的宽度。</returns> private int GetWidthReversed(char ch) { if (!char.IsSurrogate(ch)) { return(ch.Width()); } if (char.IsLowSurrogate(ch)) { surrogate = ch; return(0); } // ch 是高代理项 if (surrogate == NoSurrogate) { // 缺失相应低代理项,忽略无效数据。 return(0); } var chValue = char.ConvertToUtf32(ch, surrogate); surrogate = NoSurrogate; return(CharExt.Width(chValue)); }
/// <summary> /// 返回指定字符的宽度。 /// </summary> /// <param name="ch">要获取宽度的字符。</param> /// <returns><paramref name="ch"/> 的宽度。</returns> private int GetWidth(char ch) { if (!char.IsSurrogate(ch)) { return(ch.Width()); } if (char.IsHighSurrogate(ch)) { surrogate = ch; return(0); } // ch 是低代理项 if (surrogate == NoSurrogate) { // 缺失相应高代理项,忽略无效数据。 return(0); } var chValue = char.ConvertToUtf32(surrogate, ch); surrogate = NoSurrogate; return(CharExt.Width(chValue)); }
/// <summary> /// 令位置前进指定字符指针所指的部分。 /// </summary> /// <param name="start">要前进的起始字符指针(包含)。</param> /// <param name="end">要前进的结束字符指针(包含)。</param> /// <remarks>不会修改 <c>curIndex</c> 属性。</remarks> private unsafe void ForwardInternal(char *start, char *end) { Contract.Requires(end > start); Contract.Requires(widths.Count == 0); var oldHighSurrogate = surrogate; surrogate = NoSurrogate; // 最后一个字符需要特殊考虑,否则无法得到当前位置。 var lastWidth = 0; var lastChar = *end--; if (!char.IsHighSurrogate(lastChar)) { if (lastChar == '\n') { lastWidth = -1; } else if (lastChar == '\t') { lastWidth = -2; } else if (char.IsLowSurrogate(lastChar)) { if (char.IsHighSurrogate(*end)) { // 匹配的代理项对。 lastWidth = CharExt.Width(char.ConvertToUtf32(*end, lastChar)); end--; } } else { lastWidth = lastChar.Width(); } lastChar = NoSurrogate; } curCol = nextCol; curLine = nextLine; var width = 0; for (; end >= start; end--) { if (*end == '\n') { widths.Add(width); oldHighSurrogate = NoSurrogate; // 统计之前的行数。 curCol = 1; curLine++; for (; end >= start; end--) { if (*end == '\n') { curLine++; } } break; } if (*end == '\t') { widths.Add(width); width = 0; } else { width += GetWidthReversed(*end); } } if (oldHighSurrogate != NoSurrogate) { width += GetWidthReversed(oldHighSurrogate); } if (width > 0) { widths.Add(width); } // 统计列数。 for (var j = widths.Count - 1; j > 0; j--) { curCol += widths[j]; curCol = ForwardTab(curCol); } surrogate = lastChar; curCol += widths[0]; widths.Clear(); nextLine = curLine; if (lastWidth >= 0) { nextCol = curCol + lastWidth; } else if (lastWidth == -1) { nextCol = 1; nextLine++; } else { nextCol = ForwardTab(curCol); } }
public void TestWidth() { // 宽字符范围: // 0x1100 ~ 0x115F:Hangul Jamo init. consonants Assert.AreEqual(1, '\u10FF'.Width()); Assert.AreEqual(2, '\u1100'.Width()); Assert.AreEqual(2, '\u1101'.Width()); Assert.AreEqual(2, '\u115E'.Width()); Assert.AreEqual(2, '\u115F'.Width()); Assert.AreEqual(1, '\u1160'.Width()); // 0x2329, 0x232A:左右尖括号〈〉 Assert.AreEqual(1, '\u2328'.Width()); Assert.AreEqual(2, '\u2329'.Width()); Assert.AreEqual(2, '\u232A'.Width()); Assert.AreEqual(1, '\u232B'.Width()); // 0x2E80 ~ 0xA4CF 除了 0x303F:CJK ... YI Assert.AreEqual(1, '\u2E7F'.Width()); Assert.AreEqual(2, '\u2E80'.Width()); Assert.AreEqual(2, '\u2E81'.Width()); Assert.AreEqual(2, '\u303E'.Width()); Assert.AreEqual(1, '\u303F'.Width()); Assert.AreEqual(2, '\u3040'.Width()); Assert.AreEqual(2, '\uA4CE'.Width()); Assert.AreEqual(2, '\uA4CF'.Width()); Assert.AreEqual(1, '\uA4D0'.Width()); // 0xAC00 ~ 0xD7A3:Hangul Syllables Assert.AreEqual(1, '\uABFF'.Width()); Assert.AreEqual(2, '\uAC00'.Width()); Assert.AreEqual(2, '\uD7A3'.Width()); Assert.AreEqual(1, '\uD7A4'.Width()); // 0xF900 ~ 0xFAFF:CJK Compatibility Ideographs Assert.AreEqual(1, '\uF8FF'.Width()); Assert.AreEqual(2, '\uF900'.Width()); Assert.AreEqual(2, '\uFAFF'.Width()); Assert.AreEqual(1, '\uFB00'.Width()); // 0xFE10 ~ 0xFE19:Vertical forms Assert.AreEqual(0, '\uFE0F'.Width()); // '\uFE0F' 是 NonSpacingMark Assert.AreEqual(2, '\uFE10'.Width()); Assert.AreEqual(2, '\uFE19'.Width()); Assert.AreEqual(1, '\uFE1A'.Width()); // 0xFE30 ~ 0xFE6F:CJK Compatibility Forms Assert.AreEqual(1, '\uFE2F'.Width()); Assert.AreEqual(2, '\uFE30'.Width()); Assert.AreEqual(2, '\uFE6F'.Width()); Assert.AreEqual(1, '\uFE70'.Width()); // 0xFF00 ~ 0xFF60:Fullwidth Forms Assert.AreEqual(0, '\uFEFF'.Width()); // '\uFEFF' 是 Format Assert.AreEqual(2, '\uFF00'.Width()); Assert.AreEqual(2, '\uFF60'.Width()); Assert.AreEqual(1, '\uFF61'.Width()); // 0xFFE0 ~ 0xFFE6 Assert.AreEqual(1, '\uFFDF'.Width()); Assert.AreEqual(2, '\uFFE0'.Width()); Assert.AreEqual(2, '\uFFE6'.Width()); Assert.AreEqual(1, '\uFFE7'.Width()); // 0x20000 ~ 0x2FFFD Assert.AreEqual(1, CharExt.Width(char.ConvertFromUtf32(0x1FFFF), 0)); Assert.AreEqual(2, CharExt.Width(char.ConvertFromUtf32(0x20000), 0)); Assert.AreEqual(2, CharExt.Width(char.ConvertFromUtf32(0x2FFFD), 0)); Assert.AreEqual(1, CharExt.Width(char.ConvertFromUtf32(0x2FFFE), 0)); // 0x30000 ~ 0x3FFFD Assert.AreEqual(1, CharExt.Width(char.ConvertFromUtf32(0x2FFFF), 0)); Assert.AreEqual(2, CharExt.Width(char.ConvertFromUtf32(0x30000), 0)); Assert.AreEqual(2, CharExt.Width(char.ConvertFromUtf32(0x3FFFD), 0)); Assert.AreEqual(1, CharExt.Width(char.ConvertFromUtf32(0x3FFFE), 0)); }