提交 ecc2d34a 编写于 作者: R Raph Levien

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
上级 5adafc0d
......@@ -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
......
......@@ -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;
......
......@@ -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)
......@@ -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];
......
......@@ -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
......
......@@ -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
......@@ -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)
#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();
}
}
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
/*
* 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();
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册