Seigo Nonaka 99ef32cef0 Do not keep FontCollection reference in Layout.
LayoutCache only keeps result of layout and can live after
FontCollection is destructed by GC.

This kind of failure will be captured by minikin_stress_tests in the
subsequent CL (I1bf4ba43e6e97cd04e7d6dd42d388dd17ce64c7b)

Test: ran minikin_tests
Bug: 36223724
Change-Id: I639b73c0f1041549158c43212a901c82df4b02db
2017-03-15 15:17:43 +00:00

1168 lines
43 KiB
C++

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "Minikin"
#include <algorithm>
#include <fstream>
#include <iostream> // for debugging
#include <math.h>
#include <string>
#include <unicode/ubidi.h>
#include <unicode/utf16.h>
#include <vector>
#include <log/log.h>
#include <utils/JenkinsHash.h>
#include <utils/LruCache.h>
#include <utils/Singleton.h>
#include <utils/String16.h>
#include <hb-icu.h>
#include <hb-ot.h>
#include "FontLanguage.h"
#include "FontLanguageListCache.h"
#include "HbFontCache.h"
#include "LayoutUtils.h"
#include "MinikinInternal.h"
#include <minikin/MinikinFontFreeType.h>
#include <minikin/Layout.h>
using std::string;
using std::vector;
namespace minikin {
Bitmap::Bitmap(int width, int height) : width(width), height(height) {
buf = new uint8_t[width * height]();
}
Bitmap::~Bitmap() {
delete[] buf;
}
void Bitmap::writePnm(std::ofstream &o) const {
o << "P5" << std::endl;
o << width << " " << height << std::endl;
o << "255" << std::endl;
o.write((const char *)buf, width * height);
o.close();
}
void Bitmap::drawGlyph(const GlyphBitmap& bitmap, int x, int y) {
int bmw = bitmap.width;
int bmh = bitmap.height;
x += bitmap.left;
y -= bitmap.top;
int x0 = std::max(0, x);
int x1 = std::min(width, x + bmw);
int y0 = std::max(0, y);
int y1 = std::min(height, y + bmh);
const unsigned char* src = bitmap.buffer + (y0 - y) * bmw + (x0 - x);
uint8_t* dst = buf + y0 * width;
for (int yy = y0; yy < y1; yy++) {
for (int xx = x0; xx < x1; xx++) {
int pixel = (int)dst[xx] + (int)src[xx - x];
pixel = pixel > 0xff ? 0xff : pixel;
dst[xx] = pixel;
}
src += bmw;
dst += width;
}
}
const int kDirection_Mask = 0x1;
struct LayoutContext {
MinikinPaint paint;
FontStyle style;
std::vector<hb_font_t*> hbFonts; // parallel to mFaces
void clearHbFonts() {
for (size_t i = 0; i < hbFonts.size(); i++) {
hb_font_set_funcs(hbFonts[i], nullptr, nullptr, nullptr);
hb_font_destroy(hbFonts[i]);
}
hbFonts.clear();
}
};
// Layout cache datatypes
class LayoutCacheKey {
public:
LayoutCacheKey(const std::shared_ptr<FontCollection>& collection, const MinikinPaint& paint,
FontStyle style, const uint16_t* chars, size_t start, size_t count, size_t nchars,
bool dir)
: mChars(chars), mNchars(nchars),
mStart(start), mCount(count), mId(collection->getId()), mStyle(style),
mSize(paint.size), mScaleX(paint.scaleX), mSkewX(paint.skewX),
mLetterSpacing(paint.letterSpacing),
mPaintFlags(paint.paintFlags), mHyphenEdit(paint.hyphenEdit), mIsRtl(dir),
mHash(computeHash()) {
}
bool operator==(const LayoutCacheKey &other) const;
android::hash_t hash() const {
return mHash;
}
void copyText() {
uint16_t* charsCopy = new uint16_t[mNchars];
memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
mChars = charsCopy;
}
void freeText() {
delete[] mChars;
mChars = NULL;
}
void doLayout(Layout* layout, LayoutContext* ctx,
const std::shared_ptr<FontCollection>& collection) const {
layout->mAdvances.resize(mCount, 0);
ctx->clearHbFonts();
layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx, collection);
}
private:
const uint16_t* mChars;
size_t mNchars;
size_t mStart;
size_t mCount;
uint32_t mId; // for the font collection
FontStyle mStyle;
float mSize;
float mScaleX;
float mSkewX;
float mLetterSpacing;
int32_t mPaintFlags;
HyphenEdit mHyphenEdit;
bool mIsRtl;
// Note: any fields added to MinikinPaint must also be reflected here.
// TODO: language matching (possibly integrate into style)
android::hash_t mHash;
android::hash_t computeHash() const;
};
class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
public:
LayoutCache() : mCache(kMaxEntries) {
mCache.setOnEntryRemovedListener(this);
}
void clear() {
mCache.clear();
}
Layout* get(LayoutCacheKey& key, LayoutContext* ctx,
const std::shared_ptr<FontCollection>& collection) {
Layout* layout = mCache.get(key);
if (layout == NULL) {
key.copyText();
layout = new Layout();
key.doLayout(layout, ctx, collection);
mCache.put(key, layout);
}
return layout;
}
private:
// callback for OnEntryRemoved
void operator()(LayoutCacheKey& key, Layout*& value) {
key.freeText();
delete value;
}
android::LruCache<LayoutCacheKey, Layout*> mCache;
//static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
// TODO: eviction based on memory footprint; for now, we just use a constant
// number of strings
static const size_t kMaxEntries = 5000;
};
static unsigned int disabledDecomposeCompatibility(hb_unicode_funcs_t*, hb_codepoint_t,
hb_codepoint_t*, void*) {
return 0;
}
class LayoutEngine : public ::android::Singleton<LayoutEngine> {
public:
LayoutEngine() {
unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
/* Disable the function used for compatibility decomposition */
hb_unicode_funcs_set_decompose_compatibility_func(
unicodeFunctions, disabledDecomposeCompatibility, NULL, NULL);
hbBuffer = hb_buffer_create();
hb_buffer_set_unicode_funcs(hbBuffer, unicodeFunctions);
}
hb_buffer_t* hbBuffer;
hb_unicode_funcs_t* unicodeFunctions;
LayoutCache layoutCache;
};
bool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {
return mId == other.mId
&& mStart == other.mStart
&& mCount == other.mCount
&& mStyle == other.mStyle
&& mSize == other.mSize
&& mScaleX == other.mScaleX
&& mSkewX == other.mSkewX
&& mLetterSpacing == other.mLetterSpacing
&& mPaintFlags == other.mPaintFlags
&& mHyphenEdit == other.mHyphenEdit
&& mIsRtl == other.mIsRtl
&& mNchars == other.mNchars
&& !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));
}
android::hash_t LayoutCacheKey::computeHash() const {
uint32_t hash = android::JenkinsHashMix(0, mId);
hash = android::JenkinsHashMix(hash, mStart);
hash = android::JenkinsHashMix(hash, mCount);
hash = android::JenkinsHashMix(hash, hash_type(mStyle));
hash = android::JenkinsHashMix(hash, hash_type(mSize));
hash = android::JenkinsHashMix(hash, hash_type(mScaleX));
hash = android::JenkinsHashMix(hash, hash_type(mSkewX));
hash = android::JenkinsHashMix(hash, hash_type(mLetterSpacing));
hash = android::JenkinsHashMix(hash, hash_type(mPaintFlags));
hash = android::JenkinsHashMix(hash, hash_type(mHyphenEdit.getHyphen()));
hash = android::JenkinsHashMix(hash, hash_type(mIsRtl));
hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
return android::JenkinsHashWhiten(hash);
}
android::hash_t hash_type(const LayoutCacheKey& key) {
return key.hash();
}
void MinikinRect::join(const MinikinRect& r) {
if (isEmpty()) {
set(r);
} else if (!r.isEmpty()) {
mLeft = std::min(mLeft, r.mLeft);
mTop = std::min(mTop, r.mTop);
mRight = std::max(mRight, r.mRight);
mBottom = std::max(mBottom, r.mBottom);
}
}
void Layout::reset() {
mGlyphs.clear();
mFaces.clear();
mBounds.setEmpty();
mAdvances.clear();
mAdvance = 0;
}
static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
hb_codepoint_t glyph, void* /* userData */) {
MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
float advance = paint->font->GetHorizontalAdvance(glyph, *paint);
return 256 * advance + 0.5;
}
static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
hb_codepoint_t /* glyph */, hb_position_t* /* x */, hb_position_t* /* y */,
void* /* userData */) {
// Just return true, following the way that Harfbuzz-FreeType
// implementation does.
return true;
}
hb_font_funcs_t* getHbFontFuncs(bool forColorBitmapFont) {
assertMinikinLocked();
static hb_font_funcs_t* hbFuncs = nullptr;
static hb_font_funcs_t* hbFuncsForColorBitmap = nullptr;
hb_font_funcs_t** funcs = forColorBitmapFont ? &hbFuncs : &hbFuncsForColorBitmap;
if (*funcs == nullptr) {
*funcs = hb_font_funcs_create();
if (forColorBitmapFont) {
// Don't override the h_advance function since we use HarfBuzz's implementation for
// emoji for performance reasons.
// Note that it is technically possible for a TrueType font to have outline and embedded
// bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
// case.
} else {
// Override the h_advance function since we can't use HarfBuzz's implemenation. It may
// return the wrong value if the font uses hinting aggressively.
hb_font_funcs_set_glyph_h_advance_func(*funcs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
}
hb_font_funcs_set_glyph_h_origin_func(*funcs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
hb_font_funcs_make_immutable(*funcs);
}
return *funcs;
}
static bool isColorBitmapFont(hb_font_t* font) {
hb_face_t* face = hb_font_get_face(font);
HbBlob cbdt(hb_face_reference_table(face, HB_TAG('C', 'B', 'D', 'T')));
return cbdt.size() > 0;
}
static float HBFixedToFloat(hb_position_t v)
{
return scalbnf (v, -8);
}
static hb_position_t HBFloatToFixed(float v)
{
return scalbnf (v, +8);
}
void Layout::dump() const {
for (size_t i = 0; i < mGlyphs.size(); i++) {
const LayoutGlyph& glyph = mGlyphs[i];
std::cout << glyph.glyph_id << ": " << glyph.x << ", " << glyph.y << std::endl;
}
}
int Layout::findFace(const FakedFont& face, LayoutContext* ctx) {
unsigned int ix;
for (ix = 0; ix < mFaces.size(); ix++) {
if (mFaces[ix].font == face.font) {
return ix;
}
}
mFaces.push_back(face);
// Note: ctx == NULL means we're copying from the cache, no need to create
// corresponding hb_font object.
if (ctx != NULL) {
hb_font_t* font = getHbFontLocked(face.font);
hb_font_set_funcs(font, getHbFontFuncs(isColorBitmapFont(font)), &ctx->paint, 0);
ctx->hbFonts.push_back(font);
}
return ix;
}
static hb_script_t codePointToScript(hb_codepoint_t codepoint) {
static hb_unicode_funcs_t* u = 0;
if (!u) {
u = LayoutEngine::getInstance().unicodeFunctions;
}
return hb_unicode_script(u, codepoint);
}
static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
const uint16_t v = chars[(*iter)++];
// test whether v in (0xd800..0xdfff), lead or trail surrogate
if ((v & 0xf800) == 0xd800) {
// test whether v in (0xd800..0xdbff), lead surrogate
if (size_t(*iter) < len && (v & 0xfc00) == 0xd800) {
const uint16_t v2 = chars[(*iter)++];
// test whether v2 in (0xdc00..0xdfff), trail surrogate
if ((v2 & 0xfc00) == 0xdc00) {
// (0xd800 0xdc00) in utf-16 maps to 0x10000 in ucs-32
const hb_codepoint_t delta = (0xd800 << 10) + 0xdc00 - 0x10000;
return (((hb_codepoint_t)v) << 10) + v2 - delta;
}
(*iter) -= 1;
return 0xFFFDu;
} else {
return 0xFFFDu;
}
} else {
return v;
}
}
static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
if (size_t(*iter) == len) {
return HB_SCRIPT_UNKNOWN;
}
uint32_t cp = decodeUtf16(chars, len, iter);
hb_script_t current_script = codePointToScript(cp);
for (;;) {
if (size_t(*iter) == len)
break;
const ssize_t prev_iter = *iter;
cp = decodeUtf16(chars, len, iter);
const hb_script_t script = codePointToScript(cp);
if (script != current_script) {
if (current_script == HB_SCRIPT_INHERITED ||
current_script == HB_SCRIPT_COMMON) {
current_script = script;
} else if (script == HB_SCRIPT_INHERITED ||
script == HB_SCRIPT_COMMON) {
continue;
} else {
*iter = prev_iter;
break;
}
}
}
if (current_script == HB_SCRIPT_INHERITED) {
current_script = HB_SCRIPT_COMMON;
}
return current_script;
}
/**
* Disable certain scripts (mostly those with cursive connection) from having letterspacing
* applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
*/
static bool isScriptOkForLetterspacing(hb_script_t script) {
return !(
script == HB_SCRIPT_ARABIC ||
script == HB_SCRIPT_NKO ||
script == HB_SCRIPT_PSALTER_PAHLAVI ||
script == HB_SCRIPT_MANDAIC ||
script == HB_SCRIPT_MONGOLIAN ||
script == HB_SCRIPT_PHAGS_PA ||
script == HB_SCRIPT_DEVANAGARI ||
script == HB_SCRIPT_BENGALI ||
script == HB_SCRIPT_GURMUKHI ||
script == HB_SCRIPT_MODI ||
script == HB_SCRIPT_SHARADA ||
script == HB_SCRIPT_SYLOTI_NAGRI ||
script == HB_SCRIPT_TIRHUTA ||
script == HB_SCRIPT_OGHAM
);
}
class BidiText {
public:
class Iter {
public:
struct RunInfo {
int32_t mRunStart;
int32_t mRunLength;
bool mIsRtl;
};
Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, bool isRtl);
bool operator!= (const Iter& other) const {
return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex
|| mBidi != other.mBidi;
}
const RunInfo& operator* () const {
return mRunInfo;
}
const Iter& operator++ () {
updateRunInfo();
return *this;
}
private:
UBiDi* const mBidi;
bool mIsEnd;
size_t mNextRunIndex;
const size_t mRunCount;
const int32_t mStart;
const int32_t mEnd;
RunInfo mRunInfo;
void updateRunInfo();
};
BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags);
~BidiText() {
if (mBidi) {
ubidi_close(mBidi);
}
}
Iter begin () const {
return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl);
}
Iter end() const {
return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl);
}
private:
const size_t mStart;
const size_t mEnd;
const size_t mBufSize;
UBiDi* mBidi;
size_t mRunCount;
bool mIsRtl;
BidiText(const BidiText&) = delete;
void operator=(const BidiText&) = delete;
};
BidiText::Iter::Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount,
bool isRtl)
: mBidi(bidi), mIsEnd(runIndex == runCount), mNextRunIndex(runIndex), mRunCount(runCount),
mStart(start), mEnd(end), mRunInfo() {
if (mRunCount == 1) {
mRunInfo.mRunStart = start;
mRunInfo.mRunLength = end - start;
mRunInfo.mIsRtl = isRtl;
mNextRunIndex = mRunCount;
return;
}
updateRunInfo();
}
void BidiText::Iter::updateRunInfo() {
if (mNextRunIndex == mRunCount) {
// All runs have been iterated.
mIsEnd = true;
return;
}
int32_t startRun = -1;
int32_t lengthRun = -1;
const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun);
mNextRunIndex++;
if (startRun == -1 || lengthRun == -1) {
ALOGE("invalid visual run");
// skip the invalid run.
updateRunInfo();
return;
}
const int32_t runEnd = std::min(startRun + lengthRun, mEnd);
mRunInfo.mRunStart = std::max(startRun, mStart);
mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart;
if (mRunInfo.mRunLength <= 0) {
// skip the empty run.
updateRunInfo();
return;
}
mRunInfo.mIsRtl = (runDir == UBIDI_RTL);
}
BidiText::BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags)
: mStart(start), mEnd(start + count), mBufSize(bufSize), mBidi(NULL), mRunCount(1),
mIsRtl((bidiFlags & kDirection_Mask) != 0) {
if (bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL) {
// force single run.
return;
}
mBidi = ubidi_open();
if (!mBidi) {
ALOGE("error creating bidi object");
return;
}
UErrorCode status = U_ZERO_ERROR;
UBiDiLevel bidiReq = bidiFlags;
if (bidiFlags == kBidi_Default_LTR) {
bidiReq = UBIDI_DEFAULT_LTR;
} else if (bidiFlags == kBidi_Default_RTL) {
bidiReq = UBIDI_DEFAULT_RTL;
}
ubidi_setPara(mBidi, buf, mBufSize, bidiReq, NULL, &status);
if (!U_SUCCESS(status)) {
ALOGE("error calling ubidi_setPara, status = %d", status);
return;
}
const int paraDir = ubidi_getParaLevel(mBidi) & kDirection_Mask;
const ssize_t rc = ubidi_countRuns(mBidi, &status);
if (!U_SUCCESS(status) || rc < 0) {
ALOGW("error counting bidi runs, status = %d", status);
}
if (!U_SUCCESS(status) || rc <= 1) {
mIsRtl = (paraDir == kBidi_RTL);
return;
}
mRunCount = rc;
}
void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
const std::shared_ptr<FontCollection>& collection) {
android::AutoMutex _l(gMinikinLock);
LayoutContext ctx;
ctx.style = style;
ctx.paint = paint;
reset();
mAdvances.resize(count, 0);
for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize, runInfo.mIsRtl, &ctx,
start, collection, this, NULL);
}
ctx.clearHbFonts();
}
float Layout::measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
const std::shared_ptr<FontCollection>& collection, float* advances) {
android::AutoMutex _l(gMinikinLock);
LayoutContext ctx;
ctx.style = style;
ctx.paint = paint;
float advance = 0;
for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
float* advancesForRun = advances ? advances + (runInfo.mRunStart - start) : advances;
advance += doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize,
runInfo.mIsRtl, &ctx, 0, collection, NULL, advancesForRun);
}
ctx.clearHbFonts();
return advance;
}
float Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, LayoutContext* ctx, size_t dstStart,
const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
const uint32_t originalHyphen = ctx->paint.hyphenEdit.getHyphen();
float advance = 0;
if (!isRtl) {
// left to right
size_t wordstart =
start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
size_t wordend;
for (size_t iter = start; iter < start + count; iter = wordend) {
wordend = getNextWordBreakForCache(buf, iter, bufSize);
// Only apply hyphen to the first or last word in the string.
uint32_t hyphen = originalHyphen;
if (iter != start) { // Not the first word
hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
}
if (wordend < start + count) { // Not the last word
hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
}
ctx->paint.hyphenEdit = hyphen;
size_t wordcount = std::min(start + count, wordend) - iter;
advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
wordend - wordstart, isRtl, ctx, iter - dstStart, collection, layout,
advances ? advances + (iter - start) : advances);
wordstart = wordend;
}
} else {
// right to left
size_t wordstart;
size_t end = start + count;
size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
for (size_t iter = end; iter > start; iter = wordstart) {
wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
// Only apply hyphen to the first (rightmost) or last (leftmost) word in the string.
uint32_t hyphen = originalHyphen;
if (wordstart > start) { // Not the first word
hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
}
if (iter != end) { // Not the last word
hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
}
ctx->paint.hyphenEdit = hyphen;
size_t bufStart = std::max(start, wordstart);
advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection, layout,
advances ? advances + (bufStart - start) : advances);
wordend = wordstart;
}
}
return advance;
}
float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, LayoutContext* ctx, size_t bufStart,
const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
LayoutCache& cache = LayoutEngine::getInstance().layoutCache;
LayoutCacheKey key(collection, ctx->paint, ctx->style, buf, start, count, bufSize, isRtl);
float wordSpacing = count == 1 && isWordSpace(buf[start]) ? ctx->paint.wordSpacing : 0;
float advance;
if (ctx->paint.skipCache()) {
Layout layoutForWord;
key.doLayout(&layoutForWord, ctx, collection);
if (layout) {
layout->appendLayout(&layoutForWord, bufStart, wordSpacing);
}
if (advances) {
layoutForWord.getAdvances(advances);
}
advance = layoutForWord.getAdvance();
} else {
Layout* layoutForWord = cache.get(key, ctx, collection);
if (layout) {
layout->appendLayout(layoutForWord, bufStart, wordSpacing);
}
if (advances) {
layoutForWord->getAdvances(advances);
}
advance = layoutForWord->getAdvance();
}
if (wordSpacing != 0) {
advance += wordSpacing;
if (advances) {
advances[0] += wordSpacing;
}
}
return advance;
}
static void addFeatures(const string &str, vector<hb_feature_t>* features) {
if (!str.size())
return;
const char* start = str.c_str();
const char* end = start + str.size();
while (start < end) {
static hb_feature_t feature;
const char* p = strchr(start, ',');
if (!p)
p = end;
/* We do not allow setting features on ranges. As such, reject any
* setting that has non-universal range. */
if (hb_feature_from_string (start, p - start, &feature)
&& feature.start == 0 && feature.end == (unsigned int) -1)
features->push_back(feature);
start = p + 1;
}
}
static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
if (preferredHyphen == 0x2010 /* HYPHEN */
|| preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */
|| preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
|| preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
hb_codepoint_t glyph;
// Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
// Note that we intentionally don't do anything special if the font doesn't have a
// HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
return 0x002D; // HYPHEN-MINUS
}
}
return preferredHyphen;
}
static inline void addHyphenToHbBuffer(hb_buffer_t* buffer, hb_font_t* font, uint32_t hyphen,
uint32_t cluster) {
const uint32_t* hyphenStr = HyphenEdit::getHyphenString(hyphen);
while (*hyphenStr != 0) {
hb_codepoint_t hyphenChar = determineHyphenChar(*hyphenStr, font);
hb_buffer_add(buffer, hyphenChar, cluster);
hyphenStr++;
}
}
// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
// to translate cluster values returned by HarfBuzz to input indices.
static inline uint32_t addToHbBuffer(hb_buffer_t* buffer,
const uint16_t* buf, size_t start, size_t count, size_t bufSize,
ssize_t scriptRunStart, ssize_t scriptRunEnd,
HyphenEdit hyphenEdit, hb_font_t* hbFont) {
// Only hyphenate the very first script run for starting hyphens.
const uint32_t startHyphen = (scriptRunStart == 0)
? hyphenEdit.getStart()
: HyphenEdit::NO_EDIT;
// Only hyphenate the very last script run for ending hyphens.
const uint32_t endHyphen = (static_cast<size_t>(scriptRunEnd) == count)
? hyphenEdit.getEnd()
: HyphenEdit::NO_EDIT;
// In the following code, we drop the pre-context and/or post-context if there is a
// hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
// contexts only for joining scripts at the moment, e.g. to determine if the first or
// last letter of a text range to shape should take a joining form based on an
// adjacent letter or joiner (that comes from the context).
//
// TODO: Revisit this for:
// 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
// the context);
// 2. Special features like start-of-word font features (not implemented in HarfBuzz
// yet).
// We don't have any start-of-line replacement edit yet, so we don't need to check for
// those.
if (HyphenEdit::isInsertion(startHyphen)) {
// A cluster value of zero guarantees that the inserted hyphen will be in the same
// cluster with the next codepoint, since there is no pre-context.
addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster value */);
}
const uint16_t* hbText;
int hbTextLength;
unsigned int hbItemOffset;
unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
const bool hasEndInsertion = HyphenEdit::isInsertion(endHyphen);
const bool hasEndReplacement = HyphenEdit::isReplacement(endHyphen);
if (hasEndReplacement) {
// Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
// don't need to worry about non-BMP characters yet since replacements are only done for
// code units at the moment.
hbItemLength -= 1;
}
if (startHyphen == HyphenEdit::NO_EDIT) {
// No edit at the beginning. Use the whole pre-context.
hbText = buf;
hbItemOffset = start + scriptRunStart;
} else {
// There's an edit at the beginning. Drop the pre-context and start the buffer at where we
// want to start shaping.
hbText = buf + start + scriptRunStart;
hbItemOffset = 0;
}
if (endHyphen == HyphenEdit::NO_EDIT) {
// No edit at the end, use the whole post-context.
hbTextLength = (buf + bufSize) - hbText;
} else {
// There is an edit at the end. Drop the post-context.
hbTextLength = hbItemOffset + hbItemLength;
}
hb_buffer_add_utf16(buffer, hbText, hbTextLength, hbItemOffset, hbItemLength);
unsigned int numCodepoints;
hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer, &numCodepoints);
// Add the hyphen at the end, if there's any.
if (hasEndInsertion || hasEndReplacement) {
// When a hyphen is inserted, by assigning the added hyphen and the last
// codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
// that they always remain in the same cluster, even if the last codepoint gets
// merged into another cluster (for example when it's a combining mark).
//
// When a replacement happens instead, we want it to get the cluster value of
// the character it's replacing, which is one "codepoint length" larger than
// the last cluster. But since the character replaced is always just one
// code unit, we can just add 1.
uint32_t hyphenCluster;
if (numCodepoints == 0) {
// Nothing was added to the HarfBuzz buffer. This can only happen if
// we have a replacement that is replacing a one-code unit script run.
hyphenCluster = 0;
} else {
hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t) hasEndReplacement;
}
addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
// Since we have just added to the buffer, cpInfo no longer necessarily points to
// the right place. Refresh it.
cpInfo = hb_buffer_get_glyph_infos(buffer, nullptr /* we don't need the size */);
}
return cpInfo[0].cluster;
}
void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection) {
hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;
vector<FontCollection::Run> items;
collection->itemize(buf + start, count, ctx->style, &items);
vector<hb_feature_t> features;
// Disable default-on non-required ligature features if letter-spacing
// See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
// "When the effective spacing between two characters is not zero (due to
// either justification or a non-zero value of letter-spacing), user agents
// should not apply optional ligatures."
if (fabs(ctx->paint.letterSpacing) > 0.03)
{
static const hb_feature_t no_liga = { HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u };
static const hb_feature_t no_clig = { HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u };
features.push_back(no_liga);
features.push_back(no_clig);
}
addFeatures(ctx->paint.fontFeatureSettings, &features);
double size = ctx->paint.size;
double scaleX = ctx->paint.scaleX;
float x = mAdvance;
float y = 0;
for (int run_ix = isRtl ? items.size() - 1 : 0;
isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
isRtl ? --run_ix : ++run_ix) {
FontCollection::Run &run = items[run_ix];
if (run.fakedFont.font == NULL) {
ALOGE("no font for run starting u+%04x length %d", buf[run.start], run.end - run.start);
continue;
}
int font_ix = findFace(run.fakedFont, ctx);
ctx->paint.font = mFaces[font_ix].font;
ctx->paint.fakery = mFaces[font_ix].fakery;
hb_font_t* hbFont = ctx->hbFonts[font_ix];
#ifdef VERBOSE_DEBUG
ALOGD("Run %zu, font %d [%d:%d]", run_ix, font_ix, run.start, run.end);
#endif
hb_font_set_ppem(hbFont, size * scaleX, size);
hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
// TODO: if there are multiple scripts within a font in an RTL run,
// we need to reorder those runs. This is unlikely with our current
// font stack, but should be done for correctness.
// Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
// and count.
ssize_t scriptRunEnd;
for (ssize_t scriptRunStart = run.start;
scriptRunStart < run.end;
scriptRunStart = scriptRunEnd) {
scriptRunEnd = scriptRunStart;
hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
// After the last line, scriptRunEnd is guaranteed to have increased, since the only
// time getScriptRun does not increase its iterator is when it has already reached the
// end of the buffer. But that can't happen, since if we have already reached the end
// of the buffer, we should have had (scriptRunEnd == run.end), which means
// (scriptRunStart == run.end) which is impossible due to the exit condition of the for
// loop. So we can be sure that scriptRunEnd > scriptRunStart.
double letterSpace = 0.0;
double letterSpaceHalfLeft = 0.0;
double letterSpaceHalfRight = 0.0;
if (ctx->paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
letterSpace = ctx->paint.letterSpacing * size * scaleX;
if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
letterSpace = round(letterSpace);
letterSpaceHalfLeft = floor(letterSpace * 0.5);
} else {
letterSpaceHalfLeft = letterSpace * 0.5;
}
letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
}
hb_buffer_clear_contents(buffer);
hb_buffer_set_script(buffer, script);
hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
const FontLanguages& langList =
FontLanguageListCache::getById(ctx->style.getLanguageListId());
if (langList.size() != 0) {
const FontLanguage* hbLanguage = &langList[0];
for (size_t i = 0; i < langList.size(); ++i) {
if (langList[i].supportsHbScript(script)) {
hbLanguage = &langList[i];
break;
}
}
hb_buffer_set_language(buffer, hbLanguage->getHbLanguage());
}
const uint32_t clusterStart = addToHbBuffer(
buffer,
buf, start, count, bufSize,
scriptRunStart, scriptRunEnd,
ctx->paint.hyphenEdit, hbFont);
hb_shape(hbFont, buffer, features.empty() ? NULL : &features[0], features.size());
unsigned int numGlyphs;
hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
// At this point in the code, the cluster values in the info buffer correspond to the
// input characters with some shift. The cluster value clusterStart corresponds to the
// first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
// advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
// be reduced by (clusterStart - scriptRunStart) to get converted to indices of
// mAdvances.
const ssize_t clusterOffset = clusterStart - scriptRunStart;
if (numGlyphs)
{
mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
x += letterSpaceHalfLeft;
}
for (unsigned int i = 0; i < numGlyphs; i++) {
#ifdef VERBOSE_DEBUG
ALOGD("%d %d %d %d",
positions[i].x_advance, positions[i].y_advance,
positions[i].x_offset, positions[i].y_offset);
ALOGD("DoLayout %u: %f; %d, %d",
info[i].codepoint, HBFixedToFloat(positions[i].x_advance),
positions[i].x_offset, positions[i].y_offset);
#endif
if (i > 0 && info[i - 1].cluster != info[i].cluster) {
mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
mAdvances[info[i].cluster - clusterOffset] += letterSpaceHalfLeft;
x += letterSpace;
}
hb_codepoint_t glyph_ix = info[i].codepoint;
float xoff = HBFixedToFloat(positions[i].x_offset);
float yoff = -HBFixedToFloat(positions[i].y_offset);
xoff += yoff * ctx->paint.skewX;
LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
mGlyphs.push_back(glyph);
float xAdvance = HBFixedToFloat(positions[i].x_advance);
if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
xAdvance = roundf(xAdvance);
}
MinikinRect glyphBounds;
hb_glyph_extents_t extents = {};
if (is_color_bitmap_font && hb_font_get_glyph_extents(hbFont, glyph_ix, &extents)) {
// Note that it is technically possible for a TrueType font to have outline and
// embedded bitmap at the same time. We ignore modified bbox of hinted outline
// glyphs in that case.
glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
glyphBounds.mBottom =
roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
} else {
ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
}
glyphBounds.offset(x + xoff, y + yoff);
mBounds.join(glyphBounds);
if (static_cast<size_t>(info[i].cluster - clusterOffset) < count) {
mAdvances[info[i].cluster - clusterOffset] += xAdvance;
} else {
ALOGE("cluster %zu (start %zu) out of bounds of count %zu",
info[i].cluster - clusterOffset, start, count);
}
x += xAdvance;
}
if (numGlyphs)
{
mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
x += letterSpaceHalfRight;
}
}
}
mAdvance = x;
}
void Layout::appendLayout(Layout* src, size_t start, float extraAdvance) {
int fontMapStack[16];
int* fontMap;
if (src->mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
fontMap = fontMapStack;
} else {
fontMap = new int[src->mFaces.size()];
}
for (size_t i = 0; i < src->mFaces.size(); i++) {
int font_ix = findFace(src->mFaces[i], NULL);
fontMap[i] = font_ix;
}
int x0 = mAdvance;
for (size_t i = 0; i < src->mGlyphs.size(); i++) {
LayoutGlyph& srcGlyph = src->mGlyphs[i];
int font_ix = fontMap[srcGlyph.font_ix];
unsigned int glyph_id = srcGlyph.glyph_id;
float x = x0 + srcGlyph.x;
float y = srcGlyph.y;
LayoutGlyph glyph = {font_ix, glyph_id, x, y};
mGlyphs.push_back(glyph);
}
for (size_t i = 0; i < src->mAdvances.size(); i++) {
mAdvances[i + start] = src->mAdvances[i];
if (i == 0)
mAdvances[i + start] += extraAdvance;
}
MinikinRect srcBounds(src->mBounds);
srcBounds.offset(x0, 0);
mBounds.join(srcBounds);
mAdvance += src->mAdvance + extraAdvance;
if (fontMap != fontMapStack) {
delete[] fontMap;
}
}
void Layout::draw(minikin::Bitmap* surface, int x0, int y0, float size) const {
/*
TODO: redo as MinikinPaint settings
if (mProps.hasTag(minikinHinting)) {
int hintflags = mProps.value(minikinHinting).getIntValue();
if (hintflags & 1) load_flags |= FT_LOAD_NO_HINTING;
if (hintflags & 2) load_flags |= FT_LOAD_NO_AUTOHINT;
}
*/
for (size_t i = 0; i < mGlyphs.size(); i++) {
const LayoutGlyph& glyph = mGlyphs[i];
MinikinFont* mf = mFaces[glyph.font_ix].font;
MinikinFontFreeType* face = static_cast<MinikinFontFreeType*>(mf);
GlyphBitmap glyphBitmap;
MinikinPaint paint;
paint.size = size;
bool ok = face->Render(glyph.glyph_id, paint, &glyphBitmap);
#ifdef VERBOSE_DEBUG
ALOGD("glyphBitmap.width=%d, glyphBitmap.height=%d (%d, %d) x=%f, y=%f, ok=%d",
glyphBitmap.width, glyphBitmap.height, glyphBitmap.left, glyphBitmap.top, glyph.x, glyph.y, ok);
#endif
if (ok) {
surface->drawGlyph(glyphBitmap,
x0 + int(floor(glyph.x + 0.5)), y0 + int(floor(glyph.y + 0.5)));
}
}
}
size_t Layout::nGlyphs() const {
return mGlyphs.size();
}
const MinikinFont* Layout::getFont(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return mFaces[glyph.font_ix].font;
}
FontFakery Layout::getFakery(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return mFaces[glyph.font_ix].fakery;
}
unsigned int Layout::getGlyphId(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return glyph.glyph_id;
}
float Layout::getX(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return glyph.x;
}
float Layout::getY(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return glyph.y;
}
float Layout::getAdvance() const {
return mAdvance;
}
void Layout::getAdvances(float* advances) {
memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
}
void Layout::getBounds(MinikinRect* bounds) const {
bounds->set(mBounds);
}
void Layout::purgeCaches() {
android::AutoMutex _l(gMinikinLock);
LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
layoutCache.clear();
purgeHbFontCacheLocked();
}
} // namespace minikin
// Unable to define the static data member outside of android.
// TODO: introduce our own Singleton to drop android namespace.
namespace android {
ANDROID_SINGLETON_STATIC_INSTANCE(minikin::LayoutEngine);
} // namespace android