mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fractional advance widths were causing subtle problems with text positioning when the same text was drawn with different spans in the hwui renderer. Quantizing the coordinates on layout (as opposed to waiting until the renderer draws the glyphs) solves the problem. This patch also fixes a discrepancy between x position and advance widths when letterspacing. Bug: 17347779 Change-Id: Ia705944047408c2839d5ad078eefd6bbec446872
883 lines
29 KiB
C++
883 lines
29 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 <cutils/log.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#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>
|
|
|
|
#include "MinikinInternal.h"
|
|
#include <minikin/MinikinFontFreeType.h>
|
|
#include <minikin/Layout.h>
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace android {
|
|
|
|
// 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;
|
|
|
|
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_destroy(hbFonts[i]);
|
|
}
|
|
hbFonts.clear();
|
|
}
|
|
};
|
|
|
|
// 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), mScaleX(paint.scaleX), mSkewX(paint.skewX),
|
|
mLetterSpacing(paint.letterSpacing),
|
|
mPaintFlags(paint.paintFlags), mIsRtl(dir),
|
|
mChars(chars), mNchars(nchars) {
|
|
}
|
|
bool operator==(const LayoutCacheKey &other) const;
|
|
hash_t hash() const;
|
|
|
|
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 FontCollection* collection) const {
|
|
layout->setFontCollection(collection);
|
|
layout->mAdvances.resize(mCount, 0);
|
|
ctx->clearHbFonts();
|
|
layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx);
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
|
|
void clear() {
|
|
mCache.clear();
|
|
}
|
|
|
|
Layout* get(LayoutCacheKey& key, LayoutContext* ctx, const 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;
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
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
|
|
&& mScaleX == other.mScaleX
|
|
&& mSkewX == other.mSkewX
|
|
&& mLetterSpacing == other.mLetterSpacing
|
|
&& mPaintFlags == other.mPaintFlags
|
|
&& mIsRtl == other.mIsRtl
|
|
&& mNchars == other.mNchars
|
|
&& !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));
|
|
}
|
|
|
|
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(mScaleX));
|
|
hash = JenkinsHashMix(hash, hash_type(mSkewX));
|
|
hash = JenkinsHashMix(hash, hash_type(mLetterSpacing));
|
|
hash = JenkinsHashMix(hash, hash_type(mPaintFlags));
|
|
hash = JenkinsHashMix(hash, hash_type(mIsRtl));
|
|
hash = JenkinsHashMixShorts(hash, mChars, mNchars);
|
|
return JenkinsHashWhiten(hash);
|
|
}
|
|
|
|
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]();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Deprecated. Remove when callers are removed.
|
|
void Layout::init() {
|
|
}
|
|
|
|
void Layout::reset() {
|
|
mGlyphs.clear();
|
|
mFaces.clear();
|
|
mBounds.setEmpty();
|
|
mAdvances.clear();
|
|
mAdvance = 0;
|
|
}
|
|
|
|
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);
|
|
size_t length = 0;
|
|
bool ok = font->GetTable(tag, NULL, &length);
|
|
if (!ok) {
|
|
return 0;
|
|
}
|
|
char* buffer = reinterpret_cast<char*>(malloc(length));
|
|
if (!buffer) {
|
|
return 0;
|
|
}
|
|
ok = font->GetTable(tag, reinterpret_cast<uint8_t*>(buffer), &length);
|
|
printf("referenceTable %c%c%c%c length=%d %d\n",
|
|
(tag >>24) & 0xff, (tag>>16)&0xff, (tag>>8)&0xff, tag&0xff, length, ok);
|
|
if (!ok) {
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
return hb_blob_create(const_cast<char*>(buffer), length,
|
|
HB_MEMORY_MODE_WRITABLE, buffer, free);
|
|
}
|
|
|
|
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);
|
|
MinikinFont* font = paint->font;
|
|
uint32_t glyph_id;
|
|
/* HarfBuzz replaces broken input codepoints with (unsigned int) -1.
|
|
* Skia expects valid Unicode.
|
|
* Replace invalid codepoints with U+FFFD REPLACEMENT CHARACTER.
|
|
*/
|
|
if (unicode > 0x10FFFF)
|
|
unicode = 0xFFFD;
|
|
bool ok = font->GetGlyph(unicode, &glyph_id);
|
|
if (ok) {
|
|
*glyph = glyph_id;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, void* userData)
|
|
{
|
|
MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
|
|
MinikinFont* font = paint->font;
|
|
float advance = 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() {
|
|
static hb_font_funcs_t* hbFontFuncs = 0;
|
|
|
|
if (hbFontFuncs == 0) {
|
|
hbFontFuncs = hb_font_funcs_create();
|
|
hb_font_funcs_set_glyph_func(hbFontFuncs, harfbuzzGetGlyph, 0, 0);
|
|
hb_font_funcs_set_glyph_h_advance_func(hbFontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
|
|
hb_font_funcs_set_glyph_h_origin_func(hbFontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
|
|
hb_font_funcs_make_immutable(hbFontFuncs);
|
|
}
|
|
return hbFontFuncs;
|
|
}
|
|
|
|
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_font_set_funcs(font, getHbFontFuncs(), minikinPaint, 0);
|
|
return font;
|
|
}
|
|
|
|
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(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 = create_hb_font(face.font, &ctx->paint);
|
|
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 = 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) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
|
|
int bidiFlags, const FontStyle &style, const MinikinPaint &paint) {
|
|
AutoMutex _l(gMinikinLock);
|
|
|
|
LayoutContext ctx;
|
|
ctx.style = style;
|
|
ctx.paint = paint;
|
|
|
|
bool isRtl = (bidiFlags & kDirection_Mask) != 0;
|
|
bool doSingleRun = true;
|
|
|
|
reset();
|
|
mAdvances.resize(count, 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;
|
|
}
|
|
int32_t endRun = std::min(startRun + lengthRun, int32_t(start + count));
|
|
startRun = std::max(startRun, int32_t(start));
|
|
lengthRun = endRun - startRun;
|
|
if (lengthRun > 0) {
|
|
isRtl = (runDir == UBIDI_RTL);
|
|
doLayoutRunCached(buf, startRun, lengthRun, bufSize, isRtl, &ctx,
|
|
start);
|
|
}
|
|
}
|
|
}
|
|
} 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, start);
|
|
}
|
|
ctx.clearHbFonts();
|
|
}
|
|
|
|
void Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
|
|
bool isRtl, LayoutContext* ctx, size_t dstStart) {
|
|
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 = std::min(start + count, wordend) - iter;
|
|
doLayoutWord(buf + wordstart, iter - wordstart, wordcount, wordend - wordstart,
|
|
isRtl, ctx, iter - dstStart);
|
|
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 = std::max(start, wordstart);
|
|
doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
|
|
wordend - wordstart, isRtl, ctx, bufStart - dstStart);
|
|
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);
|
|
bool skipCache = ctx->paint.skipCache();
|
|
if (skipCache) {
|
|
Layout layout;
|
|
key.doLayout(&layout, ctx, mCollection);
|
|
appendLayout(&layout, bufStart);
|
|
} else {
|
|
Layout* layout = cache.get(key, ctx, mCollection);
|
|
appendLayout(layout, bufStart);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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;
|
|
double letterSpace = ctx->paint.letterSpacing * size * scaleX;
|
|
double letterSpaceHalfLeft;
|
|
if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
|
|
letterSpace = round(letterSpace);
|
|
letterSpaceHalfLeft = floor(letterSpace * 0.5);
|
|
} else {
|
|
letterSpaceHalfLeft = letterSpace * 0.5;
|
|
}
|
|
double letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
|
|
|
|
float x = mAdvance;
|
|
float y = 0;
|
|
for (size_t run_ix = 0; run_ix < items.size(); 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
|
|
std::cout << "Run " << run_ix << ", font " << font_ix <<
|
|
" [" << run.start << ":" << run.end << "]" << std::endl;
|
|
#endif
|
|
|
|
hb_font_set_ppem(hbFont, size * scaleX, size);
|
|
hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
|
|
|
|
// 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.
|
|
ssize_t srunend;
|
|
for (ssize_t srunstart = run.start; srunstart < run.end; srunstart = srunend) {
|
|
srunend = srunstart;
|
|
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);
|
|
FontLanguage language = ctx->style.getLanguage();
|
|
if (language) {
|
|
string lang = language.getString();
|
|
hb_buffer_set_language(buffer, hb_language_from_string(lang.c_str(), -1));
|
|
}
|
|
hb_buffer_add_utf16(buffer, buf, bufSize, srunstart + start, srunend - srunstart);
|
|
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);
|
|
if (numGlyphs)
|
|
{
|
|
mAdvances[info[0].cluster - start] += letterSpaceHalfLeft;
|
|
x += letterSpaceHalfLeft;
|
|
}
|
|
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 <<
|
|
": " << HBFixedToFloat(positions[i].x_advance) << "; " << positions[i].x_offset << ", " << positions[i].y_offset << std::endl;
|
|
#endif
|
|
if (i > 0 && info[i - 1].cluster != info[i].cluster) {
|
|
mAdvances[info[i - 1].cluster - start] += letterSpaceHalfRight;
|
|
mAdvances[info[i].cluster - start] += 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;
|
|
ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
|
|
glyphBounds.offset(x + xoff, y + yoff);
|
|
mBounds.join(glyphBounds);
|
|
mAdvances[info[i].cluster - start] += xAdvance;
|
|
x += xAdvance;
|
|
}
|
|
if (numGlyphs)
|
|
{
|
|
mAdvances[info[numGlyphs - 1].cluster - start] += letterSpaceHalfRight;
|
|
x += letterSpaceHalfRight;
|
|
}
|
|
}
|
|
}
|
|
mAdvance = x;
|
|
}
|
|
|
|
void Layout::appendLayout(Layout* src, size_t start) {
|
|
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];
|
|
}
|
|
MinikinRect srcBounds(src->mBounds);
|
|
srcBounds.offset(x0, 0);
|
|
mBounds.join(srcBounds);
|
|
mAdvance += src->mAdvance;
|
|
|
|
if (fontMap != fontMapStack) {
|
|
delete[] fontMap;
|
|
}
|
|
}
|
|
|
|
void Layout::draw(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);
|
|
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);
|
|
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();
|
|
}
|
|
|
|
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) {
|
|
bounds->set(mBounds);
|
|
}
|
|
|
|
void Layout::purgeCaches() {
|
|
AutoMutex _l(gMinikinLock);
|
|
LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
|
|
layoutCache.clear();
|
|
HbFaceCache& hbCache = LayoutEngine::getInstance().hbFaceCache;
|
|
hbCache.mCache.clear();
|
|
}
|
|
|
|
} // namespace android
|