GT2/GT2-iOS/node_modules/fbjs/lib/UnicodeUtils.js.flow

213 lines
6.0 KiB
Plaintext

/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule UnicodeUtils
* @typechecks
*/
/**
* Unicode-enabled replacesments for basic String functions.
*
* All the functions in this module assume that the input string is a valid
* UTF-16 encoding of a Unicode sequence. If it's not the case, the behavior
* will be undefined.
*
* WARNING: Since this module is typechecks-enforced, you may find new bugs
* when replacing normal String functions with ones provided here.
*/
'use strict';
const invariant = require('./invariant');
// These two ranges are consecutive so anything in [HIGH_START, LOW_END] is a
// surrogate code unit.
const SURROGATE_HIGH_START = 0xD800;
const SURROGATE_HIGH_END = 0xDBFF;
const SURROGATE_LOW_START = 0xDC00;
const SURROGATE_LOW_END = 0xDFFF;
const SURROGATE_UNITS_REGEX = /[\uD800-\uDFFF]/;
/**
* @param {number} codeUnit A Unicode code-unit, in range [0, 0x10FFFF]
* @return {boolean} Whether code-unit is in a surrogate (hi/low) range
*/
function isCodeUnitInSurrogateRange(codeUnit) {
return SURROGATE_HIGH_START <= codeUnit && codeUnit <= SURROGATE_LOW_END;
}
/**
* Returns whether the two characters starting at `index` form a surrogate pair.
* For example, given the string s = "\uD83D\uDE0A", (s, 0) returns true and
* (s, 1) returns false.
*
* @param {string} str
* @param {number} index
* @return {boolean}
*/
function isSurrogatePair(str, index) {
invariant(0 <= index && index < str.length, 'isSurrogatePair: Invalid index %s for string length %s.', index, str.length);
if (index + 1 === str.length) {
return false;
}
const first = str.charCodeAt(index);
const second = str.charCodeAt(index + 1);
return SURROGATE_HIGH_START <= first && first <= SURROGATE_HIGH_END && SURROGATE_LOW_START <= second && second <= SURROGATE_LOW_END;
}
/**
* @param {string} str Non-empty string
* @return {boolean} True if the input includes any surrogate code units
*/
function hasSurrogateUnit(str) {
return SURROGATE_UNITS_REGEX.test(str);
}
/**
* Return the length of the original Unicode character at given position in the
* String by looking into the UTF-16 code unit; that is equal to 1 for any
* non-surrogate characters in BMP ([U+0000..U+D7FF] and [U+E000, U+FFFF]); and
* returns 2 for the hi/low surrogates ([U+D800..U+DFFF]), which are in fact
* representing non-BMP characters ([U+10000..U+10FFFF]).
*
* Examples:
* - '\u0020' => 1
* - '\u3020' => 1
* - '\uD835' => 2
* - '\uD835\uDDEF' => 2
* - '\uDDEF' => 2
*
* @param {string} str Non-empty string
* @param {number} pos Position in the string to look for one code unit
* @return {number} Number 1 or 2
*/
function getUTF16Length(str, pos) {
return 1 + isCodeUnitInSurrogateRange(str.charCodeAt(pos));
}
/**
* Fully Unicode-enabled replacement for String#length
*
* @param {string} str Valid Unicode string
* @return {number} The number of Unicode characters in the string
*/
function strlen(str) {
// Call the native functions if there's no surrogate char
if (!hasSurrogateUnit(str)) {
return str.length;
}
let len = 0;
for (let pos = 0; pos < str.length; pos += getUTF16Length(str, pos)) {
len++;
}
return len;
}
/**
* Fully Unicode-enabled replacement for String#substr()
*
* @param {string} str Valid Unicode string
* @param {number} start Location in Unicode sequence to begin extracting
* @param {?number} length The number of Unicode characters to extract
* (default: to the end of the string)
* @return {string} Extracted sub-string
*/
function substr(str, start, length) {
start = start || 0;
length = length === undefined ? Infinity : length || 0;
// Call the native functions if there's no surrogate char
if (!hasSurrogateUnit(str)) {
return str.substr(start, length);
}
// Obvious cases
const size = str.length;
if (size <= 0 || start > size || length <= 0) {
return '';
}
// Find the actual starting position
let posA = 0;
if (start > 0) {
for (; start > 0 && posA < size; start--) {
posA += getUTF16Length(str, posA);
}
if (posA >= size) {
return '';
}
} else if (start < 0) {
for (posA = size; start < 0 && 0 < posA; start++) {
posA -= getUTF16Length(str, posA - 1);
}
if (posA < 0) {
posA = 0;
}
}
// Find the actual ending position
let posB = size;
if (length < size) {
for (posB = posA; length > 0 && posB < size; length--) {
posB += getUTF16Length(str, posB);
}
}
return str.substring(posA, posB);
}
/**
* Fully Unicode-enabled replacement for String#substring()
*
* @param {string} str Valid Unicode string
* @param {number} start Location in Unicode sequence to begin extracting
* @param {?number} end Location in Unicode sequence to end extracting
* (default: end of the string)
* @return {string} Extracted sub-string
*/
function substring(str, start, end) {
start = start || 0;
end = end === undefined ? Infinity : end || 0;
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
const length = Math.abs(end - start);
start = start < end ? start : end;
return substr(str, start, length);
}
/**
* Get a list of Unicode code-points from a String
*
* @param {string} str Valid Unicode string
* @return {array<number>} A list of code-points in [0..0x10FFFF]
*/
function getCodePoints(str) {
const codePoints = [];
for (let pos = 0; pos < str.length; pos += getUTF16Length(str, pos)) {
codePoints.push(str.codePointAt(pos));
}
return codePoints;
}
const UnicodeUtils = {
getCodePoints: getCodePoints,
getUTF16Length: getUTF16Length,
hasSurrogateUnit: hasSurrogateUnit,
isCodeUnitInSurrogateRange: isCodeUnitInSurrogateRange,
isSurrogatePair: isSurrogatePair,
strlen: strlen,
substring: substring,
substr: substr
};
module.exports = UnicodeUtils;