diff options
author | 2013-05-21 16:53:50 +0000 | |
---|---|---|
committer | 2013-05-21 16:53:50 +0000 | |
commit | bfae9d373ccc9cf47fd70757092962c7850fadf4 (patch) | |
tree | bb2dae453daf82743682a8323d273af418d1a145 /src/ports/SkFontConfigInterface_android.cpp | |
parent | 7768751b8bec9e50fcbdad67b82e1962e71c3eb9 (diff) |
Add a fontConfig interface for android.
The contents of this CL are based on the SkFontHost_android.cpp found
in the android repository.
BUG=
R=reed@google.com
Review URL: https://codereview.chromium.org/14731025
git-svn-id: http://skia.googlecode.com/svn/trunk@9219 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/ports/SkFontConfigInterface_android.cpp')
-rw-r--r-- | src/ports/SkFontConfigInterface_android.cpp | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/src/ports/SkFontConfigInterface_android.cpp b/src/ports/SkFontConfigInterface_android.cpp new file mode 100644 index 0000000000..850d902ba4 --- /dev/null +++ b/src/ports/SkFontConfigInterface_android.cpp @@ -0,0 +1,623 @@ + +/* + * Copyright 2013 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkFontConfigInterface.h" +#include "SkTypeface_android.h" + +#include "SkFontConfigParser_android.h" +#include "SkFontConfigTypeface.h" +#include "SkFontMgr.h" +#include "SkGlyphCache.h" +#include "SkPaint.h" +#include "SkPaintOptionsAndroid.h" +#include "SkString.h" +#include "SkStream.h" +#include "SkThread.h" +#include "SkTypefaceCache.h" +#include "SkTArray.h" +#include "SkTDict.h" +#include "SkTSearch.h" + +#include <stdio.h> +#include <string.h> + +#ifndef SK_DEBUG_FONTS + #define SK_DEBUG_FONTS 0 +#endif + +#if SK_DEBUG_FONTS + #define DEBUG_FONT(args) SkDebugf args +#else + #define DEBUG_FONT(args) +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// For test only. +static const char* gTestMainConfigFile = NULL; +static const char* gTestFallbackConfigFile = NULL; +static const char* gTestFontFilePrefix = NULL; + +/////////////////////////////////////////////////////////////////////////////// + +// used to record our notion of the pre-existing fonts +struct FontRec { + SkRefPtr<SkTypeface> fTypeface; + SkString fFileName; + SkTypeface::Style fStyle; + SkPaintOptionsAndroid fPaintOptions; + bool fIsFallbackFont; + bool fIsValid; +}; + +typedef int32_t FontRecID; +#define INVALID_FONT_REC_ID -1 + +struct FamilyRec { + FamilyRec() { + memset(fFontRecID, INVALID_FONT_REC_ID, sizeof(fFontRecID)); + } + + static const int FONT_STYLE_COUNT = 4; + FontRecID fFontRecID[FONT_STYLE_COUNT]; +}; + +typedef int32_t FamilyRecID; +#define INVALID_FAMILY_REC_ID -1 + +typedef SkTDArray<FontRecID> FallbackFontList; + +class SkFontConfigInterfaceAndroid : public SkFontConfigInterface { +public: + SkFontConfigInterfaceAndroid(SkTDArray<FontFamily*>& fontFamilies); + virtual ~SkFontConfigInterfaceAndroid(); + + virtual bool matchFamilyName(const char familyName[], + SkTypeface::Style requested, + FontIdentity* outFontIdentifier, + SkString* outFamilyName, + SkTypeface::Style* outStyle) SK_OVERRIDE; + virtual SkStream* openStream(const FontIdentity&) SK_OVERRIDE; + + // new APIs + virtual SkDataTable* getFamilyNames() SK_OVERRIDE; + virtual bool matchFamilySet(const char inFamilyName[], + SkString* outFamilyName, + SkTArray<FontIdentity>*) SK_OVERRIDE; + + /** + * Get the family name of the font in the default fallback font list that + * contains the specified chararacter. if no font is found, returns false. + */ + bool getFallbackFamilyNameForChar(SkUnichar uni, SkString* name); + /** + * + */ + SkTypeface* getTypefaceForChar(SkUnichar uni, SkTypeface::Style style, + SkPaintOptionsAndroid::FontVariant fontVariant); + SkTypeface* nextLogicalTypeface(SkFontID currFontID, SkFontID origFontID, + const SkPaintOptionsAndroid& options); + +private: + void addFallbackFont(FontRecID fontRecID); + FallbackFontList* findFallbackFontList(const SkLanguage& lang); + + SkTArray<FontRec> fFonts; + SkTArray<FamilyRec> fFontFamilies; + SkTDict<FamilyRecID> fFamilyNameDict; + FamilyRecID fDefaultFamilyRecID; + + // (SkLanguage)<->(fallback chain index) translation + SkTDict<FallbackFontList*> fFallbackFontDict; + FallbackFontList fDefaultFallbackList; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static SkFontConfigInterfaceAndroid* getSingletonInterface() { + SK_DECLARE_STATIC_MUTEX(gMutex); + static SkFontConfigInterfaceAndroid* gFontConfigInterface; + + SkAutoMutexAcquire ac(gMutex); + if (NULL == gFontConfigInterface) { + // load info from a configuration file that we can use to populate the + // system/fallback font structures + SkTDArray<FontFamily*> fontFamilies; + if (!gTestMainConfigFile) { + SkFontConfigParser::GetFontFamilies(fontFamilies); + } else { + SkFontConfigParser::GetTestFontFamilies(fontFamilies, gTestMainConfigFile, + gTestFallbackConfigFile); + } + + gFontConfigInterface = new SkFontConfigInterfaceAndroid(fontFamilies); + + // cleanup the data we received from the parser + fontFamilies.deleteAll(); + } + return gFontConfigInterface; +} + +SkFontConfigInterface* SkFontConfigInterface::GetSingletonDirectInterface() { + return getSingletonInterface(); +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool has_font(const SkTArray<FontRec>& array, const SkString& filename) { + for (int i = 0; i < array.count(); i++) { + if (array[i].fFileName == filename) { + return true; + } + } + return false; +} + +#ifndef SK_FONT_FILE_PREFIX + #define SK_FONT_FILE_PREFIX "/fonts/" +#endif + +static void get_path_for_sys_fonts(SkString* full, const char name[]) { + if (gTestFontFilePrefix) { + full->set(gTestFontFilePrefix); + } else { + full->set(getenv("ANDROID_ROOT")); + full->append(SK_FONT_FILE_PREFIX); + } + full->append(name); +} + +static void insert_into_name_dict(SkTDict<FamilyRecID>& familyNameDict, + const char* name, FamilyRecID familyRecID) { + SkAutoAsciiToLC tolc(name); + familyNameDict.set(tolc.lc(), familyRecID); +} + +// Defined in SkFontHost_FreeType.cpp +bool find_name_and_attributes(SkStream* stream, SkString* name, + SkTypeface::Style* style, bool* isFixedWidth); + +/////////////////////////////////////////////////////////////////////////////// + +SkFontConfigInterfaceAndroid::SkFontConfigInterfaceAndroid(SkTDArray<FontFamily*>& fontFamilies) : + fFonts(fontFamilies.count()), + fFontFamilies(fontFamilies.count() / FamilyRec::FONT_STYLE_COUNT), + fFamilyNameDict(1024), + fDefaultFamilyRecID(INVALID_FAMILY_REC_ID), + fFallbackFontDict(128) { + + for (int i = 0; i < fontFamilies.count(); ++i) { + FontFamily* family = fontFamilies[i]; + + // defer initializing the familyRec until we can be sure that at least + // one of it's children contains a valid font file + FamilyRec* familyRec = NULL; + FamilyRecID familyRecID = INVALID_FAMILY_REC_ID; + + for (int j = 0; j < family->fFontFiles.count(); ++j) { + SkString filename; + get_path_for_sys_fonts(&filename, family->fFontFiles[j]->fFileName); + + if (has_font(fFonts, filename)) { + SkDebugf("---- system font and fallback font files specify a duplicate " + "font %s, skipping the second occurrence", filename.c_str()); + continue; + } + + FontRec& fontRec = fFonts.push_back(); + fontRec.fFileName = filename; + fontRec.fStyle = SkTypeface::kNormal; + fontRec.fPaintOptions = family->fFontFiles[j]->fPaintOptions; + fontRec.fIsFallbackFont = family->fIsFallbackFont; + fontRec.fIsValid = false; + + const FontRecID fontRecID = fFonts.count() - 1; + + SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(filename.c_str())); + if (stream.get() != NULL) { + bool isFixedWidth; + SkString name; + fontRec.fIsValid = find_name_and_attributes(stream.get(), &name, + &fontRec.fStyle, &isFixedWidth); + } else { + if (!fontRec.fIsFallbackFont) { + SkDebugf("---- failed to open <%s> as a font\n", filename.c_str()); + } + } + + if (fontRec.fIsValid) { + DEBUG_FONT(("---- SystemFonts[%d][%d] fallback=%d file=%s", + i, fFonts.count() - 1, fontRec.fIsFallbackFont, filename.c_str())); + } else { + DEBUG_FONT(("---- SystemFonts[%d][%d] fallback=%d file=%s (INVALID)", + i, fFonts.count() - 1, fontRec.fIsFallbackFont, filename.c_str())); + continue; + } + + // create a familyRec now that we know that at least one font in + // the family is valid + if (familyRec == NULL) { + familyRec = &fFontFamilies.push_back(); + familyRecID = fFontFamilies.count() - 1; + } + + // add this font to the current familyRec + if (INVALID_FONT_REC_ID != familyRec->fFontRecID[fontRec.fStyle]) { + DEBUG_FONT(("Overwriting familyRec for style[%d] old,new:(%d,%d)", + fontRec.fStyle, familyRec->fFontRecID[fontRec.fStyle], + fontRecID)); + } + familyRec->fFontRecID[fontRec.fStyle] = fontRecID; + + // if this is a fallback font then add it to the appropriate fallback chains + if (fontRec.fIsFallbackFont) { + addFallbackFont(fontRecID); + } + + // add the fallback file name to the name dictionary. This is needed + // by getFallbackFamilyNameForChar() so that fallback families can be + // requested by the filenames of the fonts they contain. + if (family->fIsFallbackFont && familyRec) { + insert_into_name_dict(fFamilyNameDict, fontRec.fFileName.c_str(), familyRecID); + } + } + + // add the names that map to this family to the dictionary for easy lookup + if (familyRec && !family->fIsFallbackFont) { + SkTDArray<const char*> names = family->fNames; + if (names.isEmpty()) { + SkDEBUGFAIL("ERROR: non-fallback font with no name"); + continue; + } + + for (int i = 0; i < names.count(); i++) { + insert_into_name_dict(fFamilyNameDict, names[i], familyRecID); + } + } + + } + + DEBUG_FONT(("---- We have %d system fonts", fFonts.count())); + + if (fFontFamilies.count() > 0) { + fDefaultFamilyRecID = 0; + } + + // scans the default fallback font chain, adding every entry to every other + // fallback font chain to which it does not belong. this results in every + // language-specific fallback font chain having all of its fallback fonts at + // the front of the chain, and everything else at the end. + FallbackFontList* fallbackList; + SkTDict<FallbackFontList*>::Iter iter(fFallbackFontDict); + const char* fallbackLang = iter.next(&fallbackList); + while(fallbackLang != NULL) { + for (int i = 0; i < fDefaultFallbackList.count(); i++) { + FontRecID fontRecID = fDefaultFallbackList[i]; + const SkString& fontLang = fFonts[fontRecID].fPaintOptions.getLanguage().getTag(); + if (strcmp(fallbackLang, fontLang.c_str()) != 0) { + fallbackList->push(fontRecID); + } + } + // move to the next fallback list in the dictionary + fallbackLang = iter.next(&fallbackList); + } +} + +SkFontConfigInterfaceAndroid::~SkFontConfigInterfaceAndroid() { + // iterate through and cleanup fFallbackFontDict + SkTDict<FallbackFontList*>::Iter iter(fFallbackFontDict); + FallbackFontList* fallbackList; + while(iter.next(&fallbackList) != NULL) { + SkDELETE(fallbackList); + } +} + +void SkFontConfigInterfaceAndroid::addFallbackFont(FontRecID fontRecID) { + SkASSERT(fontRecID < fFonts.count()); + const FontRec& fontRec = fFonts[fontRecID]; + SkASSERT(fontRec.fIsFallbackFont); + + // add to the default fallback list + fDefaultFallbackList.push(fontRecID); + + // stop here if it is the default language tag + const SkString& languageTag = fontRec.fPaintOptions.getLanguage().getTag(); + if (languageTag.isEmpty()) { + return; + } + + // add to the appropriate language's custom fallback list + FallbackFontList* customList = NULL; + if (!fFallbackFontDict.find(languageTag.c_str(), &customList)) { + DEBUG_FONT(("---- Created fallback list for \"%s\"", languageTag.c_str())); + customList = SkNEW(FallbackFontList); + fFallbackFontDict.set(languageTag.c_str(), customList); + } + SkASSERT(customList != NULL); + customList->push(fontRecID); +} + + +static FontRecID find_best_style(const FamilyRec& family, SkTypeface::Style style) { + + const FontRecID* fontRecIDs = family.fFontRecID; + + if (fontRecIDs[style] != INVALID_FONT_REC_ID) { // exact match + return fontRecIDs[style]; + } + // look for a matching bold + style = (SkTypeface::Style)(style ^ SkTypeface::kItalic); + if (fontRecIDs[style] != INVALID_FONT_REC_ID) { + return fontRecIDs[style]; + } + // look for the plain + if (fontRecIDs[SkTypeface::kNormal] != INVALID_FONT_REC_ID) { + return fontRecIDs[SkTypeface::kNormal]; + } + // look for anything + for (int i = 0; i < FamilyRec::FONT_STYLE_COUNT; i++) { + if (fontRecIDs[i] != INVALID_FONT_REC_ID) { + return fontRecIDs[i]; + } + } + // should never get here, since the fontRecID list should not be empty + SkDEBUGFAIL("No valid fonts exist for this family"); + return -1; +} + +bool SkFontConfigInterfaceAndroid::matchFamilyName(const char familyName[], + SkTypeface::Style style, + FontIdentity* outFontIdentifier, + SkString* outFamilyName, + SkTypeface::Style* outStyle) SK_OVERRIDE { + // clip to legal style bits + style = (SkTypeface::Style)(style & SkTypeface::kBoldItalic); + + bool exactNameMatch = false; + + FamilyRecID familyRecID = INVALID_FAMILY_REC_ID; + if (NULL != familyName) { + if (fFamilyNameDict.find(familyName, &familyRecID)) { + exactNameMatch = true; + } + } else { + familyRecID = fDefaultFamilyRecID; + + } + + if (INVALID_FAMILY_REC_ID == familyRecID) { + //TODO this ensures that we always return something + familyRecID = fDefaultFamilyRecID; + //return false; + } + + FontRecID fontRecID = find_best_style(fFontFamilies[familyRecID], style); + FontRec& fontRec = fFonts[fontRecID]; + + if (NULL != outFontIdentifier) { + outFontIdentifier->fID = fontRecID; + outFontIdentifier->fTTCIndex = 0; + outFontIdentifier->fString.set(fontRec.fFileName); +// outFontIdentifier->fStyle = fontRec.fStyle; + } + + if (NULL != outFamilyName) { + if (exactNameMatch) { + outFamilyName->set(familyName); + } else { + // find familyName from list of names + const char* familyName = NULL; + bool found = fFamilyNameDict.findKey(familyRecID, &familyName); + SkASSERT(found && familyName); + outFamilyName->set(familyName); + } + } + + if (NULL != outStyle) { + *outStyle = fontRec.fStyle; + } + + return true; +} + +SkStream* SkFontConfigInterfaceAndroid::openStream(const FontIdentity& identity) SK_OVERRIDE { + return SkStream::NewFromFile(identity.fString.c_str()); +} + +SkDataTable* SkFontConfigInterfaceAndroid::getFamilyNames() SK_OVERRIDE { + SkTDArray<const char*> names; + SkTDArray<size_t> sizes; + + SkTDict<FamilyRecID>::Iter iter(fFamilyNameDict); + const char* familyName = iter.next(NULL); + while(familyName != NULL) { + *names.append() = familyName; + *sizes.append() = strlen(familyName) + 1; + + // move to the next familyName in the dictionary + familyName = iter.next(NULL); + } + + return SkDataTable::NewCopyArrays((const void*const*)names.begin(), + sizes.begin(), names.count()); +} + +bool SkFontConfigInterfaceAndroid::matchFamilySet(const char inFamilyName[], + SkString* outFamilyName, + SkTArray<FontIdentity>*) SK_OVERRIDE { + return false; +} + +static SkTypeface* get_typeface_for_rec(FontRec& fontRec) { + SkTypeface* face = fontRec.fTypeface.get(); + if (!face) { + // TODO look for it in the typeface cache + + // if it is not in the cache then create it + face = SkTypeface::CreateFromFile(fontRec.fFileName.c_str()); + + // store the result for subsequent lookups + fontRec.fTypeface = face; + } + SkASSERT(face); + return face; +} + +bool SkFontConfigInterfaceAndroid::getFallbackFamilyNameForChar(SkUnichar uni, SkString* name) { + for (int i = 0; i < fDefaultFallbackList.count(); i++) { + FontRecID fontRecID = fDefaultFallbackList[i]; + SkTypeface* face = get_typeface_for_rec(fFonts[fontRecID]); + + SkPaint paint; + paint.setTypeface(face); + paint.setTextEncoding(SkPaint::kUTF32_TextEncoding); + + uint16_t glyphID; + paint.textToGlyphs(&uni, sizeof(uni), &glyphID); + if (glyphID != 0) { + name->set(fFonts[fontRecID].fFileName); + return true; + } + } + return false; +} + +SkTypeface* SkFontConfigInterfaceAndroid::getTypefaceForChar(SkUnichar uni, + SkTypeface::Style style, + SkPaintOptionsAndroid::FontVariant fontVariant) { + FontRecID fontRecID = find_best_style(fFontFamilies[fDefaultFamilyRecID], style); + SkTypeface* face = get_typeface_for_rec(fFonts[fontRecID]); + + SkPaintOptionsAndroid paintOptions; + paintOptions.setFontVariant(fontVariant); + paintOptions.setUseFontFallbacks(true); + + SkPaint paint; + paint.setTypeface(face); + paint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + paint.setPaintOptionsAndroid(paintOptions); + + SkAutoGlyphCache autoCache(paint, NULL, NULL); + SkGlyphCache* cache = autoCache.getCache(); + + SkScalerContext* ctx = cache->getScalerContext(); + if (ctx) { + SkFontID fontID = ctx->findTypefaceIdForChar(uni); + return SkTypefaceCache::FindByID(fontID); + } + return NULL; +} + +FallbackFontList* SkFontConfigInterfaceAndroid::findFallbackFontList(const SkLanguage& lang) { + const SkString& langTag = lang.getTag(); + if (langTag.isEmpty()) { + return &fDefaultFallbackList; + } + + FallbackFontList* fallbackFontList; + if (fFallbackFontDict.find(langTag.c_str(), langTag.size(), &fallbackFontList)) { + return fallbackFontList; + } + + // attempt a recursive fuzzy match + // TODO we could cache the intermediate parent so that we don't have to do + // the recursion again. + SkLanguage parent = lang.getParent(); + return findFallbackFontList(parent); +} + +SkTypeface* SkFontConfigInterfaceAndroid::nextLogicalTypeface(SkFontID currFontID, + SkFontID origFontID, + const SkPaintOptionsAndroid& opts) { + // Skia does not support font fallback by default. This enables clients such + // as WebKit to customize their font selection. In any case, clients can use + // GetFallbackFamilyNameForChar() to get the fallback font for individual + // characters. + if (!opts.isUsingFontFallbacks()) { + return NULL; + } + + const SkTypeface* origTypeface = SkTypefaceCache::FindByID(origFontID); + const SkTypeface* currTypeface = SkTypefaceCache::FindByID(currFontID); + + FallbackFontList* currentFallbackList = findFallbackFontList(opts.getLanguage()); + SkASSERT(currentFallbackList); + + SkASSERT(origTypeface != 0); + SkASSERT(currTypeface != 0); + + // we must convert currTypeface into a FontRecID + FontRecID currFontRecID = ((FontConfigTypeface*)currTypeface)->getIdentity().fID; + + // TODO lookup the index next font in the chain + int currFallbackFontIndex = currentFallbackList->find(currFontRecID); + int nextFallbackFontIndex = currFallbackFontIndex + 1; + SkASSERT(-1 == nextFallbackFontIndex); + + if(nextFallbackFontIndex >= currentFallbackList->count() && -1 == currFallbackFontIndex) { + return NULL; + } + + // If a rec object is set to prefer "kDefault_Variant" it means they have no preference + // In this case, we set the value to "kCompact_Variant" + SkPaintOptionsAndroid::FontVariant variant = opts.getFontVariant(); + if (variant == SkPaintOptionsAndroid::kDefault_Variant) { + variant = SkPaintOptionsAndroid::kCompact_Variant; + } + + int32_t acceptedVariants = SkPaintOptionsAndroid::kDefault_Variant | variant; + + SkTypeface* nextLogicalTypeface = 0; + while (nextFallbackFontIndex < currentFallbackList->count()) { + FontRecID fontRecID = currentFallbackList->getAt(nextFallbackFontIndex); + if (fFonts[fontRecID].fPaintOptions.getFontVariant() & acceptedVariants) { + nextLogicalTypeface = get_typeface_for_rec(fFonts[fontRecID]); + break; + } + nextFallbackFontIndex++; + } + + DEBUG_FONT(("---- nextLogicalFont: currFontID=%d, origFontID=%d, currRecID=%d, " + "lang=%s, variant=%d, nextFallbackIndex=%d => nextLogicalTypeface=%d", + currFontID, origFontID, currFontRecID, opts.getLanguage().getTag().c_str(), + variant, nextFallbackFontIndex, + (nextLogicalTypeface) ? nextLogicalTypeface->uniqueID() : 0)); + return SkSafeRef(nextLogicalTypeface); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkGetFallbackFamilyNameForChar(SkUnichar uni, SkString* name) { + SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface(); + return fontConfig->getFallbackFamilyNameForChar(uni, name); +} + +void SkUseTestFontConfigFile(const char* mainconf, const char* fallbackconf, + const char* fontsdir) { + gTestMainConfigFile = mainconf; + gTestFallbackConfigFile = fallbackconf; + gTestFontFilePrefix = fontsdir; + SkASSERT(gTestMainConfigFile); + SkASSERT(gTestFallbackConfigFile); + SkASSERT(gTestFontFilePrefix); + SkDEBUGF(("Use Test Config File Main %s, Fallback %s, Font Dir %s", + gTestMainConfigFile, gTestFallbackConfigFile, gTestFontFilePrefix)); +} + +SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID, SkFontID origFontID, + const SkPaintOptionsAndroid& options) { + SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface(); + return fontConfig->nextLogicalTypeface(currFontID, origFontID, options); + +} + +/////////////////////////////////////////////////////////////////////////////// + +SkFontMgr* SkFontMgr::Factory() { + return NULL; +} |