summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/minikin/FontCollection.h11
-rw-r--r--include/minikin/FontFamily.h14
-rw-r--r--include/minikin/Layout.h43
-rw-r--r--include/minikin/MinikinFont.h1
-rw-r--r--libs/minikin/Android.mk4
-rw-r--r--libs/minikin/FontCollection.cpp13
-rw-r--r--libs/minikin/Layout.cpp452
-rw-r--r--sample/example.cpp2
8 files changed, 461 insertions, 79 deletions
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index dc48f8e..c6c2ed0 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -54,9 +54,12 @@ public:
int start;
int end;
};
+
void itemize(const uint16_t *string, size_t string_length, FontStyle style,
std::vector<Run>* result) const;
- private:
+
+ uint32_t getId() const;
+private:
static const int kLogCharsPerPage = 8;
static const int kPageMask = (1 << kLogCharsPerPage) - 1;
@@ -70,6 +73,12 @@ public:
size_t end;
};
+ // 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;
diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h
index 82fcfe9..a4fe9cb 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -19,6 +19,8 @@
#include <vector>
+#include <utils/TypeHelpers.h>
+
#include <minikin/MinikinRefCounted.h>
namespace android {
@@ -31,16 +33,22 @@ public:
FontStyle(int weight = 4, bool italic = false) {
bits = (weight & kWeightMask) | (italic ? kItalicMask : 0);
}
- int getWeight() { return bits & kWeightMask; }
- bool getItalic() { return (bits & kItalicMask) != 0; }
- bool operator==(const FontStyle other) { return bits == other.bits; }
+ int getWeight() const { return bits & kWeightMask; }
+ bool getItalic() const { return (bits & kItalicMask) != 0; }
+ bool operator==(const FontStyle other) const { return bits == other.bits; }
// TODO: language, variant
+
+ hash_t hash() const { return bits; }
private:
static const int kWeightMask = 0xf;
static const int kItalicMask = 16;
uint32_t bits;
};
+inline hash_t hash_type(const FontStyle &style) {
+ return style.hash();
+}
+
class FontFamily : public MinikinRefCounted {
public:
~FontFamily();
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index 4fe9d87..a1ef0c1 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -55,6 +55,9 @@ struct LayoutGlyph {
float y;
};
+// Internal state used during layout operation
+class LayoutContext;
+
// Lifecycle and threading assumptions for Layout:
// The object is assumed to be owned by a single thread; multiple threads
// may not mutate it at the same time.
@@ -62,13 +65,19 @@ struct LayoutGlyph {
// extend through the lifetime of the Layout object.
class Layout {
public:
- ~Layout();
-
void dump() const;
void setFontCollection(const FontCollection* collection);
+
+ // deprecated - missing functionality
void doLayout(const uint16_t* buf, size_t nchars);
- void draw(Bitmap*, int x0, int y0) const;
- void setProperties(const std::string css);
+
+ void doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ const std::string& css);
+
+ void draw(Bitmap*, int x0, int y0, float size) const;
+
+ // deprecated - pass as argument to doLayout instead
+ void setProperties(const std::string& css);
// This must be called before any invocations.
// TODO: probably have a factory instead
@@ -92,19 +101,31 @@ public:
private:
// Find a face in the mFaces vector, or create a new entry
- int findFace(MinikinFont* face, MinikinPaint* paint);
+ int findFace(MinikinFont* face, LayoutContext* ctx);
+
+ // Lay out a single bidi run
+ void doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx);
+
+ // Lay out a single word
+ void doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx, size_t bufStart);
+
+ // Lay out a single bidi run
+ void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx);
+
+ // Append another layout (for example, cached value) into this one
+ void appendLayout(Layout* src, size_t start);
+
+ // deprecated - remove when setProperties is removed
+ std::string mCssString;
- CssProperties mProps; // TODO: want spans
std::vector<LayoutGlyph> mGlyphs;
std::vector<float> mAdvances;
- // In future, this will be some kind of mapping from the
- // identifier used to represent font-family to a font collection.
- // But for the time being, it should be ok to have just one
- // per layout.
const FontCollection* mCollection;
std::vector<MinikinFont *> mFaces;
- std::vector<hb_font_t *> mHbFonts;
float mAdvance;
MinikinRect mBounds;
};
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index 568f19d..dbb89f8 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -27,6 +27,7 @@ namespace android {
class MinikinFont;
// Possibly move into own .h file?
+// Note: if you add a field here, also update LayoutCacheKey
struct MinikinPaint {
MinikinFont *font;
float size;
diff --git a/libs/minikin/Android.mk b/libs/minikin/Android.mk
index c441285..a1d88c2 100644
--- a/libs/minikin/Android.mk
+++ b/libs/minikin/Android.mk
@@ -43,6 +43,8 @@ LOCAL_SHARED_LIBRARIES := \
liblog \
libpng \
libz \
- libstlport
+ libstlport \
+ libicuuc \
+ libutils
include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index cd7b19e..67089db 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -32,9 +32,12 @@ static inline T max(T a, T b) {
return a>b ? a : b;
}
+uint32_t FontCollection::sNextId = 0;
+
FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
mMaxChar(0) {
AutoMutex _l(gMinikinLock);
+ mId = sNextId++;
vector<uint32_t> lastChar;
size_t nTypefaces = typefaces.size();
#ifdef VERBOSE_DEBUG
@@ -50,6 +53,12 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
instance->mFamily = family;
instance->mCoverage = new SparseBitSet;
MinikinFont* typeface = family->getClosestMatch(defaultStyle);
+ if (typeface == NULL) {
+ ALOGE("FontCollection: closest match was null");
+ // TODO: we shouldn't hit this, as there should be more robust
+ // checks upstream to prevent empty/invalid FontFamily objects
+ continue;
+ }
#ifdef VERBOSE_DEBUG
ALOGD("closest match = %p, family size = %d\n", typeface, family->getNumFonts());
#endif
@@ -149,4 +158,8 @@ void FontCollection::itemize(const uint16_t *string, size_t string_size, FontSty
}
}
+uint32_t FontCollection::getId() const {
+ return mId;
+}
+
} // namespace android
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index f32e9f4..886b7d1 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -24,6 +24,11 @@
#include <iostream> // for debugging
#include <stdio.h> // ditto
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+#include <utils/Singleton.h>
+#include <utils/String16.h>
+
#include <unicode/ubidi.h>
#include <hb-icu.h>
@@ -36,8 +41,128 @@ using std::vector;
namespace android {
-// TODO: globals are not cool, move to a factory-ish object
-hb_buffer_t* buffer = 0;
+// TODO: these should move into the header file, but for now we don't want
+// to cause namespace collisions with TextLayout.h
+enum {
+ kBidi_LTR = 0,
+ kBidi_RTL = 1,
+ kBidi_Default_LTR = 2,
+ kBidi_Default_RTL = 3,
+ kBidi_Force_LTR = 4,
+ kBidi_Force_RTL = 5,
+
+ kBidi_Mask = 0x7
+};
+
+const int kDirection_Mask = 0x1;
+
+// Layout cache datatypes
+
+class LayoutCacheKey {
+public:
+ LayoutCacheKey(const FontCollection* collection, const MinikinPaint& paint, FontStyle style,
+ const uint16_t* chars, size_t start, size_t count, size_t nchars, bool dir)
+ : mStart(start), mCount(count), mId(collection->getId()), mStyle(style),
+ mSize(paint.size), mIsRtl(dir) {
+ mText.setTo(chars, nchars);
+ }
+ bool operator==(const LayoutCacheKey &other) const;
+ hash_t hash() const;
+
+ // This is present to avoid having to copy the text more than once.
+ const uint16_t* textBuf() { return mText.string(); }
+private:
+ String16 mText;
+ size_t mStart;
+ size_t mCount;
+ uint32_t mId; // for the font collection
+ FontStyle mStyle;
+ float mSize;
+ bool mIsRtl;
+ // Note: any fields added to MinikinPaint must also be reflected here.
+ // TODO: language matching (possibly integrate into style)
+};
+
+class LayoutCache : private OnEntryRemoved<LayoutCacheKey, Layout*> {
+public:
+ LayoutCache() : mCache(kMaxEntries) {
+ mCache.setOnEntryRemovedListener(this);
+ }
+
+ // callback for OnEntryRemoved
+ void operator()(LayoutCacheKey& key, Layout*& value) {
+ delete value;
+ }
+
+ LruCache<LayoutCacheKey, Layout*> mCache;
+private:
+ //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;
+};
+
+class HbFaceCache : private OnEntryRemoved<int32_t, hb_face_t*> {
+public:
+ HbFaceCache() : mCache(kMaxEntries) {
+ mCache.setOnEntryRemovedListener(this);
+ }
+
+ // callback for OnEntryRemoved
+ void operator()(int32_t& key, hb_face_t*& value) {
+ hb_face_destroy(value);
+ }
+
+ LruCache<int32_t, hb_face_t*> mCache;
+private:
+ static const size_t kMaxEntries = 100;
+};
+
+class LayoutEngine : public Singleton<LayoutEngine> {
+public:
+ LayoutEngine() {
+ hbBuffer = hb_buffer_create();
+ }
+
+ hb_buffer_t* hbBuffer;
+ LayoutCache layoutCache;
+ HbFaceCache hbFaceCache;
+};
+
+ANDROID_SINGLETON_STATIC_INSTANCE(LayoutEngine);
+
+bool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {
+ return mId == other.mId &&
+ mStart == other.mStart &&
+ mCount == other.mCount &&
+ mStyle == other.mStyle &&
+ mSize == other.mSize &&
+ mIsRtl == other.mIsRtl &&
+ mText == other.mText;
+}
+
+hash_t LayoutCacheKey::hash() const {
+ uint32_t hash = JenkinsHashMix(0, mId);
+ hash = JenkinsHashMix(hash, mStart);
+ hash = JenkinsHashMix(hash, mCount);
+ hash = JenkinsHashMix(hash, hash_type(mStyle));
+ hash = JenkinsHashMix(hash, hash_type(mSize));
+ hash = JenkinsHashMix(hash, hash_type(mIsRtl));
+ hash = JenkinsHashMixShorts(hash, mText.string(), mText.size());
+ return JenkinsHashWhiten(hash);
+}
+
+struct LayoutContext {
+ MinikinPaint paint;
+ FontStyle style;
+ CssProperties props;
+ std::vector<hb_font_t*> hbFonts; // parallel to mFaces
+};
+
+hash_t hash_type(const LayoutCacheKey& key) {
+ return key.hash();
+}
Bitmap::Bitmap(int width, int height) : width(width), height(height) {
buf = new uint8_t[width * height]();
@@ -92,18 +217,18 @@ void MinikinRect::join(const MinikinRect& r) {
void Layout::init() {
}
-void Layout::setFontCollection(const FontCollection *collection) {
+void Layout::setFontCollection(const FontCollection* collection) {
mCollection = collection;
}
hb_blob_t* referenceTable(hb_face_t* face, hb_tag_t tag, void* userData) {
- MinikinFont* font = reinterpret_cast<MinikinFont *>(userData);
+ MinikinFont* font = reinterpret_cast<MinikinFont*>(userData);
size_t length = 0;
bool ok = font->GetTable(tag, NULL, &length);
if (!ok) {
return 0;
}
- char *buffer = reinterpret_cast<char*>(malloc(length));
+ char* buffer = reinterpret_cast<char*>(malloc(length));
if (!buffer) {
return 0;
}
@@ -120,7 +245,7 @@ hb_blob_t* referenceTable(hb_face_t* face, hb_tag_t tag, void* userData) {
static hb_bool_t harfbuzzGetGlyph(hb_font_t* hbFont, void* fontData, hb_codepoint_t unicode, hb_codepoint_t variationSelector, hb_codepoint_t* glyph, void* userData)
{
- MinikinPaint* paint = reinterpret_cast<MinikinPaint *>(fontData);
+ MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
MinikinFont* font = paint->font;
uint32_t glyph_id;
bool ok = font->GetGlyph(unicode, &glyph_id);
@@ -132,7 +257,7 @@ static hb_bool_t harfbuzzGetGlyph(hb_font_t* hbFont, void* fontData, hb_codepoin
static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, void* userData)
{
- MinikinPaint* paint = reinterpret_cast<MinikinPaint *>(fontData);
+ MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
MinikinFont* font = paint->font;
float advance = font->GetHorizontalAdvance(glyph, *paint);
return 256 * advance + 0.5;
@@ -158,12 +283,21 @@ hb_font_funcs_t* getHbFontFuncs() {
return hbFontFuncs;
}
-hb_font_t* create_hb_font(MinikinFont* minikinFont, MinikinPaint* minikinPaint) {
- hb_face_t* face = hb_face_create_for_tables(referenceTable, minikinFont, NULL);
+static hb_face_t* getHbFace(MinikinFont* minikinFont) {
+ HbFaceCache& cache = LayoutEngine::getInstance().hbFaceCache;
+ int32_t fontId = minikinFont->GetUniqueId();
+ hb_face_t* face = cache.mCache.get(fontId);
+ if (face == NULL) {
+ face = hb_face_create_for_tables(referenceTable, minikinFont, NULL);
+ cache.mCache.put(fontId, face);
+ }
+ return face;
+}
+
+static hb_font_t* create_hb_font(MinikinFont* minikinFont, MinikinPaint* minikinPaint) {
+ hb_face_t* face = getHbFace(minikinFont);
hb_font_t* font = hb_font_create(face);
- hb_face_destroy(face);
hb_font_set_funcs(font, getHbFontFuncs(), minikinPaint, 0);
- // TODO: manage ownership of face
return font;
}
@@ -177,12 +311,6 @@ static hb_position_t HBFloatToFixed(float v)
return scalbnf (v, +8);
}
-Layout::~Layout() {
- for (size_t ix = 0; ix < mHbFonts.size(); ix++) {
- hb_font_destroy(mHbFonts[ix]);
- }
-}
-
void Layout::dump() const {
for (size_t i = 0; i < mGlyphs.size(); i++) {
const LayoutGlyph& glyph = mGlyphs[i];
@@ -190,11 +318,7 @@ void Layout::dump() const {
}
}
-// A couple of things probably need to change:
-// 1. Deal with multiple sizes in a layout
-// 2. We'll probably store FT_Face as primary and then use a cache
-// for the hb fonts
-int Layout::findFace(MinikinFont* face, MinikinPaint* paint) {
+int Layout::findFace(MinikinFont* face, LayoutContext* ctx) {
unsigned int ix;
for (ix = 0; ix < mFaces.size(); ix++) {
if (mFaces[ix] == face) {
@@ -202,8 +326,12 @@ int Layout::findFace(MinikinFont* face, MinikinPaint* paint) {
}
}
mFaces.push_back(face);
- hb_font_t *font = create_hb_font(face, paint);
- mHbFonts.push_back(font);
+ // 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 = create_hb_font(face, &ctx->paint);
+ ctx->hbFonts.push_back(font);
+ }
return ix;
}
@@ -220,14 +348,14 @@ static FontStyle styleFromCss(const CssProperties &props) {
}
static hb_script_t codePointToScript(hb_codepoint_t codepoint) {
- static hb_unicode_funcs_t *u = 0;
+ static hb_unicode_funcs_t* u = 0;
if (!u) {
u = hb_icu_get_unicode_funcs();
}
return hb_unicode_script(u, codepoint);
}
-static hb_codepoint_t decodeUtf16(const uint16_t *chars, size_t len, ssize_t *iter) {
+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) {
@@ -251,7 +379,7 @@ static hb_codepoint_t decodeUtf16(const uint16_t *chars, size_t len, ssize_t *it
}
}
-static hb_script_t getScriptRun(const uint16_t *chars, size_t len, ssize_t *iter) {
+static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
if (size_t(*iter) == len) {
return HB_SCRIPT_UNKNOWN;
}
@@ -283,41 +411,214 @@ static hb_script_t getScriptRun(const uint16_t *chars, size_t len, ssize_t *iter
return current_script;
}
-// TODO: API should probably take context
-void Layout::doLayout(const uint16_t* buf, size_t nchars) {
- AutoMutex _l(gMinikinLock);
- if (buffer == 0) {
- buffer = hb_buffer_create();
+/**
+ * For the purpose of layout, a word break is a boundary with no
+ * kerning or complex script processing. This is necessarily a
+ * heuristic, but should be accurate most of the time.
+ */
+static bool isWordBreak(int c) {
+ if (c == ' ' || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
+ // spaces
+ return true;
}
- FT_Error error;
+ if ((c >= 0x3400 && c <= 0x9fff)) {
+ // CJK ideographs (and yijing hexagram symbols)
+ return true;
+ }
+ // Note: kana is not included, as sophisticated fonts may kern kana
+ return false;
+}
- vector<FontCollection::Run> items;
- FontStyle style = styleFromCss(mProps);
- mCollection->itemize(buf, nchars, style, &items);
+/**
+ * Return offset of previous word break. It is either < offset or == 0.
+ */
+static size_t getPrevWordBreak(const uint16_t* chars, size_t offset) {
+ if (offset == 0) return 0;
+ if (isWordBreak(chars[offset - 1])) {
+ return offset - 1;
+ }
+ for (size_t i = offset - 1; i > 0; i--) {
+ if (isWordBreak(chars[i - 1])) {
+ return i;
+ }
+ }
+ return 0;
+}
- MinikinPaint paint;
- double size = mProps.value(fontSize).getFloatValue();
- paint.size = size;
- int bidiFlags = mProps.hasTag(minikinBidi) ? mProps.value(minikinBidi).getIntValue() : 0;
- bool isRtl = (bidiFlags & 1) != 0; // TODO: do real bidi algo
- if (isRtl) {
- std::reverse(items.begin(), items.end());
+/**
+ * Return offset of next word break. It is either > offset or == len.
+ */
+static size_t getNextWordBreak(const uint16_t* chars, size_t offset, size_t len) {
+ if (offset >= len) return len;
+ if (isWordBreak(chars[offset])) {
+ return offset + 1;
+ }
+ for (size_t i = offset + 1; i < len; i++) {
+ if (isWordBreak(chars[i])) {
+ return i;
+ }
}
+ return len;
+}
+
+// deprecated API, to avoid breaking client
+void Layout::doLayout(const uint16_t* buf, size_t nchars) {
+ doLayout(buf, 0, nchars, nchars, mCssString);
+}
+
+// TODO: use some standard implementation
+template<typename T>
+static T mymin(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+template<typename T>
+static T mymax(const T& a, const T& b) {
+ return a > b ? a : b;
+}
+
+static void clearHbFonts(LayoutContext* ctx) {
+ for (size_t i = 0; i < ctx->hbFonts.size(); i++) {
+ hb_font_destroy(ctx->hbFonts[i]);
+ }
+ ctx->hbFonts.clear();
+}
+
+// TODO: API should probably take context
+void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ const std::string& css) {
+ AutoMutex _l(gMinikinLock);
+ LayoutContext ctx;
+
+ ctx.props.parse(css);
+ ctx.style = styleFromCss(ctx.props);
+
+ double size = ctx.props.value(fontSize).getFloatValue();
+ ctx.paint.size = size;
+ int bidiFlags = ctx.props.hasTag(minikinBidi) ? ctx.props.value(minikinBidi).getIntValue() : 0;
+ bool isRtl = (bidiFlags & kDirection_Mask) != 0;
+ bool doSingleRun = true;
mGlyphs.clear();
mFaces.clear();
- mHbFonts.clear();
mBounds.setEmpty();
mAdvances.clear();
- mAdvances.resize(nchars, 0);
- float x = 0;
+ mAdvances.resize(count, 0);
+ mAdvance = 0;
+ if (!(bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL)) {
+ UBiDi* bidi = ubidi_open();
+ if (bidi) {
+ 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(bidi, buf, bufSize, bidiReq, NULL, &status);
+ if (U_SUCCESS(status)) {
+ int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask;
+ ssize_t rc = ubidi_countRuns(bidi, &status);
+ if (!U_SUCCESS(status) || rc < 0) {
+ ALOGW("error counting bidi runs, status = %d", status);
+ }
+ if (!U_SUCCESS(status) || rc <= 1) {
+ isRtl = (paraDir == kBidi_RTL);
+ } else {
+ doSingleRun = false;
+ // iterate through runs
+ for (ssize_t i = 0; i < (ssize_t)rc; i++) {
+ int32_t startRun = -1;
+ int32_t lengthRun = -1;
+ UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
+ if (startRun == -1 || lengthRun == -1) {
+ ALOGE("invalid visual run");
+ // skip the invalid run
+ continue;
+ }
+ isRtl = (runDir == UBIDI_RTL);
+ // TODO: min/max with context
+ doLayoutRunCached(buf, startRun, lengthRun, bufSize, isRtl, &ctx);
+ }
+ }
+ } else {
+ ALOGE("error calling ubidi_setPara, status = %d", status);
+ }
+ ubidi_close(bidi);
+ } else {
+ ALOGE("error creating bidi object");
+ }
+ }
+ if (doSingleRun) {
+ doLayoutRunCached(buf, start, count, bufSize, isRtl, &ctx);
+ }
+ clearHbFonts(&ctx);
+}
+
+void Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx) {
+ if (!isRtl) {
+ // left to right
+ size_t wordstart = start == bufSize ? start : getPrevWordBreak(buf, start + 1);
+ size_t wordend;
+ for (size_t iter = start; iter < start + count; iter = wordend) {
+ wordend = getNextWordBreak(buf, iter, bufSize);
+ size_t wordcount = mymin(start + count, wordend) - iter;
+ doLayoutWord(buf + wordstart, iter - wordstart, wordcount, wordend - wordstart,
+ isRtl, ctx, iter);
+ wordstart = wordend;
+ }
+ } else {
+ // right to left
+ size_t wordstart;
+ size_t end = start + count;
+ size_t wordend = end == 0 ? 0 : getNextWordBreak(buf, end - 1, bufSize);
+ for (size_t iter = end; iter > start; iter = wordstart) {
+ wordstart = getPrevWordBreak(buf, iter);
+ size_t bufStart = mymax(start, wordstart);
+ doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
+ wordend - wordstart, isRtl, ctx, bufStart);
+ wordend = wordstart;
+ }
+ }
+}
+
+void Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx, size_t bufStart) {
+ LayoutCache& cache = LayoutEngine::getInstance().layoutCache;
+ LayoutCacheKey key(mCollection, ctx->paint, ctx->style, buf, start, count, bufSize, isRtl);
+ Layout* value = cache.mCache.get(key);
+ if (value == NULL) {
+ value = new Layout();
+ value->setFontCollection(mCollection);
+ value->mAdvances.resize(count, 0);
+ clearHbFonts(ctx);
+ // Note: we do the layout from the copy stored in the key, in case a
+ // badly-behaved client is mutating the buffer in a separate thread.
+ value->doLayoutRun(key.textBuf(), start, count, bufSize, isRtl, ctx);
+ }
+ appendLayout(value, bufStart);
+ cache.mCache.put(key, value);
+
+}
+
+void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ bool isRtl, LayoutContext* ctx) {
+ hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;
+ vector<FontCollection::Run> items;
+ mCollection->itemize(buf + start, count, ctx->style, &items);
+ if (isRtl) {
+ std::reverse(items.begin(), items.end());
+ }
+
+ float x = mAdvance;
float y = 0;
for (size_t run_ix = 0; run_ix < items.size(); run_ix++) {
FontCollection::Run &run = items[run_ix];
- int font_ix = findFace(run.font, &paint);
- paint.font = mFaces[font_ix];
- hb_font_t *hbFont = mHbFonts[font_ix];
- if (paint.font == NULL) {
+ int font_ix = findFace(run.font, ctx);
+ ctx->paint.font = mFaces[font_ix];
+ hb_font_t* hbFont = ctx->hbFonts[font_ix];
+ if (ctx->paint.font == NULL) {
// TODO: should log what went wrong
continue;
}
@@ -325,6 +626,7 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) {
std::cout << "Run " << run_ix << ", font " << font_ix <<
" [" << run.start << ":" << run.end << "]" << std::endl;
#endif
+ double size = ctx->paint.size;
hb_font_set_ppem(hbFont, size, size);
hb_font_set_scale(hbFont, HBFloatToFixed(size), HBFloatToFixed(size));
@@ -334,16 +636,16 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) {
ssize_t srunend;
for (ssize_t srunstart = run.start; srunstart < run.end; srunstart = srunend) {
srunend = srunstart;
- hb_script_t script = getScriptRun(buf, run.end, &srunend);
+ hb_script_t script = getScriptRun(buf + start, run.end, &srunend);
hb_buffer_reset(buffer);
hb_buffer_set_script(buffer, script);
hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
- hb_buffer_add_utf16(buffer, buf, nchars, srunstart, srunend - srunstart);
+ hb_buffer_add_utf16(buffer, buf, bufSize, srunstart + start, srunend - srunstart);
hb_shape(hbFont, buffer, NULL, 0);
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);
+ hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
+ hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
for (unsigned int i = 0; i < numGlyphs; i++) {
#ifdef VERBOSE
std::cout << positions[i].x_advance << " " << positions[i].y_advance << " " << positions[i].x_offset << " " << positions[i].y_offset << std::endl; std::cout << "DoLayout " << info[i].codepoint <<
@@ -356,7 +658,7 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) {
mGlyphs.push_back(glyph);
float xAdvance = HBFixedToFloat(positions[i].x_advance);
MinikinRect glyphBounds;
- paint.font->GetBounds(&glyphBounds, glyph_ix, paint);
+ ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
glyphBounds.offset(x + xoff, y + yoff);
mBounds.join(glyphBounds);
size_t cluster = info[i].cluster;
@@ -368,7 +670,33 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) {
mAdvance = x;
}
-void Layout::draw(Bitmap* surface, int x0, int y0) const {
+void Layout::appendLayout(Layout* src, size_t start) {
+ // Note: size==1 is by far most common, should have specialized vector for this
+ std::vector<int> fontMap;
+ for (size_t i = 0; i < src->mFaces.size(); i++) {
+ int font_ix = findFace(src->mFaces[i], NULL);
+ fontMap.push_back(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];
+ }
+ MinikinRect srcBounds(src->mBounds);
+ srcBounds.offset(x0, 0);
+ mBounds.join(srcBounds);
+ mAdvance += src->mAdvance;
+}
+
+void Layout::draw(Bitmap* surface, int x0, int y0, float size) const {
/*
TODO: redo as MinikinPaint settings
if (mProps.hasTag(minikinHinting)) {
@@ -379,11 +707,11 @@ void Layout::draw(Bitmap* surface, int x0, int y0) const {
*/
for (size_t i = 0; i < mGlyphs.size(); i++) {
const LayoutGlyph& glyph = mGlyphs[i];
- MinikinFont *mf = mFaces[glyph.font_ix];
- MinikinFontFreeType *face = static_cast<MinikinFontFreeType *>(mf);
+ MinikinFont* mf = mFaces[glyph.font_ix];
+ MinikinFontFreeType* face = static_cast<MinikinFontFreeType*>(mf);
GlyphBitmap glyphBitmap;
MinikinPaint paint;
- paint.size = mProps.value(fontSize).getFloatValue();
+ paint.size = size;
bool ok = face->Render(glyph.glyph_id, paint, &glyphBitmap);
printf("glyphBitmap.width=%d, glyphBitmap.height=%d (%d, %d) x=%f, y=%f, ok=%d\n",
glyphBitmap.width, glyphBitmap.height, glyphBitmap.left, glyphBitmap.top, glyph.x, glyph.y, ok);
@@ -394,15 +722,15 @@ void Layout::draw(Bitmap* surface, int x0, int y0) const {
}
}
-void Layout::setProperties(string css) {
- mProps.parse(css);
+void Layout::setProperties(const string& css) {
+ mCssString = css;
}
size_t Layout::nGlyphs() const {
return mGlyphs.size();
}
-MinikinFont *Layout::getFont(int i) const {
+MinikinFont* Layout::getFont(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
return mFaces[glyph.font_ix];
}
diff --git a/sample/example.cpp b/sample/example.cpp
index 9b012ef..b8bd66f 100644
--- a/sample/example.cpp
+++ b/sample/example.cpp
@@ -89,7 +89,7 @@ int runMinikinTest() {
layout.doLayout(icuText.getBuffer(), icuText.length());
layout.dump();
Bitmap bitmap(250, 50);
- layout.draw(&bitmap, 10, 40);
+ layout.draw(&bitmap, 10, 40, 32);
std::ofstream o;
o.open("/data/local/tmp/foo.pgm", std::ios::out | std::ios::binary);
bitmap.writePnm(o);