[android] Android implementation of local CJK glyph rendering

- Draws bold version of glyph if font stack contains string "bold"
 - Not hooked up to global configuration yet
This commit is contained in:
Chris Loer
2017-11-30 12:38:06 -08:00
committed by Chris Loer
parent f1f2ebb019
commit 97e45d5fda
5 changed files with 209 additions and 1 deletions

View File

@@ -0,0 +1,46 @@
package com.mapbox.mapboxsdk.text;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.support.annotation.WorkerThread;
/**
* LocalGlyphRasterizer is the Android-specific platform implementation used
* by the portable local_glyph_rasterizer.hpp
*/
public class LocalGlyphRasterizer {
/***
* Uses Android-native drawing code to rasterize a single glyph
* to a square @{link Bitmap} which can be returned to portable
* code for transformation into a Signed Distance Field glyph.
*
* @param fontFamily Font family string to pass to Typeface.create
* @param bold If true, use Typeface.BOLD option
* @param glyphID 16-bit Unicode BMP codepoint to draw
*
* @return Return a @{link Bitmap} to be displayed in the requested tile.
*/
@WorkerThread
protected static Bitmap drawGlyphBitmap(String fontFamily, boolean bold, char glyphID) {
/*
35x35px dimensions are hardwired to match local_glyph_rasterizer.cpp
These dimensions are large enough to draw a 24 point character in the middle
of the bitmap (y: 20) with some buffer around the edge
*/
Bitmap bitmap = Bitmap.createBitmap(35, 35, Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(24);
paint.setTypeface(Typeface.create(fontFamily, bold ? Typeface.BOLD : Typeface.NORMAL));
Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
canvas.drawText(String.valueOf(glyphID), 0, 20, paint);
return bitmap;
}
}

View File

@@ -32,11 +32,12 @@ macro(mbgl_platform_core)
PRIVATE platform/android/src/timer.cpp
# Misc
PRIVATE platform/android/src/text/local_glyph_rasterizer_jni.hpp
PRIVATE platform/android/src/text/local_glyph_rasterizer.cpp
PRIVATE platform/android/src/logging_android.cpp
PRIVATE platform/android/src/thread.cpp
PRIVATE platform/default/string_stdlib.cpp
PRIVATE platform/default/bidi.cpp
PRIVATE platform/default/local_glyph_rasterizer.cpp
PRIVATE platform/default/thread_local.cpp
PRIVATE platform/default/utf.cpp

View File

@@ -51,6 +51,7 @@
#include "style/light.hpp"
#include "snapshotter/map_snapshotter.hpp"
#include "snapshotter/map_snapshot.hpp"
#include "text/local_glyph_rasterizer_jni.hpp"
namespace mbgl {
namespace android {
@@ -188,6 +189,9 @@ void registerNatives(JavaVM *vm) {
// Snapshotter
MapSnapshotter::registerNative(env);
MapSnapshot::registerNative(env);
// text
LocalGlyphRasterizer::registerNative(env);
}
} // namespace android

View File

@@ -0,0 +1,126 @@
#include <mbgl/text/local_glyph_rasterizer.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/util/platform.hpp>
#include <jni/jni.hpp>
#include "../attach_env.hpp"
#include "../bitmap.hpp"
#include "local_glyph_rasterizer_jni.hpp"
/*
Android implementation of LocalGlyphRasterizer:
Draws CJK glyphs using locally available fonts.
Follows pattern of GL JS implementation in that:
- Only CJK glyphs are drawn locally (because we can guess their metrics effectively)
* Render size/metrics determined experimentally using Noto Sans
- Configuration is done at map creation time by setting a "font family"
* JS uses a CSS font-family, this uses android.graphics.Typeface
https://developer.android.com/reference/android/graphics/Typeface.html
- We use heuristics to extract a font-weight based on the incoming font stack
* JS tries to extract multiple weights, this implementation only looks for
"bold"
mbgl::LocalGlyphRasterizer is the portable interface
mbgl::LocalGlyphRasterizer::Impl stores platform-specific configuration data
mbgl::android::LocalGlyphRasterizer is the JNI wrapper
com.mapbox.mapboxsdk.text.LocalGlyphRasterizer is the Java implementation that
actually does the drawing
*/
namespace mbgl {
namespace android {
PremultipliedImage LocalGlyphRasterizer::drawGlyphBitmap(const std::string& fontFamily, const bool bold, const GlyphID glyphID) {
UniqueEnv env { AttachEnv() };
using Signature = jni::Object<Bitmap>(jni::String, jni::jboolean, jni::jchar);
auto method = javaClass.GetStaticMethod<Signature>(*env, "drawGlyphBitmap");
jni::String jniFontFamily = jni::Make<jni::String>(*env, fontFamily);
auto javaBitmap = javaClass.Call(*env,
method,
jniFontFamily,
static_cast<jni::jboolean>(bold),
static_cast<jni::jchar>(glyphID));
PremultipliedImage result = Bitmap::GetImage(*env, javaBitmap);
jni::DeleteLocalRef(*env, javaBitmap);
return result;
}
void LocalGlyphRasterizer::registerNative(jni::JNIEnv& env) {
javaClass = *jni::Class<LocalGlyphRasterizer>::Find(env).NewGlobalRef(env).release();
}
jni::Class<LocalGlyphRasterizer> LocalGlyphRasterizer::javaClass;
} // namespace android
class LocalGlyphRasterizer::Impl {
public:
Impl(const optional<std::string> fontFamily_)
: fontFamily(fontFamily_)
{}
bool isConfigured() const {
return bool(fontFamily);
}
PremultipliedImage drawGlyphBitmap(const FontStack& fontStack, GlyphID glyphID) {
bool bold = false;
for (auto font : fontStack) {
std::string lowercaseFont = platform::lowercase(font);
if (lowercaseFont.find("bold") != std::string::npos) {
bold = true;
}
}
return android::LocalGlyphRasterizer::drawGlyphBitmap(*fontFamily, bold, glyphID);
}
private:
optional<std::string> fontFamily;
};
LocalGlyphRasterizer::LocalGlyphRasterizer(const optional<std::string> fontFamily)
: impl(std::make_unique<Impl>(fontFamily))
{}
LocalGlyphRasterizer::~LocalGlyphRasterizer()
{}
bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) {
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isConfigured();
}
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) {
Glyph fixedMetrics;
if (!impl->isConfigured()) {
return fixedMetrics;
}
fixedMetrics.id = glyphID;
Size size(35, 35);
fixedMetrics.metrics.width = size.width;
fixedMetrics.metrics.height = size.height;
fixedMetrics.metrics.left = 3;
fixedMetrics.metrics.top = -10;
fixedMetrics.metrics.advance = 24;
PremultipliedImage rgbaBitmap = impl->drawGlyphBitmap(fontStack, glyphID);
// Copy alpha values from RGBA bitmap into the AlphaImage output
fixedMetrics.bitmap = AlphaImage(size);
for (uint32_t i = 0; i < size.width * size.height; i++) {
fixedMetrics.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
}
return fixedMetrics;
}
} // namespace mbgl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <mbgl/util/image.hpp>
#include <jni/jni.hpp>
/*
android::LocalGlyphRasterizer is the JNI wrapper of
com/mapbox/mapboxsdk/text/LocalGlyphRasterizer
mbgl::LocalGlyphRasterizer is the portable interface
Both implementations are in local_glyph_rasterizer.cpp
*/
namespace mbgl {
namespace android {
class LocalGlyphRasterizer {
public:
static PremultipliedImage drawGlyphBitmap(const std::string& fontFamily, const bool bold, const char16_t glyphID);
static constexpr auto Name() { return "com/mapbox/mapboxsdk/text/LocalGlyphRasterizer"; };
static jni::Class<LocalGlyphRasterizer> javaClass;
static void registerNative(jni::JNIEnv&);
};
} // namespace android
} // namespace mbgl