/*
* Copyright 2009, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
#include "EmojiFactory.h"
#include "EmojiFont.h"
#include "SkCanvas.h"
#include "SkImageDecoder.h"
#include "SkPaint.h"
#include "SkTSearch.h"
#include "SkUtils.h"
#include "gmoji_pua_table.h"
#include <string.h>
namespace android {
// lazily allocate the factory
static EmojiFactory* get_emoji_factory() {
static EmojiFactory* gEmojiFactory;
if (NULL == gEmojiFactory) {
gEmojiFactory = EmojiFactory::GetAvailableImplementation();
// we may still be NULL, if there is no impl.
}
return gEmojiFactory;
}
#define UNINITIALIZED_ENCODE_SIZE 0 // our array is initialzed with 0s
#define NOT_AVAILABLE_ENCODE_SIZE -1 // never a legal length for data
struct EncodeDataRec {
SkBitmap* fBitmap;
const void* fData;
int fSize;
};
static EncodeDataRec gGmojiEncodeData[GMOJI_PUA_COUNT] = {};
/* Given a local index, return (initialized if needed) a rec containing the
encoded data and length. The bitmap field is initialized to 0, and is not
filled in by this routine per-se.
*/
static EncodeDataRec* get_encoderec(int index) {
if ((unsigned)index >= GMOJI_PUA_COUNT) {
SkDebugf("bad index passed to EncodeDataRec& get_encode_data %d\n",
index);
return NULL;
}
// lazily fill in the data
EncodeDataRec* rec = &gGmojiEncodeData[index];
if (NOT_AVAILABLE_ENCODE_SIZE == rec->fSize) {
return NULL;
}
if (UNINITIALIZED_ENCODE_SIZE == rec->fSize) {
EmojiFactory* fact = get_emoji_factory();
if (NULL == fact) {
return NULL;
}
int32_t pua = GMOJI_PUA_MIN + gGmojiPUA[index];
rec->fData = fact->GetImageBinaryFromAndroidPua(pua, &rec->fSize);
if (NULL == rec->fData) {
// flag this entry is not available, so we won't ask again
rec->fSize = NOT_AVAILABLE_ENCODE_SIZE;
return NULL;
}
}
return rec;
}
/* Return the bitmap associated with the local index, or NULL if none is
available. Note that this will try to cache the bitmap the first time it
creates it.
*/
static const SkBitmap* get_bitmap(int index) {
EncodeDataRec* rec = get_encoderec(index);
SkBitmap* bitmap = NULL;
if (rec) {
bitmap = rec->fBitmap;
if (NULL == bitmap) {
bitmap = new SkBitmap;
if (!SkImageDecoder::DecodeMemory(rec->fData, rec->fSize, bitmap)) {
delete bitmap;
// we failed, so mark us to not try again
rec->fSize = NOT_AVAILABLE_ENCODE_SIZE;
return NULL;
}
// cache the answer
rec->fBitmap = bitmap;
// todo: we never know if/when to let go of this cached bitmap
// tho, since the pixels are managed separately, and are purged,
// the "leak" may not be too important
}
}
return bitmap;
}
///////////////////////////////////////////////////////////////////////////////
bool EmojiFont::IsAvailable() {
return get_emoji_factory() != NULL;
}
const char *EmojiFont::GetShiftJisConverterName() {
EmojiFactory* fact = get_emoji_factory();
if (NULL != fact) {
if (strcmp(fact->Name(), "kddi") == 0) {
return "kddi-emoji";
} else if (strcmp(fact->Name(), "softbank") == 0) {
return "softbank-emoji";
}
}
// Until Eclair, we have used DoCoMo's Shift_JIS table.
return "docomo-emoji";
}
uint16_t EmojiFont::UnicharToGlyph(int32_t unichar) {
// do a quick range check before calling the search routine
if (unichar >= GMOJI_PUA_MIN && unichar <= GMOJI_PUA_MAX) {
// our table is stored relative to GMOJI_PUA_MIN to save space (16bits)
uint16_t relative = unichar - GMOJI_PUA_MIN;
int index = SkTSearch<uint16_t>(gGmojiPUA, GMOJI_PUA_COUNT, relative,
sizeof(uint16_t));
// a negative value means it was not found
if (index >= 0) {
return index + kGlyphBase;
}
// fall through to return 0
}
// not a supported emoji pua
return 0;
}
SkScalar EmojiFont::GetAdvanceWidth(uint16_t glyphID, const SkPaint& paint) {
if (glyphID < kGlyphBase) {
SkDebugf("-------- bad glyph passed to EmojiFont::GetAdvanceWidth %d\n",
glyphID);
return 0;
}
const SkBitmap* bitmap = get_bitmap(glyphID - kGlyphBase);
if (NULL == bitmap) {
return 0;
}
// assume that our advance width is always the pointsize
return paint.getTextSize();
}
/* This tells us to shift the emoji bounds down by 20% below the baseline,
to better align with the Kanji characters' placement in the line.
*/
static const SkScalar gBaselinePercentDrop = SkFloatToScalar(0.2f);
void EmojiFont::Draw(SkCanvas* canvas, uint16_t glyphID,
SkScalar x, SkScalar y, const SkPaint& paint) {
if (glyphID < kGlyphBase) {
SkDebugf("-------- bad glyph passed to EmojiFont::Draw %d\n", glyphID);
}
const SkBitmap* bitmap = get_bitmap(glyphID - kGlyphBase);
if (bitmap && !bitmap->empty()) {
SkRect dst;
SkScalar size = paint.getTextSize();
y += SkScalarMul(size, gBaselinePercentDrop);
dst.set(x, y - size, x + size, y);
canvas->drawBitmapRect(*bitmap, NULL, dst, &paint);
}
}
}