Files
yoga/lib/fb/jni/LocalString.cpp

313 lines
9.3 KiB
C++
Raw Normal View History

2016-08-31 16:15:15 +01:00
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <jni/LocalString.h>
#include <fb/Environment.h>
#include <fb/assert.h>
#include <vector>
namespace facebook {
namespace jni {
namespace {
const uint16_t kUtf8OneByteBoundary = 0x80;
const uint16_t kUtf8TwoBytesBoundary = 0x800;
const uint16_t kUtf16HighSubLowBoundary = 0xD800;
const uint16_t kUtf16HighSubHighBoundary = 0xDC00;
const uint16_t kUtf16LowSubHighBoundary = 0xE000;
inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
out[0] = 0xE0 | (code >> 12);
out[1] = 0x80 | ((code >> 6) & 0x3F);
out[2] = 0x80 | (code & 0x3F);
}
inline char32_t decode3ByteUTF8(const uint8_t* in) {
return (((in[0] & 0x0f) << 12) |
((in[1] & 0x3f) << 6) |
( in[2] & 0x3f));
}
inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) {
FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits");
out[offset] = (char) (0xF0 | (code >> 18));
out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F));
out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F));
out[offset + 3] = (char) (0x80 | (code & 0x3F));
}
template <typename T>
inline bool isFourByteUTF8Encoding(const T* utf8) {
return ((*utf8 & 0xF8) == 0xF0);
}
}
namespace detail {
size_t modifiedLength(const std::string& str) {
// Scan for supplementary characters
size_t j = 0;
for (size_t i = 0; i < str.size(); ) {
if (str[i] == 0) {
i += 1;
j += 2;
} else if (i + 4 > str.size() ||
!isFourByteUTF8Encoding(&(str[i]))) {
// See the code in utf8ToModifiedUTF8 for what's happening here.
i += 1;
j += 1;
} else {
i += 4;
j += 6;
}
}
return j;
}
// returns modified utf8 length; *length is set to strlen(str)
size_t modifiedLength(const uint8_t* str, size_t* length) {
// NUL-terminated: Scan for length and supplementary characters
size_t i = 0;
size_t j = 0;
while (str[i] != 0) {
if (str[i + 1] == 0 ||
str[i + 2] == 0 ||
str[i + 3] == 0 ||
!isFourByteUTF8Encoding(&(str[i]))) {
i += 1;
j += 1;
} else {
i += 4;
j += 6;
}
}
*length = i;
return j;
}
void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen)
{
size_t j = 0;
for (size_t i = 0; i < len; ) {
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
if (utf8[i] == 0) {
FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short");
modified[j] = 0xc0;
modified[j + 1] = 0x80;
i += 1;
j += 2;
continue;
}
if (i + 4 > len ||
!isFourByteUTF8Encoding(utf8 + i)) {
// If the input is too short for this to be a four-byte
// encoding, or it isn't one for real, just copy it on through.
modified[j] = utf8[i];
i++;
j++;
continue;
}
// Convert 4 bytes of input to 2 * 3 bytes of output
char32_t code = (((utf8[i] & 0x07) << 18) |
((utf8[i + 1] & 0x3f) << 12) |
((utf8[i + 2] & 0x3f) << 6) |
( utf8[i + 3] & 0x3f));
char32_t first;
char32_t second;
if (code > 0x10ffff) {
// These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit
// limit on that representation. Encode two replacement characters, so the expected output
// length lines up.
const char32_t kUnicodeReplacementChar = 0xfffd;
first = kUnicodeReplacementChar;
second = kUnicodeReplacementChar;
} else {
// split into surrogate pair
first = ((code - 0x010000) >> 10) | 0xd800;
second = ((code - 0x010000) & 0x3ff) | 0xdc00;
}
// encode each as a 3 byte surrogate value
FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short");
encode3ByteUTF8(first, modified + j);
encode3ByteUTF8(second, modified + j + 3);
i += 4;
j += 6;
}
FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
modified[j++] = '\0';
}
std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept {
// Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
std::string utf8(len, 0);
size_t j = 0;
for (size_t i = 0; i < len; ) {
// surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx
// encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx
if (len >= i + 6 &&
modified[i] == 0xed &&
(modified[i + 1] & 0xf0) == 0xa0 &&
modified[i + 3] == 0xed &&
(modified[i + 4] & 0xf0) == 0xb0) {
// Valid surrogate pair
char32_t pair1 = decode3ByteUTF8(modified + i);
char32_t pair2 = decode3ByteUTF8(modified + i + 3);
char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) |
( pair2 & 0x3ff));
encode4ByteUTF8(ch, utf8, j);
i += 6;
j += 4;
continue;
} else if (len >= i + 2 &&
modified[i] == 0xc0 &&
modified[i + 1] == 0x80) {
utf8[j] = 0;
i += 2;
j += 1;
continue;
}
// copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid
// encoding of some sort, but garbage in garbage out is ok.
utf8[j] = (char) modified[i];
i++;
j++;
}
utf8.resize(j);
return utf8;
}
// Calculate how many bytes are needed to convert an UTF16 string into UTF8
// UTF16 string
size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
if (!utf16String || utf16StringLen == 0) {
return 0;
}
uint32_t utf8StringLen = 0;
auto utf16StringEnd = utf16String + utf16StringLen;
auto idx16 = utf16String;
while (idx16 < utf16StringEnd) {
auto ch = *idx16++;
if (ch < kUtf8OneByteBoundary) {
utf8StringLen++;
} else if (ch < kUtf8TwoBytesBoundary) {
utf8StringLen += 2;
} else if (
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
(idx16 < utf16StringEnd) &&
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
utf8StringLen += 4;
idx16++;
} else {
utf8StringLen += 3;
}
}
return utf8StringLen;
}
std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
if (!utf16String || utf16StringLen <= 0) {
return "";
}
std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
auto idx8 = utf8String.begin();
auto idx16 = utf16String;
auto utf16StringEnd = utf16String + utf16StringLen;
while (idx16 < utf16StringEnd) {
auto ch = *idx16++;
if (ch < kUtf8OneByteBoundary) {
*idx8++ = (ch & 0x7F);
} else if (ch < kUtf8TwoBytesBoundary) {
*idx8++ = 0b11000000 | (ch >> 6);
*idx8++ = 0b10000000 | (ch & 0x3F);
} else if (
(ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
(idx16 < utf16StringEnd) &&
(*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
auto ch2 = *idx16++;
uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
*idx8++ = 0b11110000 | (trunc_byte >> 2);
*idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
*idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
*idx8++ = 0b10000000 | (ch2 & 0x3F);
} else {
*idx8++ = 0b11100000 | (ch >> 12);
*idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
*idx8++ = 0b10000000 | (ch & 0x3F);
}
}
return utf8String;
}
}
LocalString::LocalString(const std::string& str)
{
size_t modlen = detail::modifiedLength(str);
if (modlen == str.size()) {
// no supplementary characters, build jstring from input buffer
m_string = Environment::current()->NewStringUTF(str.data());
return;
}
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
detail::utf8ToModifiedUTF8(
reinterpret_cast<const uint8_t*>(str.data()), str.size(),
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
m_string = Environment::current()->NewStringUTF(modified.data());
}
LocalString::LocalString(const char* str)
{
size_t len;
size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(str), &len);
if (modlen == len) {
// no supplementary characters, build jstring from input buffer
m_string = Environment::current()->NewStringUTF(str);
return;
}
auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
detail::utf8ToModifiedUTF8(
reinterpret_cast<const uint8_t*>(str), len,
reinterpret_cast<uint8_t*>(modified.data()), modified.size());
m_string = Environment::current()->NewStringUTF(modified.data());
}
LocalString::~LocalString() {
Environment::current()->DeleteLocalRef(m_string);
}
std::string fromJString(JNIEnv* env, jstring str) {
auto utf16String = JStringUtf16Extractor(env, str);
auto length = env->GetStringLength(str);
return detail::utf16toUTF8(utf16String, length);
}
} }