// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Diagnostics; using System.Runtime.CompilerServices; namespace SixLabors.Fonts.Unicode { internal static class UnicodeUtility { /// /// Returns if is an ASCII /// character ([ U+0000..U+007F ]). /// /// /// Per http://www.unicode.org/glossary/#ASCII, ASCII is only U+0000..U+007F. /// /// The codepoint to test. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsAsciiCodePoint(uint value) => value <= 0x7Fu; /// /// Returns if is in the /// Basic Multilingual Plane (BMP). /// /// The codepoint to test. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBmpCodePoint(uint value) => value <= 0xFFFFu; /// /// Gets the codepoint value representing the vertical mirror for this instance. ///
/// ///
/// ///
/// The codepoint to test. /// /// The representing the mirror or 0u if not found. /// public static uint GetVerticalMirror(uint value) { switch (value >> 8) { case 0x20: switch (value) { case 0x2013u: return 0xfe32u; // EN DASH case 0x2014u: return 0xfe31u; // EM DASH case 0x2025u: return 0xfe30u; // TWO DOT LEADER case 0x2026u: return 0xfe19u; // HORIZONTAL ELLIPSIS } break; case 0x30: switch (value) { case 0x3001u: return 0xfe11u; // IDEOGRAPHIC COMMA case 0x3002u: return 0xfe12u; // IDEOGRAPHIC FULL STOP case 0x3008u: return 0xfe3fu; // LEFT ANGLE BRACKET case 0x3009u: return 0xfe40u; // RIGHT ANGLE BRACKET case 0x300au: return 0xfe3du; // LEFT DOUBLE ANGLE BRACKET case 0x300bu: return 0xfe3eu; // RIGHT DOUBLE ANGLE BRACKET case 0x300cu: return 0xfe41u; // LEFT CORNER BRACKET case 0x300du: return 0xfe42u; // RIGHT CORNER BRACKET case 0x300eu: return 0xfe43u; // LEFT WHITE CORNER BRACKET case 0x300fu: return 0xfe44u; // RIGHT WHITE CORNER BRACKET case 0x3010u: return 0xfe3bu; // LEFT BLACK LENTICULAR BRACKET case 0x3011u: return 0xfe3cu; // RIGHT BLACK LENTICULAR BRACKET case 0x3014u: return 0xfe39u; // LEFT TORTOISE SHELL BRACKET case 0x3015u: return 0xfe3au; // RIGHT TORTOISE SHELL BRACKET case 0x3016u: return 0xfe17u; // LEFT WHITE LENTICULAR BRACKET case 0x3017u: return 0xfe18u; // RIGHT WHITE LENTICULAR BRACKET } break; case 0xfe: switch (value) { case 0xfe4fu: return 0xfe34u; // WAVY LOW LINE } break; case 0xff: switch (value) { case 0xff01u: return 0xfe15u; // FULLWIDTH EXCLAMATION MARK case 0xff08u: return 0xfe35u; // FULLWIDTH LEFT PARENTHESIS case 0xff09u: return 0xfe36u; // FULLWIDTH RIGHT PARENTHESIS case 0xff0cu: return 0xfe10u; // FULLWIDTH COMMA case 0xff1au: return 0xfe13u; // FULLWIDTH COLON case 0xff1bu: return 0xfe14u; // FULLWIDTH SEMICOLON case 0xff1fu: return 0xfe16u; // FULLWIDTH QUESTION MARK case 0xff3bu: return 0xfe47u; // FULLWIDTH LEFT SQUARE BRACKET case 0xff3du: return 0xfe48u; // FULLWIDTH RIGHT SQUARE BRACKET case 0xff3fu: return 0xfe33u; // FULLWIDTH LOW LINE case 0xff5bu: return 0xfe37u; // FULLWIDTH LEFT CURLY BRACKET case 0xff5du: return 0xfe38u; // FULLWIDTH RIGHT CURLY BRACKET } break; } return 0u; } /// /// Returns if is a Default Ignorable Code Point. /// /// The codepoint value. /// /// /// /// public static bool IsDefaultIgnorableCodePoint(uint value) { // SOFT HYPHEN if (value == 0x00AD) { return true; } // COMBINING GRAPHEME JOINER if (value == 0x034F) { return true; } // COMBINING GRAPHEME JOINER if (value == 0x061C) { return true; } // HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER if (IsInRangeInclusive(value, 0x115F, 0x1160)) { return true; } // KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA if (IsInRangeInclusive(value, 0x17B4, 0x17B5)) { return true; } // MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE if (IsInRangeInclusive(value, 0x180B, 0x180D)) { return true; } // MONGOLIAN VOWEL SEPARATOR if (value == 0x180E) { return true; } // MONGOLIAN FREE VARIATION SELECTOR FOUR if (value == 0x180F) { return true; } // ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK if (IsInRangeInclusive(value, 0x200B, 0x200F)) { return true; } // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE if (IsInRangeInclusive(value, 0x202A, 0x202E)) { return true; } // WORD JOINER..INVISIBLE PLUS if (IsInRangeInclusive(value, 0x2060, 0x2064)) { return true; } // if (value == 0x2065) { return true; } // LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES if (IsInRangeInclusive(value, 0x2066, 0x206F)) { return true; } // HANGUL FILLER if (value == 0x3164) { return true; } // VARIATION SELECTOR-1..VARIATION SELECTOR-16 if (IsInRangeInclusive(value, 0xFE00, 0xFE0F)) { return true; } // ZERO WIDTH NO-BREAK SPACE if (value == 0xFEFF) { return true; } // HALFWIDTH HANGUL FILLER if (value == 0xFFA0) { return true; } // .. if (IsInRangeInclusive(value, 0xFFF0, 0xFFF8)) { return true; } // SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP if (IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3)) { return true; } // MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE if (IsInRangeInclusive(value, 0x1D173, 0x1D17A)) { return true; } // if (value == 0xE0000) { return true; } // LANGUAGE TAG if (value == 0xE0001) { return true; } // .. if (IsInRangeInclusive(value, 0xE0002, 0xE001F)) { return true; } // TAG SPACE..CANCEL TAG if (IsInRangeInclusive(value, 0xE0020, 0xE007F)) { return true; } // .. if (IsInRangeInclusive(value, 0xE0080, 0xE00FF)) { return true; } // VARIATION SELECTOR-17..VARIATION SELECTOR-256 if (IsInRangeInclusive(value, 0xE0100, 0xE01EF)) { return true; } // .. if (IsInRangeInclusive(value, 0xE01F0, 0xE0FFF)) { return true; } return false; } /// /// Gets a value indicating whether the specified code point should be rendered as a white space only. /// /// The code point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ShouldRenderWhiteSpaceOnly(in CodePoint codePoint) { if (CodePoint.IsWhiteSpace(codePoint)) { return true; } // Note: While U+115F, U+1160, U+3164 and U+FFA0 are Default_Ignorable, // we do NOT want to hide them, as the way Uniscribe has implemented them // is with regular spacing glyphs, and that's the way fonts are made to work. // As such, we make exceptions for those four. // Also ignoring U+1BCA0..1BCA3. https://github.com/harfbuzz/harfbuzz/issues/503 uint value = (uint)codePoint.Value; if (value is 0x115F or 0x1160 or 0x3164 or 0xFFA0) { return true; } if (IsInRangeInclusive(value, 0x1BCA0, 0x1BCA3)) { return true; } return false; } /// /// Gets a value indicating whether the specified code point should not be rendered. /// /// The code point. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ShouldNotBeRendered(in CodePoint codePoint) => CodePoint.IsNewLine(codePoint) || (IsDefaultIgnorableCodePoint((uint)codePoint.Value) && !ShouldRenderWhiteSpaceOnly(codePoint)); /// /// Returns the Unicode plane (0 through 16, inclusive) which contains this code point. /// /// The code point. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetPlane(uint codePoint) { DebugAssertIsValidCodePoint(codePoint); return (int)(codePoint >> 16); } /// /// Given a Unicode scalar value, gets the number of UTF-16 code units required to represent this value. /// /// The code point. public static int GetUtf16SequenceLength(uint codePoint) { DebugAssertIsValidCodePoint(codePoint); codePoint -= 0x10000; // if value < 0x10000, high byte = 0xFF; else high byte = 0x00 codePoint += 2 << 24; // if value < 0x10000, high byte = 0x01; else high byte = 0x02 codePoint >>= 24; // shift high byte down return (int)codePoint; // and return it } /// /// Given a Unicode scalar value, gets the number of UTF-8 code units required to represent this value. /// /// The code point. public static int GetUtf8SequenceLength(uint codePoint) { DebugAssertIsValidCodePoint(codePoint); // The logic below can handle all valid scalar values branchlessly. // It gives generally good performance across all inputs, and on x86 // it's only six instructions: lea, sar, xor, add, shr, lea. // 'a' will be -1 if input is < 0x800; else 'a' will be 0 // => 'a' will be -1 if input is 1 or 2 UTF-8 code units; else 'a' will be 0 int a = ((int)codePoint - 0x0800) >> 31; // The number of UTF-8 code units for a given scalar is as follows: // - U+0000..U+007F => 1 code unit // - U+0080..U+07FF => 2 code units // - U+0800..U+FFFF => 3 code units // - U+10000+ => 4 code units // // If we XOR the incoming scalar with 0xF800, the chart mutates: // - U+0000..U+F7FF => 3 code units // - U+F800..U+F87F => 1 code unit // - U+F880..U+FFFF => 2 code units // - U+10000+ => 4 code units // // Since the 1- and 3-code unit cases are now clustered, they can // both be checked together very cheaply. codePoint ^= 0xF800u; codePoint -= 0xF880u; // if scalar is 1 or 3 code units, high byte = 0xFF; else high byte = 0x00 codePoint += 4 << 24; // if scalar is 1 or 3 code units, high byte = 0x03; else high byte = 0x04 codePoint >>= 24; // shift high byte down // Final return value: // - U+0000..U+007F => 3 + (-1) * 2 = 1 // - U+0080..U+07FF => 4 + (-1) * 2 = 2 // - U+0800..U+FFFF => 3 + ( 0) * 2 = 3 // - U+10000+ => 4 + ( 0) * 2 = 4 return (int)codePoint + (a * 2); } /// /// Returns if is a valid Unicode code /// point, i.e., is in [ U+0000..U+10FFFF ], inclusive. /// /// The code point. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsValidCodePoint(uint codePoint) => codePoint <= 0x10FFFFu; /// /// Returns if is a UTF-16 high surrogate code point, /// i.e., is in [ U+D800..U+DBFF ], inclusive. /// /// The value to test. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsHighSurrogateCodePoint(uint value) => IsInRangeInclusive(value, 0xD800u, 0xDBFFu); /// /// Returns if is a UTF-16 low surrogate code point, /// i.e., is in [ U+DC00..U+DFFF ], inclusive. /// /// The value to test. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsLowSurrogateCodePoint(uint value) => IsInRangeInclusive(value, 0xDC00u, 0xDFFFu); /// /// Returns if is a UTF-16 surrogate code point, /// i.e., is in [ U+D800..U+DFFF ], inclusive. /// /// The value to test. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsSurrogateCodePoint(uint value) => IsInRangeInclusive(value, 0xD800u, 0xDFFFu); /// /// Returns if is between /// and , inclusive. /// /// The value to test. /// The lower bound. /// The upper bound. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound) => (value - lowerBound) <= (upperBound - lowerBound); /// /// Returns a Unicode scalar value from two code points representing a UTF-16 surrogate pair. /// /// The high surrogate code point. /// The low surrogate code point. public static uint GetScalarFromUtf16SurrogatePair(uint highSurrogateCodePoint, uint lowSurrogateCodePoint) { DebugAssertIsHighSurrogateCodePoint(highSurrogateCodePoint); DebugAssertIsLowSurrogateCodePoint(lowSurrogateCodePoint); // This calculation comes from the Unicode specification, Table 3-5. // Need to remove the D800 marker from the high surrogate and the DC00 marker from the low surrogate, // then fix up the "wwww = uuuuu - 1" section of the bit distribution. The code is written as below // to become just two instructions: shl, lea. return (highSurrogateCodePoint << 10) + lowSurrogateCodePoint - ((0xD800U << 10) + 0xDC00U - (1 << 16)); } /// /// Decomposes an astral Unicode code point into UTF-16 high and low surrogate code units. /// /// The Unicode code point. /// The high surrogate code point. /// The low surrogate code point. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void GetUtf16SurrogatesFromSupplementaryPlaneCodePoint(uint value, out char highSurrogateCodePoint, out char lowSurrogateCodePoint) { DebugAssertIsValidSupplementaryPlaneCodePoint(value); // This calculation comes from the Unicode specification, Table 3-5. highSurrogateCodePoint = (char)((value + ((0xD800u - 0x40u) << 10)) >> 10); lowSurrogateCodePoint = (char)((value & 0x3FFu) + 0xDC00u); } [Conditional("DEBUG")] internal static void DebugAssertIsHighSurrogateCodePoint(uint codePoint) { if (!IsHighSurrogateCodePoint(codePoint)) { Debug.Fail($"The value {ToHexString(codePoint)} is not a valid UTF-16 high surrogate code point."); } } [Conditional("DEBUG")] internal static void DebugAssertIsLowSurrogateCodePoint(uint codePoint) { if (!IsLowSurrogateCodePoint(codePoint)) { Debug.Fail($"The value {ToHexString(codePoint)} is not a valid UTF-16 low surrogate code point."); } } [Conditional("DEBUG")] internal static void DebugAssertIsValidCodePoint(uint codePoint) { if (!IsValidCodePoint(codePoint)) { Debug.Fail($"The value {ToHexString(codePoint)} is not a valid Unicode code point value."); } } [Conditional("DEBUG")] internal static void DebugAssertIsValidSupplementaryPlaneCodePoint(uint codePoint) { if (!IsValidCodePoint(codePoint) || IsBmpCodePoint(codePoint)) { Debug.Fail($"The value {ToHexString(codePoint)} is not a valid supplementary plane Unicode code point value."); } } /// /// Formats a code point as the hex string "U+XXXX". /// /// /// The input value doesn't have to be a real code point in the Unicode codespace. It can be any integer. /// /// The code point. internal static string ToHexString(uint codePoint) => FormattableString.Invariant($"U+{codePoint:X4}"); } }