提交 0be64826 编写于 作者: C Chinmay Garde 提交者: GitHub

Clang format the imported minikin sources, tests and benchmarks to match...

Clang format the imported minikin sources, tests and benchmarks to match Flutter engine stylf. (#4018)
上级 aa0726f4
......@@ -38,4 +38,4 @@ int main(int argc, char** argv) {
fml::icu::InitializeICU();
::benchmark::RunSpecifiedBenchmarks();
}
\ No newline at end of file
}
......@@ -23,266 +23,283 @@ using std::vector;
#include <log/log.h>
#include <minikin/SparseBitSet.h>
#include <minikin/CmapCoverage.h>
#include <minikin/SparseBitSet.h>
#include "MinikinInternal.h"
namespace minikin {
// These could perhaps be optimized to use __builtin_bswap16 and friends.
static uint32_t readU16(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);
return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);
}
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
static void addRange(vector<uint32_t> &coverage, uint32_t start, uint32_t end) {
static void addRange(vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
#ifdef VERBOSE_DEBUG
ALOGD("adding range %d-%d\n", start, end);
ALOGD("adding range %d-%d\n", start, end);
#endif
if (coverage.empty() || coverage.back() < start) {
coverage.push_back(start);
coverage.push_back(end);
} else {
coverage.back() = end;
}
if (coverage.empty() || coverage.back() < start) {
coverage.push_back(start);
coverage.push_back(end);
} else {
coverage.back() = end;
}
}
// Get the coverage information out of a Format 4 subtable, storing it in the coverage vector
static bool getCoverageFormat4(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kSegCountOffset = 6;
const size_t kEndCountOffset = 14;
const size_t kHeaderSize = 16;
const size_t kSegmentSize = 8; // total size of array elements for one segment
if (kEndCountOffset > size) {
return false;
// Get the coverage information out of a Format 4 subtable, storing it in the
// coverage vector
static bool getCoverageFormat4(vector<uint32_t>& coverage,
const uint8_t* data,
size_t size) {
const size_t kSegCountOffset = 6;
const size_t kEndCountOffset = 14;
const size_t kHeaderSize = 16;
const size_t kSegmentSize =
8; // total size of array elements for one segment
if (kEndCountOffset > size) {
return false;
}
size_t segCount = readU16(data, kSegCountOffset) >> 1;
if (kHeaderSize + segCount * kSegmentSize > size) {
return false;
}
for (size_t i = 0; i < segCount; i++) {
uint32_t end = readU16(data, kEndCountOffset + 2 * i);
uint32_t start = readU16(data, kHeaderSize + 2 * (segCount + i));
if (end < start) {
// invalid segment range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
}
size_t segCount = readU16(data, kSegCountOffset) >> 1;
if (kHeaderSize + segCount * kSegmentSize > size) {
return false;
}
for (size_t i = 0; i < segCount; i++) {
uint32_t end = readU16(data, kEndCountOffset + 2 * i);
uint32_t start = readU16(data, kHeaderSize + 2 * (segCount + i));
if (end < start) {
// invalid segment range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
uint32_t rangeOffset = readU16(data, kHeaderSize + 2 * (3 * segCount + i));
if (rangeOffset == 0) {
uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
if (((end + delta) & 0xffff) > end - start) {
addRange(coverage, start, end + 1);
} else {
for (uint32_t j = start; j < end + 1; j++) {
if (((j + delta) & 0xffff) != 0) {
addRange(coverage, j, j + 1);
}
}
}
} else {
for (uint32_t j = start; j < end + 1; j++) {
uint32_t actualRangeOffset =
kHeaderSize + 6 * segCount + rangeOffset + (i + j - start) * 2;
if (actualRangeOffset + 2 > size) {
// invalid rangeOffset is considered a "warning" by OpenType Sanitizer
continue;
}
uint32_t rangeOffset = readU16(data, kHeaderSize + 2 * (3 * segCount + i));
if (rangeOffset == 0) {
uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
if (((end + delta) & 0xffff) > end - start) {
addRange(coverage, start, end + 1);
} else {
for (uint32_t j = start; j < end + 1; j++) {
if (((j + delta) & 0xffff) != 0) {
addRange(coverage, j, j + 1);
}
}
}
} else {
for (uint32_t j = start; j < end + 1; j++) {
uint32_t actualRangeOffset = kHeaderSize + 6 * segCount + rangeOffset +
(i + j - start) * 2;
if (actualRangeOffset + 2 > size) {
// invalid rangeOffset is considered a "warning" by OpenType Sanitizer
continue;
}
uint32_t glyphId = readU16(data, actualRangeOffset);
if (glyphId != 0) {
addRange(coverage, j, j + 1);
}
}
uint32_t glyphId = readU16(data, actualRangeOffset);
if (glyphId != 0) {
addRange(coverage, j, j + 1);
}
}
}
return true;
}
return true;
}
// Get the coverage information out of a Format 12 subtable, storing it in the coverage vector
static bool getCoverageFormat12(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kNGroupsOffset = 12;
const size_t kFirstGroupOffset = 16;
const size_t kGroupSize = 12;
const size_t kStartCharCodeOffset = 0;
const size_t kEndCharCodeOffset = 4;
const size_t kMaxNGroups = 0xfffffff0 / kGroupSize; // protection against overflow
// For all values < kMaxNGroups, kFirstGroupOffset + nGroups * kGroupSize fits in 32 bits.
if (kFirstGroupOffset > size) {
return false;
}
uint32_t nGroups = readU32(data, kNGroupsOffset);
if (nGroups >= kMaxNGroups || kFirstGroupOffset + nGroups * kGroupSize > size) {
android_errorWriteLog(0x534e4554, "25645298");
return false;
// Get the coverage information out of a Format 12 subtable, storing it in the
// coverage vector
static bool getCoverageFormat12(vector<uint32_t>& coverage,
const uint8_t* data,
size_t size) {
const size_t kNGroupsOffset = 12;
const size_t kFirstGroupOffset = 16;
const size_t kGroupSize = 12;
const size_t kStartCharCodeOffset = 0;
const size_t kEndCharCodeOffset = 4;
const size_t kMaxNGroups =
0xfffffff0 / kGroupSize; // protection against overflow
// For all values < kMaxNGroups, kFirstGroupOffset + nGroups * kGroupSize fits
// in 32 bits.
if (kFirstGroupOffset > size) {
return false;
}
uint32_t nGroups = readU32(data, kNGroupsOffset);
if (nGroups >= kMaxNGroups ||
kFirstGroupOffset + nGroups * kGroupSize > size) {
android_errorWriteLog(0x534e4554, "25645298");
return false;
}
for (uint32_t i = 0; i < nGroups; i++) {
uint32_t groupOffset = kFirstGroupOffset + i * kGroupSize;
uint32_t start = readU32(data, groupOffset + kStartCharCodeOffset);
uint32_t end = readU32(data, groupOffset + kEndCharCodeOffset);
if (end < start) {
// invalid group range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
}
for (uint32_t i = 0; i < nGroups; i++) {
uint32_t groupOffset = kFirstGroupOffset + i * kGroupSize;
uint32_t start = readU32(data, groupOffset + kStartCharCodeOffset);
uint32_t end = readU32(data, groupOffset + kEndCharCodeOffset);
if (end < start) {
// invalid group range: size must be positive
android_errorWriteLog(0x534e4554, "26413177");
return false;
}
// No need to read outside of Unicode code point range.
if (start > MAX_UNICODE_CODE_POINT) {
return true;
}
if (end > MAX_UNICODE_CODE_POINT) {
// file is inclusive, vector is exclusive
addRange(coverage, start, MAX_UNICODE_CODE_POINT + 1);
return true;
}
addRange(coverage, start, end + 1); // file is inclusive, vector is exclusive
// No need to read outside of Unicode code point range.
if (start > MAX_UNICODE_CODE_POINT) {
return true;
}
if (end > MAX_UNICODE_CODE_POINT) {
// file is inclusive, vector is exclusive
addRange(coverage, start, MAX_UNICODE_CODE_POINT + 1);
return true;
}
return true;
addRange(coverage, start,
end + 1); // file is inclusive, vector is exclusive
}
return true;
}
// Lower value has higher priority. 0 for the highest priority table.
// kLowestPriority for unsupported tables.
// This order comes from HarfBuzz's hb-ot-font.cc and needs to be kept in sync with it.
// This order comes from HarfBuzz's hb-ot-font.cc and needs to be kept in sync
// with it.
constexpr uint8_t kLowestPriority = 255;
uint8_t getTablePriority(uint16_t platformId, uint16_t encodingId) {
if (platformId == 3 && encodingId == 10) {
return 0;
}
if (platformId == 0 && encodingId == 6) {
return 1;
}
if (platformId == 0 && encodingId == 4) {
return 2;
}
if (platformId == 3 && encodingId == 1) {
return 3;
}
if (platformId == 0 && encodingId == 3) {
return 4;
}
if (platformId == 0 && encodingId == 2) {
return 5;
}
if (platformId == 0 && encodingId == 1) {
return 6;
}
if (platformId == 0 && encodingId == 0) {
return 7;
}
// Tables other than above are not supported.
return kLowestPriority;
if (platformId == 3 && encodingId == 10) {
return 0;
}
if (platformId == 0 && encodingId == 6) {
return 1;
}
if (platformId == 0 && encodingId == 4) {
return 2;
}
if (platformId == 3 && encodingId == 1) {
return 3;
}
if (platformId == 0 && encodingId == 3) {
return 4;
}
if (platformId == 0 && encodingId == 2) {
return 5;
}
if (platformId == 0 && encodingId == 1) {
return 6;
}
if (platformId == 0 && encodingId == 0) {
return 7;
}
// Tables other than above are not supported.
return kLowestPriority;
}
SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size,
bool* has_cmap_format14_subtable) {
constexpr size_t kHeaderSize = 4;
constexpr size_t kNumTablesOffset = 2;
constexpr size_t kTableSize = 8;
constexpr size_t kPlatformIdOffset = 0;
constexpr size_t kEncodingIdOffset = 2;
constexpr size_t kOffsetOffset = 4;
constexpr size_t kFormatOffset = 0;
constexpr uint32_t kInvalidOffset = UINT32_MAX;
SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data,
size_t cmap_size,
bool* has_cmap_format14_subtable) {
constexpr size_t kHeaderSize = 4;
constexpr size_t kNumTablesOffset = 2;
constexpr size_t kTableSize = 8;
constexpr size_t kPlatformIdOffset = 0;
constexpr size_t kEncodingIdOffset = 2;
constexpr size_t kOffsetOffset = 4;
constexpr size_t kFormatOffset = 0;
constexpr uint32_t kInvalidOffset = UINT32_MAX;
if (kHeaderSize > cmap_size) {
return SparseBitSet();
}
uint32_t numTables = readU16(cmap_data, kNumTablesOffset);
if (kHeaderSize + numTables * kTableSize > cmap_size) {
return SparseBitSet();
}
if (kHeaderSize > cmap_size) {
return SparseBitSet();
}
uint32_t numTables = readU16(cmap_data, kNumTablesOffset);
if (kHeaderSize + numTables * kTableSize > cmap_size) {
return SparseBitSet();
}
uint32_t bestTableOffset = kInvalidOffset;
uint16_t bestTableFormat = 0;
uint8_t bestTablePriority = kLowestPriority;
*has_cmap_format14_subtable = false;
for (uint32_t i = 0; i < numTables; ++i) {
const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize;
const uint16_t platformId = readU16(cmap_data, tableHeadOffset + kPlatformIdOffset);
const uint16_t encodingId = readU16(cmap_data, tableHeadOffset + kEncodingIdOffset);
const uint32_t offset = readU32(cmap_data, tableHeadOffset + kOffsetOffset);
if (offset > cmap_size - 2) {
continue; // Invalid table: not enough space to read.
}
const uint16_t format = readU16(cmap_data, offset + kFormatOffset);
uint32_t bestTableOffset = kInvalidOffset;
uint16_t bestTableFormat = 0;
uint8_t bestTablePriority = kLowestPriority;
*has_cmap_format14_subtable = false;
for (uint32_t i = 0; i < numTables; ++i) {
const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize;
const uint16_t platformId =
readU16(cmap_data, tableHeadOffset + kPlatformIdOffset);
const uint16_t encodingId =
readU16(cmap_data, tableHeadOffset + kEncodingIdOffset);
const uint32_t offset = readU32(cmap_data, tableHeadOffset + kOffsetOffset);
if (platformId == 0 /* Unicode */ && encodingId == 5 /* Variation Sequences */) {
if (!(*has_cmap_format14_subtable) && format == 14) {
*has_cmap_format14_subtable = true;
} else {
// Ignore the (0, 5) table if we have already seen another valid one or it's in a
// format we don't understand.
}
} else {
uint32_t length;
uint32_t language;
if (offset > cmap_size - 2) {
continue; // Invalid table: not enough space to read.
}
const uint16_t format = readU16(cmap_data, offset + kFormatOffset);
if (format == 4) {
constexpr size_t lengthOffset = 2;
constexpr size_t languageOffset = 4;
constexpr size_t minTableSize = languageOffset + 2;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
length = readU16(cmap_data, offset + lengthOffset);
language = readU16(cmap_data, offset + languageOffset);
} else if (format == 12) {
constexpr size_t lengthOffset = 4;
constexpr size_t languageOffset = 8;
constexpr size_t minTableSize = languageOffset + 4;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
length = readU32(cmap_data, offset + lengthOffset);
language = readU32(cmap_data, offset + languageOffset);
} else {
continue;
}
if (platformId == 0 /* Unicode */ &&
encodingId == 5 /* Variation Sequences */) {
if (!(*has_cmap_format14_subtable) && format == 14) {
*has_cmap_format14_subtable = true;
} else {
// Ignore the (0, 5) table if we have already seen another valid one or
// it's in a format we don't understand.
}
} else {
uint32_t length;
uint32_t language;
if (length > cmap_size - offset) {
continue; // Invalid table: table length is larger than whole cmap data size.
}
if (language != 0) {
// Unsupported or invalid table: this is either a subtable for the Macintosh
// platform (which we don't support), or an invalid subtable since language field
// should be zero for non-Macintosh subtables.
continue;
}
const uint8_t priority = getTablePriority(platformId, encodingId);
if (priority < bestTablePriority) {
bestTableOffset = offset;
bestTablePriority = priority;
bestTableFormat = format;
}
if (format == 4) {
constexpr size_t lengthOffset = 2;
constexpr size_t languageOffset = 4;
constexpr size_t minTableSize = languageOffset + 2;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
if (*has_cmap_format14_subtable && bestTablePriority == 0 /* highest priority */) {
// Already found the highest priority table and variation sequences table. No need to
// look at remaining tables.
break;
length = readU16(cmap_data, offset + lengthOffset);
language = readU16(cmap_data, offset + languageOffset);
} else if (format == 12) {
constexpr size_t lengthOffset = 4;
constexpr size_t languageOffset = 8;
constexpr size_t minTableSize = languageOffset + 4;
if (offset > cmap_size - minTableSize) {
continue; // Invalid table: not enough space to read.
}
length = readU32(cmap_data, offset + lengthOffset);
language = readU32(cmap_data, offset + languageOffset);
} else {
continue;
}
if (length > cmap_size - offset) {
continue; // Invalid table: table length is larger than whole cmap data
// size.
}
if (language != 0) {
// Unsupported or invalid table: this is either a subtable for the
// Macintosh platform (which we don't support), or an invalid subtable
// since language field should be zero for non-Macintosh subtables.
continue;
}
const uint8_t priority = getTablePriority(platformId, encodingId);
if (priority < bestTablePriority) {
bestTableOffset = offset;
bestTablePriority = priority;
bestTableFormat = format;
}
}
if (bestTableOffset == kInvalidOffset) {
return SparseBitSet();
}
const uint8_t* tableData = cmap_data + bestTableOffset;
const size_t tableSize = cmap_size - bestTableOffset;
vector<uint32_t> coverageVec;
bool success;
if (bestTableFormat == 4) {
success = getCoverageFormat4(coverageVec, tableData, tableSize);
} else {
success = getCoverageFormat12(coverageVec, tableData, tableSize);
}
if (success) {
return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1);
} else {
return SparseBitSet();
if (*has_cmap_format14_subtable &&
bestTablePriority == 0 /* highest priority */) {
// Already found the highest priority table and variation sequences table.
// No need to look at remaining tables.
break;
}
}
if (bestTableOffset == kInvalidOffset) {
return SparseBitSet();
}
const uint8_t* tableData = cmap_data + bestTableOffset;
const size_t tableSize = cmap_size - bestTableOffset;
vector<uint32_t> coverageVec;
bool success;
if (bestTableFormat == 4) {
success = getCoverageFormat4(coverageVec, tableData, tableSize);
} else {
success = getCoverageFormat12(coverageVec, tableData, tableSize);
}
if (success) {
return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1);
} else {
return SparseBitSet();
}
}
} // namespace minikin
......@@ -22,9 +22,10 @@
namespace minikin {
class CmapCoverage {
public:
static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size,
bool* has_cmap_format14_subtable);
public:
static SparseBitSet getCoverage(const uint8_t* cmap_data,
size_t cmap_size,
bool* has_cmap_format14_subtable);
};
} // namespace minikin
......
......@@ -19,71 +19,65 @@
namespace minikin {
bool isNewEmoji(uint32_t c) {
// Emoji characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c < 0x1F6F7 || c > 0x1F9E6) {
// Optimization for characters outside the new emoji range.
return false;
}
return (0x1F6F7 <= c && c <= 0x1F6F8)
|| c == 0x1F91F
|| (0x1F928 <= c && c <= 0x1F92F)
|| (0x1F931 <= c && c <= 0x1F932)
|| c == 0x1F94C
|| (0x1F95F <= c && c <= 0x1F96B)
|| (0x1F992 <= c && c <= 0x1F997)
|| (0x1F9D0 <= c && c <= 0x1F9E6);
// Emoji characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c < 0x1F6F7 || c > 0x1F9E6) {
// Optimization for characters outside the new emoji range.
return false;
}
return (0x1F6F7 <= c && c <= 0x1F6F8) || c == 0x1F91F ||
(0x1F928 <= c && c <= 0x1F92F) || (0x1F931 <= c && c <= 0x1F932) ||
c == 0x1F94C || (0x1F95F <= c && c <= 0x1F96B) ||
(0x1F992 <= c && c <= 0x1F997) || (0x1F9D0 <= c && c <= 0x1F9E6);
}
bool isEmoji(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
return isNewEmoji(c) || u_hasBinaryProperty(c, UCHAR_EMOJI);
#endif // WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
return isNewEmoji(c) || u_hasBinaryProperty(c, UCHAR_EMOJI);
#endif // WIP_NEEDS_ICU_UPDATE
}
bool isEmojiModifier(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// Emoji modifier are not expected to change, so there's a small change we need to customize
// this.
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER);
#endif // WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// Emoji modifier are not expected to change, so there's a small change we
// need to customize this.
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER);
#endif // WIP_NEEDS_ICU_UPDATE
}
bool isEmojiBase(uint32_t c) {
#if WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// These two characters were removed from Emoji_Modifier_Base in Emoji 4.0, but we need to keep
// them as emoji modifier bases since there are fonts and user-generated text out there that
// treats these as potential emoji bases.
if (c == 0x1F91D || c == 0x1F93C) {
return true;
}
// Emoji Modifier Base characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c == 0x1F91F
|| (0x1F931 <= c && c <= 0x1F932)
|| (0x1F9D1 <= c && c <= 0x1F9DD)) {
return true;
}
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);
#endif // WIP_NEEDS_ICU_UPDATE
return false;
#else // WIP_NEEDS_ICU_UPDATE
// These two characters were removed from Emoji_Modifier_Base in Emoji 4.0,
// but we need to keep them as emoji modifier bases since there are fonts and
// user-generated text out there that treats these as potential emoji bases.
if (c == 0x1F91D || c == 0x1F93C) {
return true;
}
// Emoji Modifier Base characters new in Unicode emoji 5.0.
// From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
// TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
if (c == 0x1F91F || (0x1F931 <= c && c <= 0x1F932) ||
(0x1F9D1 <= c && c <= 0x1F9DD)) {
return true;
}
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);
#endif // WIP_NEEDS_ICU_UPDATE
}
UCharDirection emojiBidiOverride(const void* /* context */, UChar32 c) {
if (isNewEmoji(c)) {
// All new emoji characters in Unicode 10.0 are of the bidi class ON.
return U_OTHER_NEUTRAL;
} else {
return u_charDirection(c);
}
if (isNewEmoji(c)) {
// All new emoji characters in Unicode 10.0 are of the bidi class ON.
return U_OTHER_NEUTRAL;
} else {
return u_charDirection(c);
}
}
} // namespace minikin
......@@ -31,4 +31,3 @@ bool isEmojiModifier(uint32_t c);
UCharDirection emojiBidiOverride(const void* context, UChar32 c);
} // namespace minikin
......@@ -21,102 +21,116 @@
#include <unordered_set>
#include <vector>
#include <minikin/MinikinFont.h>
#include <minikin/FontFamily.h>
#include <minikin/MinikinFont.h>
namespace minikin {
class FontCollection {
public:
explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
explicit FontCollection(std::shared_ptr<FontFamily>&& typeface);
struct Run {
FakedFont fakedFont;
int start;
int end;
};
void itemize(const uint16_t *string, size_t string_length, FontStyle style,
std::vector<Run>* result) const;
// Returns true if there is a glyph for the code point and variation selector pair.
// Returns false if no fonts have a glyph for the code point and variation
// selector pair, or invalid variation selector is passed.
bool hasVariationSelector(uint32_t baseCodepoint, uint32_t variationSelector) const;
// Get base font with fakery information (fake bold could affect metrics)
FakedFont baseFontFaked(FontStyle style);
// Creates new FontCollection based on this collection while applying font variations. Returns
// nullptr if none of variations apply to this collection.
std::shared_ptr<FontCollection>
createCollectionWithVariation(const std::vector<FontVariation>& variations);
const std::unordered_set<AxisTag>& getSupportedTags() const {
return mSupportedAxes;
}
uint32_t getId() const;
private:
static const int kLogCharsPerPage = 8;
static const int kPageMask = (1 << kLogCharsPerPage) - 1;
// mFamilyVec holds the indices of the mFamilies and mRanges holds the range of indices of
// mFamilyVec. The maximum number of pages is 0x10FF (U+10FFFF >> 8). The maximum number of
// the fonts is 0xFF. Thus, technically the maximum length of mFamilyVec is 0x10EE01
// (0x10FF * 0xFF). However, in practice, 16-bit integers are enough since most fonts supports
// only limited range of code points.
struct Range {
uint16_t start;
uint16_t end;
};
// Initialize the FontCollection.
void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
uint32_t langListId, int variant) const;
uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
const std::shared_ptr<FontFamily>& fontFamily) const;
uint32_t calcCoverageScore(uint32_t ch, uint32_t vs,
const std::shared_ptr<FontFamily>& fontFamily) const;
static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,
const FontFamily& fontFamily);
static uint32_t calcVariantMatchingScore(int variant, const FontFamily& fontFamily);
// static for allocating unique id's
static uint32_t sNextId;
// unique id for this font collection (suitable for cache key)
uint32_t mId;
// Highest UTF-32 code point that can be mapped
uint32_t mMaxChar;
// This vector has pointers to the all font family instances in this collection.
// This vector can't be empty.
std::vector<std::shared_ptr<FontFamily>> mFamilies;
// Following two vectors are pre-calculated tables for resolving coverage faster.
// For example, to iterate over all fonts which support Unicode code point U+XXYYZZ,
// iterate font families index from mFamilyVec[mRanges[0xXXYY].start] to
// mFamilyVec[mRange[0xXXYY].end] instead of whole mFamilies.
// This vector contains indices into mFamilies.
// This vector can't be empty.
std::vector<Range> mRanges;
std::vector<uint8_t> mFamilyVec;
// This vector has pointers to the font family instances which have cmap 14 subtables.
std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec;
// Set of supported axes in this collection.
std::unordered_set<AxisTag> mSupportedAxes;
public:
explicit FontCollection(
const std::vector<std::shared_ptr<FontFamily>>& typefaces);
explicit FontCollection(std::shared_ptr<FontFamily>&& typeface);
struct Run {
FakedFont fakedFont;
int start;
int end;
};
void itemize(const uint16_t* string,
size_t string_length,
FontStyle style,
std::vector<Run>* result) const;
// Returns true if there is a glyph for the code point and variation selector
// pair. Returns false if no fonts have a glyph for the code point and
// variation selector pair, or invalid variation selector is passed.
bool hasVariationSelector(uint32_t baseCodepoint,
uint32_t variationSelector) const;
// Get base font with fakery information (fake bold could affect metrics)
FakedFont baseFontFaked(FontStyle style);
// Creates new FontCollection based on this collection while applying font
// variations. Returns nullptr if none of variations apply to this collection.
std::shared_ptr<FontCollection> createCollectionWithVariation(
const std::vector<FontVariation>& variations);
const std::unordered_set<AxisTag>& getSupportedTags() const {
return mSupportedAxes;
}
uint32_t getId() const;
private:
static const int kLogCharsPerPage = 8;
static const int kPageMask = (1 << kLogCharsPerPage) - 1;
// mFamilyVec holds the indices of the mFamilies and mRanges holds the range
// of indices of mFamilyVec. The maximum number of pages is 0x10FF (U+10FFFF
// >> 8). The maximum number of the fonts is 0xFF. Thus, technically the
// maximum length of mFamilyVec is 0x10EE01 (0x10FF * 0xFF). However, in
// practice, 16-bit integers are enough since most fonts supports only limited
// range of code points.
struct Range {
uint16_t start;
uint16_t end;
};
// Initialize the FontCollection.
void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch,
uint32_t vs,
uint32_t langListId,
int variant) const;
uint32_t calcFamilyScore(uint32_t ch,
uint32_t vs,
int variant,
uint32_t langListId,
const std::shared_ptr<FontFamily>& fontFamily) const;
uint32_t calcCoverageScore(
uint32_t ch,
uint32_t vs,
const std::shared_ptr<FontFamily>& fontFamily) const;
static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,
const FontFamily& fontFamily);
static uint32_t calcVariantMatchingScore(int variant,
const FontFamily& fontFamily);
// static for allocating unique id's
static uint32_t sNextId;
// unique id for this font collection (suitable for cache key)
uint32_t mId;
// Highest UTF-32 code point that can be mapped
uint32_t mMaxChar;
// This vector has pointers to the all font family instances in this
// collection. This vector can't be empty.
std::vector<std::shared_ptr<FontFamily>> mFamilies;
// Following two vectors are pre-calculated tables for resolving coverage
// faster. For example, to iterate over all fonts which support Unicode code
// point U+XXYYZZ, iterate font families index from
// mFamilyVec[mRanges[0xXXYY].start] to mFamilyVec[mRange[0xXXYY].end] instead
// of whole mFamilies. This vector contains indices into mFamilies. This
// vector can't be empty.
std::vector<Range> mRanges;
std::vector<uint8_t> mFamilyVec;
// This vector has pointers to the font family instances which have cmap 14
// subtables.
std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec;
// Set of supported axes in this collection.
std::unordered_set<AxisTag> mSupportedAxes;
};
} // namespace minikin
......
......@@ -23,224 +23,238 @@
#include <log/log.h>
#include <utils/JenkinsHash.h>
#include <hb.h>
#include <hb-ot.h>
#include <hb.h>
#include <minikin/CmapCoverage.h>
#include <minikin/FontFamily.h>
#include <minikin/MinikinFont.h>
#include "FontLanguage.h"
#include "FontLanguageListCache.h"
#include "FontUtils.h"
#include "HbFontCache.h"
#include "MinikinInternal.h"
#include <minikin/CmapCoverage.h>
#include <minikin/MinikinFont.h>
#include <minikin/FontFamily.h>
#include <minikin/MinikinFont.h>
using std::vector;
namespace minikin {
FontStyle::FontStyle(int variant, int weight, bool italic)
: FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {
}
: FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {}
FontStyle::FontStyle(uint32_t languageListId, int variant, int weight, bool italic)
: bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {
}
FontStyle::FontStyle(uint32_t languageListId,
int variant,
int weight,
bool italic)
: bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {}
android::hash_t FontStyle::hash() const {
uint32_t hash = android::JenkinsHashMix(0, bits);
hash = android::JenkinsHashMix(hash, mLanguageListId);
return android::JenkinsHashWhiten(hash);
uint32_t hash = android::JenkinsHashMix(0, bits);
hash = android::JenkinsHashMix(hash, mLanguageListId);
return android::JenkinsHashWhiten(hash);
}
// static
uint32_t FontStyle::registerLanguageList(const std::string& languages) {
std::lock_guard<std::mutex> _l(gMinikinLock);
return FontLanguageListCache::getId(languages);
std::lock_guard<std::mutex> _l(gMinikinLock);
return FontLanguageListCache::getId(languages);
}
// static
uint32_t FontStyle::pack(int variant, int weight, bool italic) {
return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
return (weight & kWeightMask) | (italic ? kItalicMask : 0) |
(variant << kVariantShift);
}
Font::Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style)
: typeface(typeface), style(style) {
}
: typeface(typeface), style(style) {}
Font::Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style)
: typeface(typeface), style(style) {
}
: typeface(typeface), style(style) {}
std::unordered_set<AxisTag> Font::getSupportedAxesLocked() const {
const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');
HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));
if (fvarTable.size() == 0) {
return std::unordered_set<AxisTag>();
}
std::unordered_set<AxisTag> supportedAxes;
analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);
return supportedAxes;
const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');
HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));
if (fvarTable.size() == 0) {
return std::unordered_set<AxisTag>();
}
std::unordered_set<AxisTag> supportedAxes;
analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);
return supportedAxes;
}
Font::Font(Font&& o) {
typeface = std::move(o.typeface);
style = o.style;
o.typeface = nullptr;
typeface = std::move(o.typeface);
style = o.style;
o.typeface = nullptr;
}
Font::Font(const Font& o) {
typeface = o.typeface;
style = o.style;
typeface = o.typeface;
style = o.style;
}
// static
FontFamily::FontFamily(std::vector<Font>&& fonts) : FontFamily(0 /* variant */, std::move(fonts)) {
}
FontFamily::FontFamily(std::vector<Font>&& fonts)
: FontFamily(0 /* variant */, std::move(fonts)) {}
FontFamily::FontFamily(int variant, std::vector<Font>&& fonts)
: FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts)) {
}
: FontFamily(FontLanguageListCache::kEmptyListId,
variant,
std::move(fonts)) {}
FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
: mLangId(langId), mVariant(variant), mFonts(std::move(fonts)), mHasVSTable(false) {
computeCoverage();
: mLangId(langId),
mVariant(variant),
mFonts(std::move(fonts)),
mHasVSTable(false) {
computeCoverage();
}
bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
bool* italic) {
std::lock_guard<std::mutex> _l(gMinikinLock);
const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
if (os2Table.get() == nullptr) return false;
return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight, italic);
bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface,
int* weight,
bool* italic) {
std::lock_guard<std::mutex> _l(gMinikinLock);
const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
if (os2Table.get() == nullptr)
return false;
return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight,
italic);
}
// Compute a matching metric between two styles - 0 is an exact match
static int computeMatch(FontStyle style1, FontStyle style2) {
if (style1 == style2) return 0;
int score = abs(style1.getWeight() - style2.getWeight());
if (style1.getItalic() != style2.getItalic()) {
score += 2;
}
return score;
if (style1 == style2)
return 0;
int score = abs(style1.getWeight() - style2.getWeight());
if (style1.getItalic() != style2.getItalic()) {
score += 2;
}
return score;
}
static FontFakery computeFakery(FontStyle wanted, FontStyle actual) {
// If desired weight is semibold or darker, and 2 or more grades
// higher than actual (for example, medium 500 -> bold 700), then
// select fake bold.
int wantedWeight = wanted.getWeight();
bool isFakeBold = wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
return FontFakery(isFakeBold, isFakeItalic);
// If desired weight is semibold or darker, and 2 or more grades
// higher than actual (for example, medium 500 -> bold 700), then
// select fake bold.
int wantedWeight = wanted.getWeight();
bool isFakeBold =
wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
return FontFakery(isFakeBold, isFakeItalic);
}
FakedFont FontFamily::getClosestMatch(FontStyle style) const {
const Font* bestFont = nullptr;
int bestMatch = 0;
for (size_t i = 0; i < mFonts.size(); i++) {
const Font& font = mFonts[i];
int match = computeMatch(font.style, style);
if (i == 0 || match < bestMatch) {
bestFont = &font;
bestMatch = match;
}
const Font* bestFont = nullptr;
int bestMatch = 0;
for (size_t i = 0; i < mFonts.size(); i++) {
const Font& font = mFonts[i];
int match = computeMatch(font.style, style);
if (i == 0 || match < bestMatch) {
bestFont = &font;
bestMatch = match;
}
if (bestFont != nullptr) {
return FakedFont{ bestFont->typeface.get(), computeFakery(style, bestFont->style) };
}
return FakedFont{ nullptr, FontFakery() };
}
if (bestFont != nullptr) {
return FakedFont{bestFont->typeface.get(),
computeFakery(style, bestFont->style)};
}
return FakedFont{nullptr, FontFakery()};
}
bool FontFamily::isColorEmojiFamily() const {
const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
for (size_t i = 0; i < languageList.size(); ++i) {
if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
return true;
}
const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
for (size_t i = 0; i < languageList.size(); ++i) {
if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
return true;
}
return false;
}
return false;
}
void FontFamily::computeCoverage() {
std::lock_guard<std::mutex> _l(gMinikinLock);
const FontStyle defaultStyle;
const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
HbBlob cmapTable(getFontTable(typeface, cmapTag));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
return;
}
mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mHasVSTable);
for (size_t i = 0; i < mFonts.size(); ++i) {
std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked();
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
std::lock_guard<std::mutex> _l(gMinikinLock);
const FontStyle defaultStyle;
const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
HbBlob cmapTable(getFontTable(typeface, cmapTag));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
return;
}
mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(),
&mHasVSTable);
for (size_t i = 0; i < mFonts.size(); ++i) {
std::unordered_set<AxisTag> supportedAxes =
mFonts[i].getSupportedAxesLocked();
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
}
bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) const {
assertMinikinLocked();
if (variationSelector != 0 && !mHasVSTable) {
// Early exit if the variation selector is specified but the font doesn't have a cmap format
// 14 subtable.
return false;
}
const FontStyle defaultStyle;
hb_font_t* font = getHbFontLocked(getClosestMatch(defaultStyle).font);
uint32_t unusedGlyph;
bool result = hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph);
hb_font_destroy(font);
return result;
bool FontFamily::hasGlyph(uint32_t codepoint,
uint32_t variationSelector) const {
assertMinikinLocked();
if (variationSelector != 0 && !mHasVSTable) {
// Early exit if the variation selector is specified but the font doesn't
// have a cmap format 14 subtable.
return false;
}
const FontStyle defaultStyle;
hb_font_t* font = getHbFontLocked(getClosestMatch(defaultStyle).font);
uint32_t unusedGlyph;
bool result =
hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph);
hb_font_destroy(font);
return result;
}
std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation(
const std::vector<FontVariation>& variations) const {
if (variations.empty() || mSupportedAxes.empty()) {
return nullptr;
const std::vector<FontVariation>& variations) const {
if (variations.empty() || mSupportedAxes.empty()) {
return nullptr;
}
bool hasSupportedAxis = false;
for (const FontVariation& variation : variations) {
if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {
hasSupportedAxis = true;
break;
}
bool hasSupportedAxis = false;
for (const FontVariation& variation : variations) {
if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {
hasSupportedAxis = true;
break;
}
if (!hasSupportedAxis) {
// None of variation axes are suppored by this family.
return nullptr;
}
std::vector<Font> fonts;
for (const Font& font : mFonts) {
bool supportedVariations = false;
std::lock_guard<std::mutex> _l(gMinikinLock);
std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
if (!supportedAxes.empty()) {
for (const FontVariation& variation : variations) {
if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {
supportedVariations = true;
break;
}
}
}
if (!hasSupportedAxis) {
// None of variation axes are suppored by this family.
return nullptr;
std::shared_ptr<MinikinFont> minikinFont;
if (supportedVariations) {
minikinFont = font.typeface->createFontWithVariation(variations);
}
std::vector<Font> fonts;
for (const Font& font : mFonts) {
bool supportedVariations = false;
std::lock_guard<std::mutex> _l(gMinikinLock);
std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
if (!supportedAxes.empty()) {
for (const FontVariation& variation : variations) {
if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {
supportedVariations = true;
break;
}
}
}
std::shared_ptr<MinikinFont> minikinFont;
if (supportedVariations) {
minikinFont = font.typeface->createFontWithVariation(variations);
}
if (minikinFont == nullptr) {
minikinFont = font.typeface;
}
fonts.push_back(Font(std::move(minikinFont), font.style));
if (minikinFont == nullptr) {
minikinFont = font.typeface;
}
fonts.push_back(Font(std::move(minikinFont), font.style));
}
return std::shared_ptr<FontFamily>(new FontFamily(mLangId, mVariant, std::move(fonts)));
return std::shared_ptr<FontFamily>(
new FontFamily(mLangId, mVariant, std::move(fonts)));
}
} // namespace minikin
......@@ -36,142 +36,157 @@ class MinikinFont;
// from a collection. The implementation is packed into two 32-bit words
// so it can be efficiently copied, embedded in other objects, etc.
class FontStyle {
public:
FontStyle() : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}
FontStyle(int weight, bool italic) : FontStyle(0 /* variant */, weight, italic) {}
FontStyle(uint32_t langListId) // NOLINT(implicit)
: FontStyle(langListId, 0 /* variant */, 4 /* weight */, false /* italic */) {}
FontStyle(int variant, int weight, bool italic);
FontStyle(uint32_t langListId, int variant, int weight, bool italic);
int getWeight() const { return bits & kWeightMask; }
bool getItalic() const { return (bits & kItalicMask) != 0; }
int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }
uint32_t getLanguageListId() const { return mLanguageListId; }
bool operator==(const FontStyle other) const {
return bits == other.bits && mLanguageListId == other.mLanguageListId;
}
android::hash_t hash() const;
// Looks up a language list from an internal cache and returns its ID.
// If the passed language list is not in the cache, registers it and returns newly assigned ID.
static uint32_t registerLanguageList(const std::string& languages);
private:
static const uint32_t kWeightMask = (1 << 4) - 1;
static const uint32_t kItalicMask = 1 << 4;
static const int kVariantShift = 5;
static const uint32_t kVariantMask = (1 << 2) - 1;
static uint32_t pack(int variant, int weight, bool italic);
uint32_t bits;
uint32_t mLanguageListId;
public:
FontStyle()
: FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}
FontStyle(int weight, bool italic)
: FontStyle(0 /* variant */, weight, italic) {}
FontStyle(uint32_t langListId) // NOLINT(implicit)
: FontStyle(langListId,
0 /* variant */,
4 /* weight */,
false /* italic */) {}
FontStyle(int variant, int weight, bool italic);
FontStyle(uint32_t langListId, int variant, int weight, bool italic);
int getWeight() const { return bits & kWeightMask; }
bool getItalic() const { return (bits & kItalicMask) != 0; }
int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }
uint32_t getLanguageListId() const { return mLanguageListId; }
bool operator==(const FontStyle other) const {
return bits == other.bits && mLanguageListId == other.mLanguageListId;
}
android::hash_t hash() const;
// Looks up a language list from an internal cache and returns its ID.
// If the passed language list is not in the cache, registers it and returns
// newly assigned ID.
static uint32_t registerLanguageList(const std::string& languages);
private:
static const uint32_t kWeightMask = (1 << 4) - 1;
static const uint32_t kItalicMask = 1 << 4;
static const int kVariantShift = 5;
static const uint32_t kVariantMask = (1 << 2) - 1;
static uint32_t pack(int variant, int weight, bool italic);
uint32_t bits;
uint32_t mLanguageListId;
};
enum FontVariant {
VARIANT_DEFAULT = 0,
VARIANT_COMPACT = 1,
VARIANT_ELEGANT = 2,
VARIANT_DEFAULT = 0,
VARIANT_COMPACT = 1,
VARIANT_ELEGANT = 2,
};
inline android::hash_t hash_type(const FontStyle &style) {
return style.hash();
inline android::hash_t hash_type(const FontStyle& style) {
return style.hash();
}
// attributes representing transforms (fake bold, fake italic) to match styles
class FontFakery {
public:
FontFakery() : mFakeBold(false), mFakeItalic(false) { }
FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) { }
// TODO: want to support graded fake bolding
bool isFakeBold() { return mFakeBold; }
bool isFakeItalic() { return mFakeItalic; }
private:
bool mFakeBold;
bool mFakeItalic;
public:
FontFakery() : mFakeBold(false), mFakeItalic(false) {}
FontFakery(bool fakeBold, bool fakeItalic)
: mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
// TODO: want to support graded fake bolding
bool isFakeBold() { return mFakeBold; }
bool isFakeItalic() { return mFakeItalic; }
private:
bool mFakeBold;
bool mFakeItalic;
};
struct FakedFont {
// ownership is the enclosing FontCollection
MinikinFont* font;
FontFakery fakery;
// ownership is the enclosing FontCollection
MinikinFont* font;
FontFakery fakery;
};
typedef uint32_t AxisTag;
struct Font {
Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);
Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);
Font(Font&& o);
Font(const Font& o);
Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);
Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);
Font(Font&& o);
Font(const Font& o);
std::shared_ptr<MinikinFont> typeface;
FontStyle style;
std::shared_ptr<MinikinFont> typeface;
FontStyle style;
std::unordered_set<AxisTag> getSupportedAxesLocked() const;
std::unordered_set<AxisTag> getSupportedAxesLocked() const;
};
struct FontVariation {
FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
AxisTag axisTag;
float value;
FontVariation(AxisTag axisTag, float value)
: axisTag(axisTag), value(value) {}
AxisTag axisTag;
float value;
};
class FontFamily {
public:
explicit FontFamily(std::vector<Font>&& fonts);
FontFamily(int variant, std::vector<Font>&& fonts);
FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
// TODO: Good to expose FontUtil.h.
static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
bool* italic);
FakedFont getClosestMatch(FontStyle style) const;
uint32_t langId() const { return mLangId; }
int variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any particular order
size_t getNumFonts() const { return mFonts.size(); }
const std::shared_ptr<MinikinFont>& getFont(size_t index) const {
return mFonts[index].typeface;
}
FontStyle getStyle(size_t index) const { return mFonts[index].style; }
bool isColorEmojiFamily() const;
const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
// Get Unicode coverage.
const SparseBitSet& getCoverage() const { return mCoverage; }
// Returns true if the font has a glyph for the code point and variation selector pair.
// Caller should acquire a lock before calling the method.
bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const;
// Returns true if this font family has a variaion sequence table (cmap format 14 subtable).
bool hasVSTable() const { return mHasVSTable; }
// Creates new FontFamily based on this family while applying font variations. Returns nullptr
// if none of variations apply to this family.
std::shared_ptr<FontFamily> createFamilyWithVariation(
const std::vector<FontVariation>& variations) const;
private:
void computeCoverage();
uint32_t mLangId;
int mVariant;
std::vector<Font> mFonts;
std::unordered_set<AxisTag> mSupportedAxes;
SparseBitSet mCoverage;
bool mHasVSTable;
// Forbid copying and assignment.
FontFamily(const FontFamily&) = delete;
void operator=(const FontFamily&) = delete;
public:
explicit FontFamily(std::vector<Font>&& fonts);
FontFamily(int variant, std::vector<Font>&& fonts);
FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
// TODO: Good to expose FontUtil.h.
static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface,
int* weight,
bool* italic);
FakedFont getClosestMatch(FontStyle style) const;
uint32_t langId() const { return mLangId; }
int variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any
// particular order
size_t getNumFonts() const { return mFonts.size(); }
const std::shared_ptr<MinikinFont>& getFont(size_t index) const {
return mFonts[index].typeface;
}
FontStyle getStyle(size_t index) const { return mFonts[index].style; }
bool isColorEmojiFamily() const;
const std::unordered_set<AxisTag>& supportedAxes() const {
return mSupportedAxes;
}
// Get Unicode coverage.
const SparseBitSet& getCoverage() const { return mCoverage; }
// Returns true if the font has a glyph for the code point and variation
// selector pair. Caller should acquire a lock before calling the method.
bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const;
// Returns true if this font family has a variaion sequence table (cmap format
// 14 subtable).
bool hasVSTable() const { return mHasVSTable; }
// Creates new FontFamily based on this family while applying font variations.
// Returns nullptr if none of variations apply to this family.
std::shared_ptr<FontFamily> createFamilyWithVariation(
const std::vector<FontVariation>& variations) const;
private:
void computeCoverage();
uint32_t mLangId;
int mVariant;
std::vector<Font> mFonts;
std::unordered_set<AxisTag> mSupportedAxes;
SparseBitSet mCoverage;
bool mHasVSTable;
// Forbid copying and assignment.
FontFamily(const FontFamily&) = delete;
void operator=(const FontFamily&) = delete;
};
} // namespace minikin
......
......@@ -24,8 +24,8 @@
namespace minikin {
// Due to the limits in font fallback score calculation, we can't use anything more than 12
// languages.
// Due to the limits in font fallback score calculation, we can't use anything
// more than 12 languages.
const size_t FONT_LANGUAGES_LIMIT = 12;
// The language or region code is encoded to 15 bits.
......@@ -37,120 +37,123 @@ class FontLanguages;
// does not capture all possible information, only what directly affects
// font rendering.
struct FontLanguage {
public:
enum EmojiStyle : uint8_t {
EMSTYLE_EMPTY = 0,
EMSTYLE_DEFAULT = 1,
EMSTYLE_EMOJI = 2,
EMSTYLE_TEXT = 3,
};
// Default constructor creates the unsupported language.
FontLanguage()
: mScript(0ul),
mLanguage(INVALID_CODE),
mRegion(INVALID_CODE),
mHbLanguage(HB_LANGUAGE_INVALID),
mSubScriptBits(0ul),
mEmojiStyle(EMSTYLE_EMPTY) {}
// Parse from string
FontLanguage(const char* buf, size_t length);
bool operator==(const FontLanguage other) const {
return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
mRegion == other.mRegion && mEmojiStyle == other.mEmojiStyle;
}
bool operator!=(const FontLanguage other) const {
return !(*this == other);
}
bool isUnsupported() const { return mLanguage == INVALID_CODE; }
EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
hb_language_t getHbLanguage() const { return mHbLanguage; }
bool isEqualScript(const FontLanguage& other) const;
// Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
// ja-Hira doesn't support Jpan.
bool supportsHbScript(hb_script_t script) const;
std::string getString() const;
// Calculates a matching score. This score represents how well the input languages cover this
// language. The maximum score in the language list is returned.
// 0 = no match, 1 = script match, 2 = script and primary language match.
int calcScoreFor(const FontLanguages& supported) const;
uint64_t getIdentifier() const {
return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) | ((uint64_t)mRegion << 2) |
mEmojiStyle;
}
private:
friend class FontLanguages; // for FontLanguages constructor
// ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
uint32_t mScript;
// ISO 639-1 or ISO 639-2 compliant language code.
// The two- or three-letter language code is packed into a 15 bit integer.
// mLanguage = 0 means the FontLanguage is unsupported.
uint16_t mLanguage;
// ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
// packed into a 15 bit integer.
uint16_t mRegion;
// The language to be passed HarfBuzz shaper.
hb_language_t mHbLanguage;
// For faster comparing, use 7 bits for specific scripts.
static const uint8_t kBopomofoFlag = 1u;
static const uint8_t kHanFlag = 1u << 1;
static const uint8_t kHangulFlag = 1u << 2;
static const uint8_t kHiraganaFlag = 1u << 3;
static const uint8_t kKatakanaFlag = 1u << 4;
static const uint8_t kSimplifiedChineseFlag = 1u << 5;
static const uint8_t kTraditionalChineseFlag = 1u << 6;
uint8_t mSubScriptBits;
EmojiStyle mEmojiStyle;
static uint8_t scriptToSubScriptBits(uint32_t script);
static EmojiStyle resolveEmojiStyle(const char* buf, size_t length, uint32_t script);
// Returns true if the provide subscript bits has the requested subscript bits.
// Note that this function returns false if the requested subscript bits are empty.
static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
public:
enum EmojiStyle : uint8_t {
EMSTYLE_EMPTY = 0,
EMSTYLE_DEFAULT = 1,
EMSTYLE_EMOJI = 2,
EMSTYLE_TEXT = 3,
};
// Default constructor creates the unsupported language.
FontLanguage()
: mScript(0ul),
mLanguage(INVALID_CODE),
mRegion(INVALID_CODE),
mHbLanguage(HB_LANGUAGE_INVALID),
mSubScriptBits(0ul),
mEmojiStyle(EMSTYLE_EMPTY) {}
// Parse from string
FontLanguage(const char* buf, size_t length);
bool operator==(const FontLanguage other) const {
return !isUnsupported() && isEqualScript(other) &&
mLanguage == other.mLanguage && mRegion == other.mRegion &&
mEmojiStyle == other.mEmojiStyle;
}
bool operator!=(const FontLanguage other) const { return !(*this == other); }
bool isUnsupported() const { return mLanguage == INVALID_CODE; }
EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
hb_language_t getHbLanguage() const { return mHbLanguage; }
bool isEqualScript(const FontLanguage& other) const;
// Returns true if this script supports the given script. For example, ja-Jpan
// supports Hira, ja-Hira doesn't support Jpan.
bool supportsHbScript(hb_script_t script) const;
std::string getString() const;
// Calculates a matching score. This score represents how well the input
// languages cover this language. The maximum score in the language list is
// returned. 0 = no match, 1 = script match, 2 = script and primary language
// match.
int calcScoreFor(const FontLanguages& supported) const;
uint64_t getIdentifier() const {
return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) |
((uint64_t)mRegion << 2) | mEmojiStyle;
}
private:
friend class FontLanguages; // for FontLanguages constructor
// ISO 15924 compliant script code. The 4 chars script code are packed into a
// 32 bit integer.
uint32_t mScript;
// ISO 639-1 or ISO 639-2 compliant language code.
// The two- or three-letter language code is packed into a 15 bit integer.
// mLanguage = 0 means the FontLanguage is unsupported.
uint16_t mLanguage;
// ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit
// region code is packed into a 15 bit integer.
uint16_t mRegion;
// The language to be passed HarfBuzz shaper.
hb_language_t mHbLanguage;
// For faster comparing, use 7 bits for specific scripts.
static const uint8_t kBopomofoFlag = 1u;
static const uint8_t kHanFlag = 1u << 1;
static const uint8_t kHangulFlag = 1u << 2;
static const uint8_t kHiraganaFlag = 1u << 3;
static const uint8_t kKatakanaFlag = 1u << 4;
static const uint8_t kSimplifiedChineseFlag = 1u << 5;
static const uint8_t kTraditionalChineseFlag = 1u << 6;
uint8_t mSubScriptBits;
EmojiStyle mEmojiStyle;
static uint8_t scriptToSubScriptBits(uint32_t script);
static EmojiStyle resolveEmojiStyle(const char* buf,
size_t length,
uint32_t script);
// Returns true if the provide subscript bits has the requested subscript
// bits. Note that this function returns false if the requested subscript bits
// are empty.
static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
};
// An immutable list of languages.
class FontLanguages {
public:
explicit FontLanguages(std::vector<FontLanguage>&& languages);
FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
FontLanguages(FontLanguages&&) = default;
public:
explicit FontLanguages(std::vector<FontLanguage>&& languages);
FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
FontLanguages(FontLanguages&&) = default;
size_t size() const { return mLanguages.size(); }
bool empty() const { return mLanguages.empty(); }
const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
size_t size() const { return mLanguages.size(); }
bool empty() const { return mLanguages.empty(); }
const FontLanguage& operator[](size_t n) const { return mLanguages[n]; }
private:
friend struct FontLanguage; // for calcScoreFor
private:
friend struct FontLanguage; // for calcScoreFor
std::vector<FontLanguage> mLanguages;
uint8_t mUnionOfSubScriptBits;
bool mIsAllTheSameLanguage;
std::vector<FontLanguage> mLanguages;
uint8_t mUnionOfSubScriptBits;
bool mIsAllTheSameLanguage;
uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
// Do not copy and assign.
FontLanguages(const FontLanguages&) = delete;
void operator=(const FontLanguages&) = delete;
// Do not copy and assign.
FontLanguages(const FontLanguages&) = delete;
void operator=(const FontLanguages&) = delete;
};
} // namespace minikin
......
......@@ -31,126 +31,136 @@ namespace minikin {
const uint32_t FontLanguageListCache::kEmptyListId;
// Returns the text length of output.
static size_t toLanguageTag(char* output, size_t outSize, const std::string& locale) {
static size_t toLanguageTag(char* output,
size_t outSize,
const std::string& locale) {
output[0] = '\0';
if (locale.empty()) {
return 0;
}
size_t outLength = 0;
UErrorCode uErr = U_ZERO_ERROR;
outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(),
u_errorName(uErr));
output[0] = '\0';
if (locale.empty()) {
return 0;
}
size_t outLength = 0;
UErrorCode uErr = U_ZERO_ERROR;
outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(), u_errorName(uErr));
output[0] = '\0';
return 0;
}
// Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
if (strncmp(output, "und", 3) == 0 &&
(outLength == 3 || (outLength == 8 && output[3] == '_'))) {
return outLength;
}
return 0;
}
char likelyChars[ULOC_FULLNAME_CAPACITY];
uErr = U_ZERO_ERROR;
uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
output[0] = '\0';
return 0;
}
uErr = U_ZERO_ERROR;
outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
output[0] = '\0';
return 0;
}
// Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to
// "en-Latn-US".
if (strncmp(output, "und", 3) == 0 &&
(outLength == 3 || (outLength == 8 && output[3] == '_'))) {
return outLength;
}
char likelyChars[ULOC_FULLNAME_CAPACITY];
uErr = U_ZERO_ERROR;
uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output,
u_errorName(uErr));
output[0] = '\0';
return 0;
}
uErr = U_ZERO_ERROR;
outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
if (U_FAILURE(uErr)) {
// unable to build a proper language identifier
ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars,
u_errorName(uErr));
output[0] = '\0';
return 0;
}
#ifdef VERBOSE_DEBUG
ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output);
ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output);
#endif
return outLength;
return outLength;
}
static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
std::vector<FontLanguage> result;
size_t currentIdx = 0;
size_t commaLoc = 0;
char langTag[ULOC_FULLNAME_CAPACITY];
std::unordered_set<uint64_t> seen;
std::string locale(input.size(), 0);
while ((commaLoc = input.find_first_of(',', currentIdx)) != std::string::npos) {
locale.assign(input, currentIdx, commaLoc - currentIdx);
currentIdx = commaLoc + 1;
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
if (result.size() == FONT_LANGUAGES_LIMIT) {
break;
}
seen.insert(identifier);
}
}
if (result.size() < FONT_LANGUAGES_LIMIT) {
locale.assign(input, currentIdx, input.size() - currentIdx);
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
std::vector<FontLanguage> result;
size_t currentIdx = 0;
size_t commaLoc = 0;
char langTag[ULOC_FULLNAME_CAPACITY];
std::unordered_set<uint64_t> seen;
std::string locale(input.size(), 0);
while ((commaLoc = input.find_first_of(',', currentIdx)) !=
std::string::npos) {
locale.assign(input, currentIdx, commaLoc - currentIdx);
currentIdx = commaLoc + 1;
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
if (result.size() == FONT_LANGUAGES_LIMIT) {
break;
}
seen.insert(identifier);
}
}
if (result.size() < FONT_LANGUAGES_LIMIT) {
locale.assign(input, currentIdx, input.size() - currentIdx);
size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
FontLanguage lang(langTag, length);
uint64_t identifier = lang.getIdentifier();
if (!lang.isUnsupported() && seen.count(identifier) == 0) {
result.push_back(lang);
}
return result;
}
return result;
}
// static
uint32_t FontLanguageListCache::getId(const std::string& languages) {
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
std::unordered_map<std::string, uint32_t>::const_iterator it =
inst->mLanguageListLookupTable.find(languages);
if (it != inst->mLanguageListLookupTable.end()) {
return it->second;
}
// Given language list is not in cache. Insert it and return newly assigned ID.
const uint32_t nextId = inst->mLanguageLists.size();
FontLanguages fontLanguages(parseLanguageList(languages));
if (fontLanguages.empty()) {
return kEmptyListId;
}
inst->mLanguageLists.push_back(std::move(fontLanguages));
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
return nextId;
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
std::unordered_map<std::string, uint32_t>::const_iterator it =
inst->mLanguageListLookupTable.find(languages);
if (it != inst->mLanguageListLookupTable.end()) {
return it->second;
}
// Given language list is not in cache. Insert it and return newly assigned
// ID.
const uint32_t nextId = inst->mLanguageLists.size();
FontLanguages fontLanguages(parseLanguageList(languages));
if (fontLanguages.empty()) {
return kEmptyListId;
}
inst->mLanguageLists.push_back(std::move(fontLanguages));
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
return nextId;
}
// static
const FontLanguages& FontLanguageListCache::getById(uint32_t id) {
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(), "Lookup by unknown language list ID.");
return inst->mLanguageLists[id];
FontLanguageListCache* inst = FontLanguageListCache::getInstance();
LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(),
"Lookup by unknown language list ID.");
return inst->mLanguageLists[id];
}
// static
FontLanguageListCache* FontLanguageListCache::getInstance() {
assertMinikinLocked();
static FontLanguageListCache* instance = nullptr;
if (instance == nullptr) {
instance = new FontLanguageListCache();
// Insert an empty language list for mapping default language list to kEmptyListId.
// The default language list has only one FontLanguage and it is the unsupported language.
instance->mLanguageLists.push_back(FontLanguages());
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
}
return instance;
assertMinikinLocked();
static FontLanguageListCache* instance = nullptr;
if (instance == nullptr) {
instance = new FontLanguageListCache();
// Insert an empty language list for mapping default language list to
// kEmptyListId. The default language list has only one FontLanguage and it
// is the unsupported language.
instance->mLanguageLists.push_back(FontLanguages());
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
}
return instance;
}
} // namespace minikin
......@@ -25,30 +25,30 @@
namespace minikin {
class FontLanguageListCache {
public:
// A special ID for the empty language list.
// This value must be 0 since the empty language list is inserted into mLanguageLists by
// default.
const static uint32_t kEmptyListId = 0;
public:
// A special ID for the empty language list.
// This value must be 0 since the empty language list is inserted into
// mLanguageLists by default.
const static uint32_t kEmptyListId = 0;
// Returns language list ID for the given string representation of FontLanguages.
// Caller should acquire a lock before calling the method.
static uint32_t getId(const std::string& languages);
// Returns language list ID for the given string representation of
// FontLanguages. Caller should acquire a lock before calling the method.
static uint32_t getId(const std::string& languages);
// Caller should acquire a lock before calling the method.
static const FontLanguages& getById(uint32_t id);
// Caller should acquire a lock before calling the method.
static const FontLanguages& getById(uint32_t id);
private:
FontLanguageListCache() {} // Singleton
~FontLanguageListCache() {}
private:
FontLanguageListCache() {} // Singleton
~FontLanguageListCache() {}
// Caller should acquire a lock before calling the method.
static FontLanguageListCache* getInstance();
// Caller should acquire a lock before calling the method.
static FontLanguageListCache* getInstance();
std::vector<FontLanguages> mLanguageLists;
std::vector<FontLanguages> mLanguageLists;
// A map from string representation of the font language list to the ID.
std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;
// A map from string representation of the font language list to the ID.
std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;
};
} // namespace minikin
......
......@@ -14,64 +14,70 @@
* limitations under the License.
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdlib.h>
#include "FontUtils.h"
namespace minikin {
static uint16_t readU16(const uint8_t* data, size_t offset) {
return data[offset] << 8 | data[offset + 1];
return data[offset] << 8 | data[offset + 1];
}
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic) {
const size_t kUsWeightClassOffset = 4;
const size_t kFsSelectionOffset = 62;
const uint16_t kItalicFlag = (1 << 0);
if (os2_size < kFsSelectionOffset + 2) {
return false;
}
uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);
*weight = weightClass / 100;
uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);
*italic = (fsSelection & kItalicFlag) != 0;
return true;
bool analyzeStyle(const uint8_t* os2_data,
size_t os2_size,
int* weight,
bool* italic) {
const size_t kUsWeightClassOffset = 4;
const size_t kFsSelectionOffset = 62;
const uint16_t kItalicFlag = (1 << 0);
if (os2_size < kFsSelectionOffset + 2) {
return false;
}
uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);
*weight = weightClass / 100;
uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);
*italic = (fsSelection & kItalicFlag) != 0;
return true;
}
void analyzeAxes(const uint8_t* fvar_data, size_t fvar_size, std::unordered_set<uint32_t>* axes) {
const size_t kMajorVersionOffset = 0;
const size_t kMinorVersionOffset = 2;
const size_t kOffsetToAxesArrayOffset = 4;
const size_t kAxisCountOffset = 8;
const size_t kAxisSizeOffset = 10;
void analyzeAxes(const uint8_t* fvar_data,
size_t fvar_size,
std::unordered_set<uint32_t>* axes) {
const size_t kMajorVersionOffset = 0;
const size_t kMinorVersionOffset = 2;
const size_t kOffsetToAxesArrayOffset = 4;
const size_t kAxisCountOffset = 8;
const size_t kAxisSizeOffset = 10;
axes->clear();
axes->clear();
if (fvar_size < kAxisSizeOffset + 2) {
return;
}
const uint16_t majorVersion = readU16(fvar_data, kMajorVersionOffset);
const uint16_t minorVersion = readU16(fvar_data, kMinorVersionOffset);
const uint32_t axisOffset = readU16(fvar_data, kOffsetToAxesArrayOffset);
const uint32_t axisCount = readU16(fvar_data, kAxisCountOffset);
const uint32_t axisSize = readU16(fvar_data, kAxisSizeOffset);
if (fvar_size < kAxisSizeOffset + 2) {
return;
}
const uint16_t majorVersion = readU16(fvar_data, kMajorVersionOffset);
const uint16_t minorVersion = readU16(fvar_data, kMinorVersionOffset);
const uint32_t axisOffset = readU16(fvar_data, kOffsetToAxesArrayOffset);
const uint32_t axisCount = readU16(fvar_data, kAxisCountOffset);
const uint32_t axisSize = readU16(fvar_data, kAxisSizeOffset);
if (majorVersion != 1 || minorVersion != 0 || axisOffset != 0x10 || axisSize != 0x14) {
return; // Unsupported version.
}
if (fvar_size < axisOffset + axisOffset * axisCount) {
return; // Invalid table size.
}
for (uint32_t i = 0; i < axisCount; ++i) {
size_t axisRecordOffset = axisOffset + i * axisSize;
uint32_t tag = readU32(fvar_data, axisRecordOffset);
axes->insert(tag);
}
if (majorVersion != 1 || minorVersion != 0 || axisOffset != 0x10 ||
axisSize != 0x14) {
return; // Unsupported version.
}
if (fvar_size < axisOffset + axisOffset * axisCount) {
return; // Invalid table size.
}
for (uint32_t i = 0; i < axisCount; ++i) {
size_t axisRecordOffset = axisOffset + i * axisSize;
uint32_t tag = readU32(fvar_data, axisRecordOffset);
axes->insert(tag);
}
}
} // namespace minikin
......@@ -21,8 +21,13 @@
namespace minikin {
bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic);
void analyzeAxes(const uint8_t* fvar_data, size_t fvar_size, std::unordered_set<uint32_t>* axes);
bool analyzeStyle(const uint8_t* os2_data,
size_t os2_size,
int* weight,
bool* italic);
void analyzeAxes(const uint8_t* fvar_data,
size_t fvar_size,
std::unordered_set<uint32_t>* axes);
} // namespace minikin
......
......@@ -15,227 +15,245 @@
*/
#include <stdint.h>
#include <algorithm>
#include <unicode/uchar.h>
#include <unicode/utf16.h>
#include <algorithm>
#include <minikin/GraphemeBreak.h>
#include <minikin/Emoji.h>
#include <minikin/GraphemeBreak.h>
#include "MinikinInternal.h"
namespace minikin {
int32_t tailoredGraphemeClusterBreak(uint32_t c) {
// Characters defined as Control that we want to treat them as Extend.
// These are curated manually.
if (c == 0x00AD // SHY
|| c == 0x061C // ALM
|| c == 0x180E // MONGOLIAN VOWEL SEPARATOR
|| c == 0x200B // ZWSP
|| c == 0x200E // LRM
|| c == 0x200F // RLM
|| (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
|| ((c | 0xF) == 0x206F) // WJ, invisible math operators, LRI, RLI, FSI, PDI,
// and the deprecated invisible format controls
|| c == 0xFEFF // BOM
|| ((c | 0x7F) == 0xE007F)) // recently undeprecated tag characters in Plane 14
return U_GCB_EXTEND;
// THAI CHARACTER SARA AM is treated as a normal letter by most other implementations: they
// allow a grapheme break before it.
else if (c == 0x0E33)
return U_GCB_OTHER;
else
return u_getIntPropertyValue(c, UCHAR_GRAPHEME_CLUSTER_BREAK);
// Characters defined as Control that we want to treat them as Extend.
// These are curated manually.
if (c == 0x00AD // SHY
|| c == 0x061C // ALM
|| c == 0x180E // MONGOLIAN VOWEL SEPARATOR
|| c == 0x200B // ZWSP
|| c == 0x200E // LRM
|| c == 0x200F // RLM
|| (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
|| ((c | 0xF) ==
0x206F) // WJ, invisible math operators, LRI, RLI, FSI, PDI,
// and the deprecated invisible format controls
|| c == 0xFEFF // BOM
|| ((c | 0x7F) ==
0xE007F)) // recently undeprecated tag characters in Plane 14
return U_GCB_EXTEND;
// THAI CHARACTER SARA AM is treated as a normal letter by most other
// implementations: they allow a grapheme break before it.
else if (c == 0x0E33)
return U_GCB_OTHER;
else
return u_getIntPropertyValue(c, UCHAR_GRAPHEME_CLUSTER_BREAK);
}
// Returns true for all characters whose IndicSyllabicCategory is Pure_Killer.
// From http://www.unicode.org/Public/9.0.0/ucd/IndicSyllabicCategory.txt
bool isPureKiller(uint32_t c) {
return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 || c == 0x1734
|| c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 || c == 0xA806
|| c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);
return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A ||
c == 0x1714 || c == 0x1734 || c == 0x17D1 || c == 0x1BAA ||
c == 0x1BF2 || c == 0x1BF3 || c == 0xA806 || c == 0xA953 ||
c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);
}
bool GraphemeBreak::isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
size_t count, const size_t offset) {
// This implementation closely follows Unicode Standard Annex #29 on
// Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),
// implementing a tailored version of extended grapheme clusters.
// The GB rules refer to section 3.1.1, Grapheme Cluster Boundary Rules.
bool GraphemeBreak::isGraphemeBreak(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
const size_t offset) {
// This implementation closely follows Unicode Standard Annex #29 on
// Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),
// implementing a tailored version of extended grapheme clusters.
// The GB rules refer to section 3.1.1, Grapheme Cluster Boundary Rules.
// Rule GB1, sot ÷; Rule GB2, ÷ eot
if (offset <= start || offset >= start + count) {
return true;
}
if (U16_IS_TRAIL(buf[offset])) {
// Don't break a surrogate pair, but a lonely trailing surrogate pair is a break
return !U16_IS_LEAD(buf[offset - 1]);
}
uint32_t c1 = 0;
uint32_t c2 = 0;
size_t offset_back = offset;
size_t offset_forward = offset;
U16_PREV(buf, start, offset_back, c1);
U16_NEXT(buf, offset_forward, start + count, c2);
int32_t p1 = tailoredGraphemeClusterBreak(c1);
int32_t p2 = tailoredGraphemeClusterBreak(c2);
// Rule GB3, CR x LF
if (p1 == U_GCB_CR && p2 == U_GCB_LF) {
return false;
}
// Rule GB4, (Control | CR | LF) ÷
if (p1 == U_GCB_CONTROL || p1 == U_GCB_CR || p1 == U_GCB_LF) {
return true;
}
// Rule GB5, ÷ (Control | CR | LF)
if (p2 == U_GCB_CONTROL || p2 == U_GCB_CR || p2 == U_GCB_LF) {
return true;
}
// Rule GB6, L x ( L | V | LV | LVT )
if (p1 == U_GCB_L && (p2 == U_GCB_L || p2 == U_GCB_V || p2 == U_GCB_LV || p2 == U_GCB_LVT)) {
return false;
}
// Rule GB7, ( LV | V ) x ( V | T )
if ((p1 == U_GCB_LV || p1 == U_GCB_V) && (p2 == U_GCB_V || p2 == U_GCB_T)) {
return false;
}
// Rule GB8, ( LVT | T ) x T
if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {
return false;
}
// Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
// TODO(abarth): Add U_GCB_ZWJ once we update ICU.
if (p2 == U_GCB_EXTEND || /* p2 == U_GCB_ZWJ || */ p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
return false;
}
// Rule GB1, sot ÷; Rule GB2, ÷ eot
if (offset <= start || offset >= start + count) {
return true;
}
if (U16_IS_TRAIL(buf[offset])) {
// Don't break a surrogate pair, but a lonely trailing surrogate pair is a
// break
return !U16_IS_LEAD(buf[offset - 1]);
}
uint32_t c1 = 0;
uint32_t c2 = 0;
size_t offset_back = offset;
size_t offset_forward = offset;
U16_PREV(buf, start, offset_back, c1);
U16_NEXT(buf, offset_forward, start + count, c2);
int32_t p1 = tailoredGraphemeClusterBreak(c1);
int32_t p2 = tailoredGraphemeClusterBreak(c2);
// Rule GB3, CR x LF
if (p1 == U_GCB_CR && p2 == U_GCB_LF) {
return false;
}
// Rule GB4, (Control | CR | LF) ÷
if (p1 == U_GCB_CONTROL || p1 == U_GCB_CR || p1 == U_GCB_LF) {
return true;
}
// Rule GB5, ÷ (Control | CR | LF)
if (p2 == U_GCB_CONTROL || p2 == U_GCB_CR || p2 == U_GCB_LF) {
return true;
}
// Rule GB6, L x ( L | V | LV | LVT )
if (p1 == U_GCB_L &&
(p2 == U_GCB_L || p2 == U_GCB_V || p2 == U_GCB_LV || p2 == U_GCB_LVT)) {
return false;
}
// Rule GB7, ( LV | V ) x ( V | T )
if ((p1 == U_GCB_LV || p1 == U_GCB_V) && (p2 == U_GCB_V || p2 == U_GCB_T)) {
return false;
}
// Rule GB8, ( LVT | T ) x T
if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {
return false;
}
// Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
// TODO(abarth): Add U_GCB_ZWJ once we update ICU.
if (p2 == U_GCB_EXTEND || /* p2 == U_GCB_ZWJ || */ p2 == U_GCB_SPACING_MARK ||
p1 == U_GCB_PREPEND) {
return false;
}
// This is used to decide font-dependent grapheme clusters. If we don't have the advance
// information, we become conservative in grapheme breaking and assume that it has no advance.
const bool c2_has_advance = (advances != nullptr && advances[offset - start] != 0.0);
// This is used to decide font-dependent grapheme clusters. If we don't have
// the advance information, we become conservative in grapheme breaking and
// assume that it has no advance.
const bool c2_has_advance =
(advances != nullptr && advances[offset - start] != 0.0);
// All the following rules are font-dependent, in the way that if we know c2 has an advance,
// we definitely know that it cannot form a grapheme with the character(s) before it. So we
// make the decision in favor a grapheme break early.
if (c2_has_advance) {
return true;
}
// All the following rules are font-dependent, in the way that if we know c2
// has an advance, we definitely know that it cannot form a grapheme with the
// character(s) before it. So we make the decision in favor a grapheme break
// early.
if (c2_has_advance) {
return true;
}
// Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking properties for
// determining emoji-ness and carry our own data, because our data could be more fresh than what
// ICU provides.
//
// Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.
// The rule itself says do not break between emoji base and emoji modifiers, skipping all Extend
// characters. Variation selectors are considered Extend, so they are handled fine.
//
// We tailor this by requiring that an actual ligature is formed. If the font doesn't form a
// ligature, we allow a break before the modifier.
if (isEmojiModifier(c2)) {
uint32_t c0 = c1;
size_t offset_backback = offset_back;
int32_t p0 = p1;
if (p0 == U_GCB_EXTEND && offset_backback > start) {
// skip over emoji variation selector
U16_PREV(buf, start, offset_backback, c0);
p0 = tailoredGraphemeClusterBreak(c0);
}
if (isEmojiBase(c0)) {
return false;
}
// Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking
// properties for determining emoji-ness and carry our own data, because our
// data could be more fresh than what ICU provides.
//
// Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.
// The rule itself says do not break between emoji base and emoji modifiers,
// skipping all Extend characters. Variation selectors are considered Extend,
// so they are handled fine.
//
// We tailor this by requiring that an actual ligature is formed. If the font
// doesn't form a ligature, we allow a break before the modifier.
if (isEmojiModifier(c2)) {
uint32_t c0 = c1;
size_t offset_backback = offset_back;
int32_t p0 = p1;
if (p0 == U_GCB_EXTEND && offset_backback > start) {
// skip over emoji variation selector
U16_PREV(buf, start, offset_backback, c0);
p0 = tailoredGraphemeClusterBreak(c0);
}
if (isEmojiBase(c0)) {
return false;
}
}
// TODO(abarth): Enablet his code once we update ICU.
// Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)
// We try to make emoji sequences with ZWJ a single grapheme cluster, but only if they actually
// merge to one cluster. So we are more relaxed than the UAX #29 rules in accepting any emoji
// character after the ZWJ, but are tighter in that we only treat it as one cluster if a
// ligature is actually formed and we also require the character before the ZWJ to also be an
// emoji.
// if (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {
// // look at character before ZWJ to see that both can participate in an emoji zwj sequence
// uint32_t c0 = 0;
// size_t offset_backback = offset_back;
// U16_PREV(buf, start, offset_backback, c0);
// if (c0 == 0xFE0F && offset_backback > start) {
// // skip over emoji variation selector
// U16_PREV(buf, start, offset_backback, c0);
// }
// if (isEmoji(c0)) {
// return false;
// }
// }
// TODO(abarth): Enablet his code once we update ICU.
// Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)
// We try to make emoji sequences with ZWJ a single grapheme cluster, but only
// if they actually merge to one cluster. So we are more relaxed than the UAX
// #29 rules in accepting any emoji character after the ZWJ, but are tighter
// in that we only treat it as one cluster if a ligature is actually formed
// and we also require the character before the ZWJ to also be an emoji. if
// (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {
// // look at character before ZWJ to see that both can participate in an
// emoji zwj sequence uint32_t c0 = 0; size_t offset_backback =
// offset_back; U16_PREV(buf, start, offset_backback, c0); if (c0 ==
// 0xFE0F && offset_backback > start) {
// // skip over emoji variation selector
// U16_PREV(buf, start, offset_backback, c0);
// }
// if (isEmoji(c0)) {
// return false;
// }
// }
// Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.
// sot (RI RI)* RI x RI
// [^RI] (RI RI)* RI x RI
//
// If we have font information, we have already broken the cluster if and only if the second
// character had no advance, which means a ligature was formed. If we don't, we look back like
// UAX #29 recommends, but only up to 1000 code units.
if (p1 == U_GCB_REGIONAL_INDICATOR && p2 == U_GCB_REGIONAL_INDICATOR) {
if (advances != nullptr) {
// We have advances information. But if we are here, we already know c2 has no advance.
// So we should definitely disallow a break.
return false;
} else {
// Look at up to 1000 code units.
const size_t lookback_barrier = std::max((ssize_t)start, (ssize_t)offset_back - 1000);
size_t offset_backback = offset_back;
while (offset_backback > lookback_barrier) {
uint32_t c0 = 0;
U16_PREV(buf, lookback_barrier, offset_backback, c0);
if (tailoredGraphemeClusterBreak(c0) != U_GCB_REGIONAL_INDICATOR) {
offset_backback += U16_LENGTH(c0);
break;
}
}
// The number 4 comes from the number of code units in a whole flag.
return (offset - offset_backback) % 4 == 0;
// Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.
// sot (RI RI)* RI x RI
// [^RI] (RI RI)* RI x RI
//
// If we have font information, we have already broken the cluster if and only
// if the second character had no advance, which means a ligature was formed.
// If we don't, we look back like UAX #29 recommends, but only up to 1000 code
// units.
if (p1 == U_GCB_REGIONAL_INDICATOR && p2 == U_GCB_REGIONAL_INDICATOR) {
if (advances != nullptr) {
// We have advances information. But if we are here, we already know c2
// has no advance. So we should definitely disallow a break.
return false;
} else {
// Look at up to 1000 code units.
const size_t lookback_barrier =
std::max((ssize_t)start, (ssize_t)offset_back - 1000);
size_t offset_backback = offset_back;
while (offset_backback > lookback_barrier) {
uint32_t c0 = 0;
U16_PREV(buf, lookback_barrier, offset_backback, c0);
if (tailoredGraphemeClusterBreak(c0) != U_GCB_REGIONAL_INDICATOR) {
offset_backback += U16_LENGTH(c0);
break;
}
}
// The number 4 comes from the number of code units in a whole flag.
return (offset - offset_backback) % 4 == 0;
}
// Cluster Indic syllables together (tailoring of UAX #29).
// Immediately after each virama (that is not just a pure killer) followed by a letter, we
// disallow grapheme breaks (if we are here, we don't know about advances, or we already know
// that c2 has no advance).
if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9 // virama
&& !isPureKiller(c1)
&& u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
return false;
}
// Rule GB999, Any ÷ Any
return true;
}
// Cluster Indic syllables together (tailoring of UAX #29).
// Immediately after each virama (that is not just a pure killer) followed by
// a letter, we disallow grapheme breaks (if we are here, we don't know about
// advances, or we already know that c2 has no advance).
if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9 // virama
&& !isPureKiller(c1) &&
u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
return false;
}
// Rule GB999, Any ÷ Any
return true;
}
size_t GraphemeBreak::getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset, MoveOpt opt) {
switch (opt) {
size_t GraphemeBreak::getTextRunCursor(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
size_t offset,
MoveOpt opt) {
switch (opt) {
case AFTER:
if (offset < start + count) {
offset++;
}
// fall through
if (offset < start + count) {
offset++;
}
// fall through
case AT_OR_AFTER:
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset++;
}
break;
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset++;
}
break;
case BEFORE:
if (offset > start) {
offset--;
}
// fall through
if (offset > start) {
offset--;
}
// fall through
case AT_OR_BEFORE:
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset--;
}
break;
while (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset--;
}
break;
case AT:
if (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset = (size_t)-1;
}
break;
}
return offset;
if (!isGraphemeBreak(advances, buf, start, count, offset)) {
offset = (size_t)-1;
}
break;
}
return offset;
}
} // namespace minikin
......@@ -20,26 +20,33 @@
namespace minikin {
class GraphemeBreak {
public:
// These values must be kept in sync with CURSOR_AFTER etc in Paint.java
enum MoveOpt {
AFTER = 0,
AT_OR_AFTER = 1,
BEFORE = 2,
AT_OR_BEFORE = 3,
AT = 4
};
public:
// These values must be kept in sync with CURSOR_AFTER etc in Paint.java
enum MoveOpt {
AFTER = 0,
AT_OR_AFTER = 1,
BEFORE = 2,
AT_OR_BEFORE = 3,
AT = 4
};
// Determine whether the given offset is a grapheme break.
// This implementation generally follows Unicode's UTR #29 extended
// grapheme break, with various tweaks.
static bool isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset);
// Determine whether the given offset is a grapheme break.
// This implementation generally follows Unicode's UTR #29 extended
// grapheme break, with various tweaks.
static bool isGraphemeBreak(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
size_t offset);
// Matches Android's Java API. Note, return (size_t)-1 for AT to
// signal non-break because unsigned return type.
static size_t getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
size_t count, size_t offset, MoveOpt opt);
// Matches Android's Java API. Note, return (size_t)-1 for AT to
// signal non-break because unsigned return type.
static size_t getTextRunCursor(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
size_t offset,
MoveOpt opt);
};
} // namespace minikin
......
......@@ -21,8 +21,8 @@
#include <log/log.h>
#include <utils/LruCache.h>
#include <hb.h>
#include <hb-ot.h>
#include <hb.h>
#include <minikin/MinikinFont.h>
#include "MinikinInternal.h"
......@@ -30,97 +30,89 @@
namespace minikin {
class HbFontCache : private android::OnEntryRemoved<int32_t, hb_font_t*> {
public:
HbFontCache() : mCache(kMaxEntries) {
mCache.setOnEntryRemovedListener(this);
}
public:
HbFontCache() : mCache(kMaxEntries) {
mCache.setOnEntryRemovedListener(this);
}
// callback for OnEntryRemoved
void operator()(int32_t& /* key */, hb_font_t*& value) {
hb_font_destroy(value);
}
// callback for OnEntryRemoved
void operator()(int32_t& /* key */, hb_font_t*& value) {
hb_font_destroy(value);
}
hb_font_t* get(int32_t fontId) {
return mCache.get(fontId);
}
hb_font_t* get(int32_t fontId) { return mCache.get(fontId); }
void put(int32_t fontId, hb_font_t* font) {
mCache.put(fontId, font);
}
void put(int32_t fontId, hb_font_t* font) { mCache.put(fontId, font); }
void clear() {
mCache.clear();
}
void clear() { mCache.clear(); }
void remove(int32_t fontId) {
mCache.remove(fontId);
}
void remove(int32_t fontId) { mCache.remove(fontId); }
private:
static const size_t kMaxEntries = 100;
private:
static const size_t kMaxEntries = 100;
android::LruCache<int32_t, hb_font_t*> mCache;
android::LruCache<int32_t, hb_font_t*> mCache;
};
HbFontCache* getFontCacheLocked() {
assertMinikinLocked();
static HbFontCache* cache = nullptr;
if (cache == nullptr) {
cache = new HbFontCache();
}
return cache;
assertMinikinLocked();
static HbFontCache* cache = nullptr;
if (cache == nullptr) {
cache = new HbFontCache();
}
return cache;
}
void purgeHbFontCacheLocked() {
assertMinikinLocked();
getFontCacheLocked()->clear();
assertMinikinLocked();
getFontCacheLocked()->clear();
}
void purgeHbFontLocked(const MinikinFont* minikinFont) {
assertMinikinLocked();
const int32_t fontId = minikinFont->GetUniqueId();
getFontCacheLocked()->remove(fontId);
assertMinikinLocked();
const int32_t fontId = minikinFont->GetUniqueId();
getFontCacheLocked()->remove(fontId);
}
// Returns a new reference to a hb_font_t object, caller is
// responsible for calling hb_font_destroy() on it.
hb_font_t* getHbFontLocked(const MinikinFont* minikinFont) {
assertMinikinLocked();
// TODO: get rid of nullFaceFont
static hb_font_t* nullFaceFont = nullptr;
if (minikinFont == nullptr) {
if (nullFaceFont == nullptr) {
nullFaceFont = hb_font_create(nullptr);
}
return hb_font_reference(nullFaceFont);
}
HbFontCache* fontCache = getFontCacheLocked();
const int32_t fontId = minikinFont->GetUniqueId();
hb_font_t* font = fontCache->get(fontId);
if (font != nullptr) {
return hb_font_reference(font);
assertMinikinLocked();
// TODO: get rid of nullFaceFont
static hb_font_t* nullFaceFont = nullptr;
if (minikinFont == nullptr) {
if (nullFaceFont == nullptr) {
nullFaceFont = hb_font_create(nullptr);
}
return hb_font_reference(nullFaceFont);
}
hb_face_t* face = minikinFont->CreateHarfBuzzFace();
hb_font_t* parent_font = hb_font_create(face);
hb_ot_font_set_funcs(parent_font);
unsigned int upem = hb_face_get_upem(face);
hb_font_set_scale(parent_font, upem, upem);
font = hb_font_create_sub_font(parent_font);
// TODO(abarth): Enable this code once we update harfbuzz.
// std::vector<hb_variation_t> variations;
// for (const FontVariation& variation : minikinFont->GetAxes()) {
// variations.push_back({variation.axisTag, variation.value});
// }
// hb_font_set_variations(font, variations.data(), variations.size());
hb_font_destroy(parent_font);
hb_face_destroy(face);
fontCache->put(fontId, font);
HbFontCache* fontCache = getFontCacheLocked();
const int32_t fontId = minikinFont->GetUniqueId();
hb_font_t* font = fontCache->get(fontId);
if (font != nullptr) {
return hb_font_reference(font);
}
hb_face_t* face = minikinFont->CreateHarfBuzzFace();
hb_font_t* parent_font = hb_font_create(face);
hb_ot_font_set_funcs(parent_font);
unsigned int upem = hb_face_get_upem(face);
hb_font_set_scale(parent_font, upem, upem);
font = hb_font_create_sub_font(parent_font);
// TODO(abarth): Enable this code once we update harfbuzz.
// std::vector<hb_variation_t> variations;
// for (const FontVariation& variation : minikinFont->GetAxes()) {
// variations.push_back({variation.axisTag, variation.value});
// }
// hb_font_set_variations(font, variations.data(), variations.size());
hb_font_destroy(parent_font);
hb_face_destroy(face);
fontCache->put(fontId, font);
return hb_font_reference(font);
}
} // namespace minikin
......@@ -23,7 +23,8 @@
namespace minikin {
/*
* Determine whether the code unit is a word space for the purposes of justification.
* Determine whether the code unit is a word space for the purposes of
* justification.
*/
bool isWordSpace(uint16_t code_unit);
......@@ -34,8 +35,9 @@ bool isWordSpace(uint16_t code_unit);
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
size_t getPrevWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len);
size_t getPrevWordBreakForCache(const uint16_t* chars,
size_t offset,
size_t len);
/**
* Return offset of next word break. It is either > offset or == len.
......@@ -44,8 +46,9 @@ size_t getPrevWordBreakForCache(
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
size_t getNextWordBreakForCache(
const uint16_t* chars, size_t offset, size_t len);
size_t getNextWordBreakForCache(const uint16_t* chars,
size_t offset,
size_t len);
} // namespace minikin
#endif // MINIKIN_LAYOUT_UTILS_H
......@@ -21,11 +21,17 @@
namespace minikin {
float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
size_t offset);
float getRunAdvance(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
size_t offset);
size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
float advance);
size_t getOffsetForAdvance(const float* advances,
const uint16_t* buf,
size_t start,
size_t count,
float advance);
} // namespace minikin
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册