diff options
author | Raph Levien <raph@google.com> | 2013-07-15 14:19:59 -0700 |
---|---|---|
committer | Raph Levien <raph@google.com> | 2014-05-12 09:08:15 -0700 |
commit | ecc2d34ac23a497988f21e5f415b53c007b9d8c5 (patch) | |
tree | 876b3c943b7841c9600db636738cd0e59b5f2a7c | |
parent | 5adafc0d84d238948b5d257ec5311030ca04271c (diff) | |
download | android_frameworks_minikin-ecc2d34ac23a497988f21e5f415b53c007b9d8c5.tar.gz android_frameworks_minikin-ecc2d34ac23a497988f21e5f415b53c007b9d8c5.tar.bz2 android_frameworks_minikin-ecc2d34ac23a497988f21e5f415b53c007b9d8c5.zip |
A basket of features: itemization, bounds, refcount
This patch improves script run itemization and also exposes metrics
and bounds for layouts. In addition, there is a fair amount of internal
cleanup, including ref counting, and making the MinikinFont abstraction
strong enough to support both FreeType and Skia implementations. There
is also a sample implementation using Skia, in the sample directory.
As part of its functionality, his patch measures the bounds of the
layout and gives access through Layout::GetBounds(). The corresponding
method is not implemented in the FreeType-only implementation of
MinikinFont, so that will probably have to be fixed.
Change-Id: Ib1a3fe9d7c90519ac651fb4aa957848e4bb758ec
-rw-r--r-- | include/minikin/Layout.h | 26 | ||||
-rw-r--r-- | include/minikin/MinikinFont.h | 28 | ||||
-rw-r--r-- | libs/minikin/Android.mk | 4 | ||||
-rw-r--r-- | libs/minikin/FontCollection.cpp | 19 | ||||
-rw-r--r-- | libs/minikin/FontFamily.cpp | 1 | ||||
-rw-r--r-- | libs/minikin/Layout.cpp | 180 | ||||
-rw-r--r-- | sample/Android.mk | 36 | ||||
-rw-r--r-- | sample/MinikinSkia.cpp | 64 | ||||
-rw-r--r-- | sample/MinikinSkia.h | 26 | ||||
-rw-r--r-- | sample/example_skia.cpp | 152 |
10 files changed, 499 insertions, 37 deletions
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h index fd0ed75..896478b 100644 --- a/include/minikin/Layout.h +++ b/include/minikin/Layout.h @@ -57,32 +57,50 @@ struct LayoutGlyph { class Layout { public: + void dump() const; - void setFontCollection(const FontCollection *collection); + void setFontCollection(const FontCollection* collection); void doLayout(const uint16_t* buf, size_t nchars); void draw(Bitmap*, int x0, int y0) const; void setProperties(const std::string css); - float getAdvance() const; - // This must be called before any invocations. // TODO: probably have a factory instead static void init(); + + // public accessors + size_t nGlyphs() const; + // Does not bump reference; ownership is still layout + MinikinFont *getFont(int i) const; + unsigned int getGlyphId(int i) const; + float getX(int i) const; + float getY(int i) const; + + float getAdvance() const; + + // Get advances, copying into caller-provided buffer. The size of this + // buffer must match the length of the string (nchars arg to doLayout). + void getAdvances(float* advances); + + void getBounds(MinikinRect* rect); + private: // Find a face in the mFaces vector, or create a new entry int findFace(MinikinFont* face, MinikinPaint* paint); 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; + const FontCollection* mCollection; std::vector<MinikinFont *> mFaces; std::vector<hb_font_t *> mHbFonts; float mAdvance; + MinikinRect mBounds; }; } // namespace android diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h index c08e4fe..e84f5df 100644 --- a/include/minikin/MinikinFont.h +++ b/include/minikin/MinikinFont.h @@ -31,6 +31,29 @@ struct MinikinPaint { // todo: skew, stretch, hinting }; +struct MinikinRect { + float mLeft, mTop, mRight, mBottom; + bool isEmpty() const { + return mLeft == mRight || mTop == mBottom; + } + void set(const MinikinRect& r) { + mLeft = r.mLeft; + mTop = r.mTop; + mRight = r.mRight; + mBottom = r.mBottom; + } + void offset(float dx, float dy) { + mLeft += dx; + mTop += dy; + mRight += dx; + mBottom += dy; + } + void setEmpty() { + mLeft = mTop = mRight = mBottom = 0; + } + void join(const MinikinRect& r); +}; + class MinikinFontFreeType; class MinikinFont { @@ -38,6 +61,8 @@ public: void Ref() { mRefcount_++; } void Unref() { if (--mRefcount_ == 0) { delete this; } } + MinikinFont() : mRefcount_(1) { } + virtual ~MinikinFont() { }; virtual bool GetGlyph(uint32_t codepoint, uint32_t *glyph) const = 0; @@ -45,6 +70,9 @@ public: virtual float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint &paint) const = 0; + virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id, + const MinikinPaint &paint) const = 0; + // If buf is NULL, just update size virtual bool GetTable(uint32_t tag, uint8_t *buf, size_t *size) = 0; diff --git a/libs/minikin/Android.mk b/libs/minikin/Android.mk index baac98c..5e13495 100644 --- a/libs/minikin/Android.mk +++ b/libs/minikin/Android.mk @@ -32,13 +32,15 @@ LOCAL_MODULE := libminikin LOCAL_C_INCLUDES += \ external/harfbuzz_ng/src \ external/freetype/include \ + external/icu4c/common \ frameworks/minikin/include LOCAL_SHARED_LIBRARIES := \ libharfbuzz_ng \ libft2 \ + liblog \ libpng \ libz \ libstlport -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_SHARED_LIBRARY) diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 702bd20..aa37825 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -14,9 +14,10 @@ * limitations under the License. */ -#ifdef VERBOSE_DEBUG -#include <stdio.h> // for debugging - remove -#endif +// #define VERBOSE_DEBUG + +#define LOG_TAG "Minikin" +#include <cutils/log.h> #include <minikin/CmapCoverage.h> #include <minikin/FontCollection.h> @@ -35,7 +36,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) : vector<uint32_t> lastChar; size_t nTypefaces = typefaces.size(); #ifdef VERBOSE_DEBUG - printf("nTypefaces = %d\n", nTypefaces); + ALOGD("nTypefaces = %d\n", nTypefaces); #endif const FontStyle defaultStyle; for (size_t i = 0; i < nTypefaces; i++) { @@ -47,7 +48,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) : instance->mCoverage = new SparseBitSet; MinikinFont* typeface = family->getClosestMatch(defaultStyle); #ifdef VERBOSE_DEBUG - printf("closest match = %x, family size = %d\n", typeface, family->getNumFonts()); + ALOGD("closest match = %p, family size = %d\n", typeface, family->getNumFonts()); #endif const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p'); size_t cmapSize = 0; @@ -56,7 +57,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) : ok = typeface->GetTable(cmapTag, cmapData.get(), &cmapSize); CmapCoverage::getCoverage(*instance->mCoverage, cmapData.get(), cmapSize); #ifdef VERBOSE_DEBUG - printf("font coverage length=%d, first ch=%x\n", instance->mCoverage->length(), + ALOGD("font coverage length=%d, first ch=%x\n", instance->mCoverage->length(), instance->mCoverage->nextSetBit(0)); #endif mMaxChar = max(mMaxChar, instance->mCoverage->length()); @@ -70,7 +71,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) : mRanges.push_back(dummy); Range* range = &mRanges.back(); #ifdef VERBOSE_DEBUG - printf("i=%d: range start = %d\n", i, offset); + ALOGD("i=%d: range start = %d\n", i, offset); #endif range->start = offset; for (size_t j = 0; j < nTypefaces; j++) { @@ -80,7 +81,7 @@ FontCollection::FontCollection(const vector<FontFamily*>& typefaces) : offset++; uint32_t nextChar = instance->mCoverage->nextSetBit((i + 1) << kLogCharsPerPage); #ifdef VERBOSE_DEBUG - printf("nextChar = %d (j = %d)\n", nextChar, j); + ALOGD("nextChar = %d (j = %d)\n", nextChar, j); #endif lastChar[j] = nextChar; } @@ -102,7 +103,7 @@ const FontFamily* FontCollection::getFamilyForChar(uint32_t ch) const { } const Range& range = mRanges[ch >> kLogCharsPerPage]; #ifdef VERBOSE_DEBUG - printf("querying range %d:%d\n", range.start, range.end); + ALOGD("querying range %d:%d\n", range.start, range.end); #endif for (size_t i = range.start; i < range.end; i++) { const FontInstance* instance = mInstanceVec[i]; diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index 558fd77..16031ce 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -51,7 +51,6 @@ bool FontFamily::addFont(MinikinFont* typeface) { void FontFamily::addFont(MinikinFont* typeface, FontStyle style) { mFonts.push_back(Font(typeface, style)); - ALOGD("added font, mFonts.size() = %d", mFonts.size()); } // Compute a matching metric between two styles - 0 is an exact match diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp index d4e09c5..ffaa451 100644 --- a/libs/minikin/Layout.cpp +++ b/libs/minikin/Layout.cpp @@ -14,12 +14,19 @@ * limitations under the License. */ +#define LOG_TAG "Minikin" +#include <cutils/log.h> + #include <string> #include <vector> #include <fstream> #include <iostream> // for debugging #include <stdio.h> // ditto +#include <hb-icu.h> + +#include <utils/Mutex.h> + #include <minikin/MinikinFontFreeType.h> #include <minikin/Layout.h> @@ -31,6 +38,8 @@ namespace android { // TODO: globals are not cool, move to a factory-ish object hb_buffer_t* buffer = 0; +Mutex gLock; + Bitmap::Bitmap(int width, int height) : width(width), height(height) { buf = new uint8_t[width * height](); } @@ -69,11 +78,23 @@ void Bitmap::drawGlyph(const GlyphBitmap& bitmap, int x, int y) { } } +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); + } +} + +// TODO: the actual initialization is deferred, maybe make this explicit void Layout::init() { - buffer = hb_buffer_create(); } void Layout::setFontCollection(const FontCollection *collection) { + ALOGD("setFontCollection(%p)", collection); mCollection = collection; } @@ -193,8 +214,76 @@ static FontStyle styleFromCss(const CssProperties &props) { return FontStyle(weight, italic); } +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) -= 2; + return ~0u; + } else { + (*iter)--; + return ~0u; + } + } 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; +} + // TODO: API should probably take context void Layout::doLayout(const uint16_t* buf, size_t nchars) { + AutoMutex _l(gLock); + if (buffer == 0) { + buffer = hb_buffer_create(); + } FT_Error error; vector<FontCollection::Run> items; @@ -208,6 +297,9 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) { mGlyphs.clear(); mFaces.clear(); mHbFonts.clear(); + mBounds.setEmpty(); + mAdvances.clear(); + mAdvances.resize(nchars, 0); float x = 0; float y = 0; for (size_t run_ix = 0; run_ix < items.size(); run_ix++) { @@ -215,6 +307,10 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) { int font_ix = findFace(run.font, &paint); paint.font = mFaces[font_ix]; hb_font_t *hbFont = mHbFonts[font_ix]; + if (paint.font == NULL) { + // TODO: should log what went wrong + continue; + } #ifdef VERBOSE std::cout << "Run " << run_ix << ", font " << font_ix << " [" << run.start << ":" << run.end << "]" << std::endl; @@ -222,24 +318,38 @@ void Layout::doLayout(const uint16_t* buf, size_t nchars) { hb_font_set_ppem(hbFont, size, size); hb_font_set_scale(hbFont, HBFloatToFixed(size), HBFloatToFixed(size)); - hb_buffer_reset(buffer); - hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); - hb_buffer_add_utf16(buffer, buf, nchars, run.start, run.end - run.start); - 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); - 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 - hb_codepoint_t glyph_ix = info[i].codepoint; - float xoff = HBFixedToFloat(positions[i].x_offset); - float yoff = HBFixedToFloat(positions[i].y_offset); - LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff}; - mGlyphs.push_back(glyph); - x += HBFixedToFloat(positions[i].x_advance); + int srunend; + for (int srunstart = run.start; srunstart < run.end; srunstart = srunend) { + srunend = srunstart; + hb_script_t script = getScriptRun(buf, run.end, &srunend); + + hb_buffer_reset(buffer); + hb_buffer_set_script(buffer, script); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + hb_buffer_add_utf16(buffer, buf, nchars, srunstart, 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); + 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 + hb_codepoint_t glyph_ix = info[i].codepoint; + float xoff = HBFixedToFloat(positions[i].x_offset); + float yoff = HBFixedToFloat(positions[i].y_offset); + LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff}; + mGlyphs.push_back(glyph); + float xAdvance = HBFixedToFloat(positions[i].x_advance); + MinikinRect glyphBounds; + paint.font->GetBounds(&glyphBounds, glyph_ix, paint); + glyphBounds.offset(x + xoff, y + yoff); + mBounds.join(glyphBounds); + size_t cluster = info[i].cluster; + mAdvances[cluster] += xAdvance; + x += xAdvance; + } } } mAdvance = x; @@ -275,8 +385,40 @@ void Layout::setProperties(string css) { mProps.parse(css); } +size_t Layout::nGlyphs() const { + return mGlyphs.size(); +} + +MinikinFont *Layout::getFont(int i) const { + const LayoutGlyph& glyph = mGlyphs[i]; + return mFaces[glyph.font_ix]; +} + +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); +} + } // namespace android diff --git a/sample/Android.mk b/sample/Android.mk index 939aca4..a19019a 100644 --- a/sample/Android.mk +++ b/sample/Android.mk @@ -36,10 +36,40 @@ LOCAL_SHARED_LIBRARIES += \ libicuuc \ libft2 \ libpng \ - libz - -LOCAL_STATIC_LIBRARIES += libminikin + libz \ + libminikin LOCAL_MODULE:= minikin_example include $(BUILD_EXECUTABLE) + + +include $(CLEAR_VARS) +include external/stlport/libstlport.mk + +LOCAL_MODULE_TAG := tests + +LOCAL_C_INCLUDES += \ + external/harfbuzz_ng/src \ + external/freetype/include \ + external/icu4c/common \ + frameworks/minikin/include \ + external/skia/src/core + +LOCAL_SRC_FILES:= example_skia.cpp \ + MinikinSkia.cpp + +LOCAL_SHARED_LIBRARIES += \ + libutils \ + liblog \ + libcutils \ + libstlport \ + libharfbuzz_ng \ + libicuuc \ + libskia \ + libminikin \ + libft2 + +LOCAL_MODULE:= minikin_skia_example + +include $(BUILD_EXECUTABLE) diff --git a/sample/MinikinSkia.cpp b/sample/MinikinSkia.cpp new file mode 100644 index 0000000..d67e59f --- /dev/null +++ b/sample/MinikinSkia.cpp @@ -0,0 +1,64 @@ +#include <SkTypeface.h> +#include <SkPaint.h> + +#include <minikin/MinikinFont.h> +#include "MinikinSkia.h" + +namespace android { + +MinikinFontSkia::MinikinFontSkia(SkTypeface *typeface) : + mTypeface(typeface) { +} + +MinikinFontSkia::~MinikinFontSkia() { + SkSafeUnref(mTypeface); +} + +bool MinikinFontSkia::GetGlyph(uint32_t codepoint, uint32_t *glyph) const { + SkPaint paint; + paint.setTypeface(mTypeface); + paint.setTextEncoding(SkPaint::kUTF32_TextEncoding); + uint16_t glyph16; + paint.textToGlyphs(&codepoint, sizeof(codepoint), &glyph16); + *glyph = glyph16; + //printf("glyph for U+%04x = %d\n", codepoint, glyph16); + return !!glyph; +} + +float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, + const MinikinPaint &paint) const { + SkPaint skpaint; + skpaint.setTypeface(mTypeface); + skpaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + // TODO: set paint from Minikin + skpaint.setTextSize(100); + uint16_t glyph16 = glyph_id; + SkScalar skWidth; + SkRect skBounds; + skpaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, &skBounds); + // bounds? + //printf("advance for glyph %d = %f\n", glyph_id, SkScalarToFP(skWidth)); + return skWidth; +} + +bool MinikinFontSkia::GetTable(uint32_t tag, uint8_t *buf, size_t *size) { + if (buf == NULL) { + const size_t tableSize = mTypeface->getTableSize(tag); + *size = tableSize; + return tableSize != 0; + } else { + const size_t actualSize = mTypeface->getTableData(tag, 0, *size, buf); + *size = actualSize; + return actualSize != 0; + } +} + +SkTypeface *MinikinFontSkia::GetSkTypeface() { + return mTypeface; +} + +int32_t MinikinFontSkia::GetUniqueId() const { + return mTypeface->uniqueID(); +} + +} diff --git a/sample/MinikinSkia.h b/sample/MinikinSkia.h new file mode 100644 index 0000000..8286a4c --- /dev/null +++ b/sample/MinikinSkia.h @@ -0,0 +1,26 @@ +namespace android { + +class MinikinFontSkia : public MinikinFont { +public: + explicit MinikinFontSkia(SkTypeface *typeface); + + ~MinikinFontSkia(); + + bool GetGlyph(uint32_t codepoint, uint32_t *glyph) const; + + float GetHorizontalAdvance(uint32_t glyph_id, + const MinikinPaint &paint) const; + + // If buf is NULL, just update size + bool GetTable(uint32_t tag, uint8_t *buf, size_t *size); + + int32_t GetUniqueId() const; + + SkTypeface *GetSkTypeface(); + +private: + SkTypeface *mTypeface; + +}; + +} // namespace android
\ No newline at end of file diff --git a/sample/example_skia.cpp b/sample/example_skia.cpp new file mode 100644 index 0000000..ff13b5c --- /dev/null +++ b/sample/example_skia.cpp @@ -0,0 +1,152 @@ +/* + * 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. + */ + +// This is a test program that uses Minikin to layout and draw some text. +// At the moment, it just draws a string into /data/local/tmp/foo.pgm. + +#include <stdio.h> +#include <vector> +#include <fstream> + +#include <unicode/unistr.h> +#include <unicode/utf16.h> + +#include <minikin/MinikinFontFreeType.h> +#include <minikin/Layout.h> + +#include <SkCanvas.h> +#include <SkGraphics.h> +#include <SkImageEncoder.h> +#include <SkTypeface.h> +#include <SkPaint.h> + +#include "MinikinSkia.h" + +using std::vector; + +namespace android { + +FT_Library library; // TODO: this should not be a global + +FontCollection *makeFontCollection() { + vector<FontFamily *>typefaces; + const char *fns[] = { + "/system/fonts/Roboto-Regular.ttf", + "/system/fonts/Roboto-Italic.ttf", + "/system/fonts/Roboto-BoldItalic.ttf", + "/system/fonts/Roboto-Light.ttf", + "/system/fonts/Roboto-Thin.ttf", + "/system/fonts/Roboto-Bold.ttf", + "/system/fonts/Roboto-ThinItalic.ttf", + "/system/fonts/Roboto-LightItalic.ttf" + }; + + FontFamily *family = new FontFamily(); + FT_Face face; + FT_Error error; + for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) { + const char *fn = fns[i]; + SkTypeface *skFace = SkTypeface::CreateFromFile(fn); + MinikinFont *font = new MinikinFontSkia(skFace); + family->addFont(font); + } + typefaces.push_back(family); + +#if 1 + family = new FontFamily(); + const char *fn = "/system/fonts/DroidSansDevanagari-Regular.ttf"; + SkTypeface *skFace = SkTypeface::CreateFromFile(fn); + MinikinFont *font = new MinikinFontSkia(skFace); + family->addFont(font); + typefaces.push_back(family); +#endif + + return new FontCollection(typefaces); +} + +// Maybe move to MinikinSkia (esp. instead of opening GetSkTypeface publicly)? + +void drawToSkia(SkCanvas *canvas, SkPaint *paint, Layout *layout, float x, float y) { + size_t nGlyphs = layout->nGlyphs(); + uint16_t *glyphs = new uint16_t[nGlyphs]; + SkPoint *pos = new SkPoint[nGlyphs]; + SkTypeface *lastFace = NULL; + SkTypeface *skFace = NULL; + size_t start = 0; + + paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); + for (size_t i = 0; i < nGlyphs; i++) { + MinikinFontSkia *mfs = static_cast<MinikinFontSkia *>(layout->getFont(i)); + skFace = mfs->GetSkTypeface(); + glyphs[i] = layout->getGlyphId(i); + pos[i].fX = SkFloatToScalar(x + layout->getX(i)); + pos[i].fY = SkFloatToScalar(y + layout->getY(i)); + if (i > 0 && skFace != lastFace) { + paint->setTypeface(lastFace); + canvas->drawPosText(glyphs + start, (i - start) << 1, pos + start, *paint); + start = i; + } + lastFace = skFace; + } + paint->setTypeface(skFace); + canvas->drawPosText(glyphs + start, (nGlyphs - start) << 1, pos + start, *paint); + delete[] glyphs; + delete[] pos; +} + +int runMinikinTest() { + FT_Error error = FT_Init_FreeType(&library); + if (error) { + return -1; + } + Layout::init(); + + FontCollection *collection = makeFontCollection(); + Layout layout; + layout.setFontCollection(collection); + layout.setProperties("font-size: 32; font-weight: 700;"); + const char *text = "fine world \xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5\x87"; + icu::UnicodeString icuText = icu::UnicodeString::fromUTF8(text); + layout.doLayout(icuText.getBuffer(), icuText.length()); + layout.dump(); + + SkAutoGraphics ag; + + SkScalar width = 800; + SkScalar height = 600; + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap.allocPixels(); + SkCanvas canvas(bitmap); + SkPaint paint; + paint.setARGB(255, 0, 0, 128); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(2); + paint.setTextSize(100); + paint.setAntiAlias(true); + canvas.drawLine(10, 300, 10 + layout.getAdvance(), 300, paint); + paint.setStyle(SkPaint::kFill_Style); + drawToSkia(&canvas, &paint, &layout, 10, 300); + + SkImageEncoder::EncodeFile("/data/local/tmp/foo.png", bitmap, SkImageEncoder::kPNG_Type, 100); + return 0; +} + +} + +int main(int argc, const char** argv) { + return android::runMinikinTest(); +} |