mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Typeface: Enable advanced colour glyph rendering on Android
Android 15+ removed the 'legacy' png-based emoji font. Modern Android versions may include only a COLR-v1-based font, which JUCE cannot render itself. As a workaround, on Android, we use a Canvas object to render each emoji glyph into a bitmap, and then render that bitmap in the same way as a legacy png-based glyph. This won't look as crisp as rendering COLRv1 glyphs directly, especially at larger sizes, but this is a sufficient stop-gap for the time being.
This commit is contained in:
parent
70a2dd7e15
commit
a3d64c7784
5 changed files with 243 additions and 46 deletions
|
|
@ -540,6 +540,7 @@ DECLARE_JNI_CLASS (AndroidPackageManager, "android/content/pm/PackageManager")
|
|||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(I)V") \
|
||||
METHOD (defaultConstructor, "<init>", "()V") \
|
||||
METHOD (setColor, "setColor", "(I)V") \
|
||||
METHOD (setAlpha, "setAlpha", "(I)V") \
|
||||
METHOD (setTypeface, "setTypeface", "(Landroid/graphics/Typeface;)Landroid/graphics/Typeface;") \
|
||||
|
|
@ -565,6 +566,12 @@ DECLARE_JNI_CLASS (AndroidPaint, "android/graphics/Paint")
|
|||
DECLARE_JNI_CLASS (AndroidCanvas, "android/graphics/Canvas")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (drawGlyphs, "drawGlyphs", "([II[FIILandroid/graphics/fonts/Font;Landroid/graphics/Paint;)V")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidCanvas31, "android/graphics/Canvas", 31)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (getActivity, "getActivity", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \
|
||||
STATICMETHOD (getBroadcast, "getBroadcast", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \
|
||||
|
|
@ -706,8 +713,10 @@ DECLARE_JNI_CLASS (JavaBoolean, "java/lang/Boolean")
|
|||
METHOD (remaining, "remaining", "()I") \
|
||||
METHOD (hasArray, "hasArray", "()Z") \
|
||||
METHOD (array, "array", "()[B") \
|
||||
METHOD (put, "put", "([B)Ljava/nio/ByteBuffer;") \
|
||||
METHOD (setOrder, "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;") \
|
||||
STATICMETHOD (wrap, "wrap", "([B)Ljava/nio/ByteBuffer;")
|
||||
STATICMETHOD (wrap, "wrap", "([B)Ljava/nio/ByteBuffer;") \
|
||||
STATICMETHOD (allocateDirect, "allocateDirect", "(I)Ljava/nio/ByteBuffer;") \
|
||||
|
||||
DECLARE_JNI_CLASS (JavaByteBuffer, "java/nio/ByteBuffer")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
|
|
|||
|
|
@ -175,11 +175,21 @@ using HbFace = std::unique_ptr<hb_face_t, FunctionPointerDestructor<hb_face_de
|
|||
using HbBuffer = std::unique_ptr<hb_buffer_t, FunctionPointerDestructor<hb_buffer_destroy>>;
|
||||
using HbBlob = std::unique_ptr<hb_blob_t, FunctionPointerDestructor<hb_blob_destroy>>;
|
||||
|
||||
struct TypefaceFallbackColourGlyphSupport
|
||||
{
|
||||
virtual ~TypefaceFallbackColourGlyphSupport() = default;
|
||||
virtual std::vector<GlyphLayer> getFallbackColourGlyphLayers (int, const AffineTransform&) const = 0;
|
||||
};
|
||||
|
||||
class Typeface::Native
|
||||
{
|
||||
public:
|
||||
Native (hb_font_t* fontRef, TypefaceAscentDescent nonPortableMetricsIn)
|
||||
: font (fontRef), nonPortable (nonPortableMetricsIn)
|
||||
Native (hb_font_t* fontRef,
|
||||
TypefaceAscentDescent nonPortableMetricsIn,
|
||||
const TypefaceFallbackColourGlyphSupport* colourGlyphSupportIn = {})
|
||||
: font (fontRef),
|
||||
nonPortable (nonPortableMetricsIn),
|
||||
colourGlyphSupport (colourGlyphSupportIn)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +223,15 @@ public:
|
|||
return subFont;
|
||||
}
|
||||
|
||||
std::vector<GlyphLayer> getFallbackColourGlyphLayers (int glyph,
|
||||
const AffineTransform& transform) const
|
||||
{
|
||||
if (colourGlyphSupport != nullptr)
|
||||
return colourGlyphSupport->getFallbackColourGlyphLayers (glyph, transform);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
static TypefaceAscentDescent findPortableMetrics (hb_font_t* f, TypefaceAscentDescent fallback)
|
||||
{
|
||||
|
|
@ -235,6 +254,7 @@ private:
|
|||
|
||||
TypefaceAscentDescent nonPortable;
|
||||
TypefaceAscentDescent portable = findPortableMetrics (font, nonPortable);
|
||||
const TypefaceFallbackColourGlyphSupport* colourGlyphSupport = nullptr;
|
||||
};
|
||||
|
||||
struct FontStyleHelpers
|
||||
|
|
@ -524,10 +544,13 @@ static std::vector<GlyphLayer> getBitmapLayer (const Typeface& typeface, int gly
|
|||
return { GlyphLayer { ImageLayer { juceImage, transform } } };
|
||||
}
|
||||
|
||||
std::vector<GlyphLayer> Typeface::getLayersForGlyph (TypefaceMetricsKind kind, int glyphNumber, const AffineTransform& transform, float) const
|
||||
std::vector<GlyphLayer> Typeface::getLayersForGlyph (TypefaceMetricsKind kind,
|
||||
int glyphNumber,
|
||||
const AffineTransform& transform) const
|
||||
{
|
||||
auto* font = getNativeDetails().getFont();
|
||||
const auto metrics = getNativeDetails().getAscentDescent (kind);
|
||||
auto native = getNativeDetails();
|
||||
auto* font = native.getFont();
|
||||
const auto metrics = native.getAscentDescent (kind);
|
||||
const auto factor = metrics.getHeightToPointsFactor();
|
||||
jassert (! std::isinf (factor));
|
||||
const auto scale = factor / (float) hb_face_get_upem (hb_font_get_face (font));
|
||||
|
|
@ -542,7 +565,14 @@ std::vector<GlyphLayer> Typeface::getLayersForGlyph (TypefaceMetricsKind kind, i
|
|||
if (auto layers = getCOLRv0Layers (*this, glyphNumber, combinedTransform); ! layers.empty())
|
||||
return layers;
|
||||
|
||||
// No bitmap or COLRv0 for this glyph, so just get a simple monochromatic outline
|
||||
// Some fonts (e.g. Noto Color Emoji on Android) might only contain COLRv1 data, which we can't
|
||||
// easily display. In such cases, we can use system facilities to render the glyph into a
|
||||
// bitmap. If the face has colour info that wasn't already handled, try rendering to a bitmap.
|
||||
if (getColourGlyphFormats() != 0)
|
||||
if (auto layer = native.getFallbackColourGlyphLayers (glyphNumber, combinedTransform); ! layer.empty())
|
||||
return layer;
|
||||
|
||||
// No colour info available for this glyph, so just get a simple monochromatic outline
|
||||
auto path = getGlyphPathInGlyphUnits ((hb_codepoint_t) glyphNumber, font);
|
||||
|
||||
if (path.isEmpty())
|
||||
|
|
|
|||
|
|
@ -255,13 +255,10 @@ public:
|
|||
Support for SVG and COLRv1 may be added in the future, depending on demand. However, this
|
||||
would require significant additions to JUCE's rendering code, so it has been omitted for
|
||||
now.
|
||||
|
||||
The height is specified in JUCE font-height units.
|
||||
*/
|
||||
std::vector<GlyphLayer> getLayersForGlyph (TypefaceMetricsKind,
|
||||
int glyphNumber,
|
||||
const AffineTransform&,
|
||||
float normalisedHeight) const;
|
||||
const AffineTransform&) const;
|
||||
|
||||
/** Kinds of colour glyph format that may be implemented by a particular typeface.
|
||||
Most typefaces are monochromatic, and do not support any colour formats.
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@ Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font)
|
|||
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (create, "<init>", "(Ljava/nio/ByteBuffer;)V") \
|
||||
METHOD (setTtcIndex, "setTtcIndex", "(I)Landroid/graphics/fonts/Font$Builder;") \
|
||||
METHOD (build, "build", "()Landroid/graphics/fonts/Font;") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidFontBuilder, "android/graphics/fonts/Font$Builder", 29)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V")
|
||||
|
|
@ -98,6 +106,7 @@ std::unique_ptr<InputStream> makeAndroidInputStreamWrapper (LocalRef<jobject> st
|
|||
struct AndroidCachedTypeface
|
||||
{
|
||||
std::shared_ptr<hb_font_t> font;
|
||||
GlobalRef javaFont;
|
||||
TypefaceAscentDescent nonPortableMetrics;
|
||||
};
|
||||
|
||||
|
|
@ -226,20 +235,24 @@ StringArray Font::findAllTypefaceStyles (const String& family)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
class AndroidTypeface final : public Typeface
|
||||
class AndroidTypeface final : public Typeface,
|
||||
private TypefaceFallbackColourGlyphSupport
|
||||
{
|
||||
public:
|
||||
enum class DoCache
|
||||
{
|
||||
no,
|
||||
yes
|
||||
};
|
||||
|
||||
static Typeface::Ptr from (const Font& font)
|
||||
{
|
||||
if (auto* cache = MemoryFontCache::getInstance())
|
||||
{
|
||||
if (auto result = cache->find ({ font.getTypefaceName(), font.getTypefaceStyle() }))
|
||||
return new AndroidTypeface (DoCache::no, result->font, result->nonPortableMetrics, font.getTypefaceName(), font.getTypefaceStyle());
|
||||
{
|
||||
return new AndroidTypeface (DoCache::no,
|
||||
result->font,
|
||||
result->nonPortableMetrics,
|
||||
font.getTypefaceName(),
|
||||
font.getTypefaceStyle(),
|
||||
result->javaFont);
|
||||
}
|
||||
}
|
||||
|
||||
auto [blob, metrics] = getBlobForFont (font);
|
||||
auto face = FontStyleHelpers::getFaceForBlob ({ static_cast<const char*> (blob.getData()), blob.getSize() }, 0);
|
||||
|
|
@ -253,7 +266,16 @@ public:
|
|||
HbFont hbFont { hb_font_create (face.get()) };
|
||||
FontStyleHelpers::initSynthetics (hbFont.get(), font);
|
||||
|
||||
return new AndroidTypeface (DoCache::no, std::move (hbFont), metrics, font.getTypefaceName(), font.getTypefaceStyle());
|
||||
const auto androidFont = shouldStoreAndroidFont (face.get())
|
||||
? makeAndroidFont ({ static_cast<const std::byte*> (blob.getData()), blob.getSize() }, 0)
|
||||
: GlobalRef{};
|
||||
|
||||
return new AndroidTypeface (DoCache::no,
|
||||
std::move (hbFont),
|
||||
metrics,
|
||||
font.getTypefaceName(),
|
||||
font.getTypefaceStyle(),
|
||||
androidFont);
|
||||
}
|
||||
|
||||
static Typeface::Ptr from (Span<const std::byte> blob, unsigned int index = 0)
|
||||
|
|
@ -263,7 +285,7 @@ public:
|
|||
|
||||
Native getNativeDetails() const override
|
||||
{
|
||||
return Native { hbFont.get(), nonPortableMetrics };
|
||||
return Native { hbFont.get(), nonPortableMetrics, this };
|
||||
}
|
||||
|
||||
Typeface::Ptr createSystemFallback (const String& text, const String& language) const override
|
||||
|
|
@ -292,6 +314,11 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
enum class DoCache
|
||||
{
|
||||
no,
|
||||
yes
|
||||
};
|
||||
|
||||
// The definition of __BIONIC_AVAILABILITY was changed in NDK 28.1 and it now has variadic
|
||||
// parameters.
|
||||
|
|
@ -366,6 +393,40 @@ private:
|
|||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
static bool shouldStoreAndroidFont (hb_face_t* face)
|
||||
{
|
||||
return (hb_ot_color_has_svg (face) || hb_ot_color_has_paint (face))
|
||||
&& ! (hb_ot_color_has_layers (face) || hb_ot_color_has_png (face));
|
||||
}
|
||||
|
||||
static GlobalRef makeAndroidFont (Span<const std::byte> blob, unsigned int index)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jbyteArray> bytes { env->NewByteArray ((jint) blob.size()) };
|
||||
{
|
||||
auto* elements = env->GetByteArrayElements (bytes, nullptr);
|
||||
const ScopeGuard scope { [&] { env->ReleaseByteArrayElements (bytes, elements, 0); }};
|
||||
std::transform (blob.begin(), blob.end(), elements, [] (auto x) { return (jbyte) x; });
|
||||
}
|
||||
|
||||
LocalRef<jobject> byteBuffer { env->CallStaticObjectMethod (JavaByteBuffer,
|
||||
JavaByteBuffer.allocateDirect,
|
||||
(jint) blob.size()) };
|
||||
env->CallObjectMethod (byteBuffer, JavaByteBuffer.put, bytes.get());
|
||||
|
||||
LocalRef<jobject> builder { env->NewObject (AndroidFontBuilder,
|
||||
AndroidFontBuilder.create,
|
||||
byteBuffer.get()) };
|
||||
env->CallObjectMethod (builder,
|
||||
AndroidFontBuilder.setTtcIndex,
|
||||
(jint) index);
|
||||
LocalRef<jobject> androidFont { env->CallObjectMethod (builder,
|
||||
AndroidFontBuilder.build) };
|
||||
|
||||
return GlobalRef { androidFont };
|
||||
}
|
||||
|
||||
static Typeface::Ptr loadCompatibleFont (const TypefaceFileAndIndex& info)
|
||||
{
|
||||
FileInputStream stream { info.file };
|
||||
|
|
@ -376,26 +437,18 @@ private:
|
|||
MemoryBlock mb;
|
||||
stream.readIntoMemoryBlock (mb);
|
||||
|
||||
auto result = fromMemory (DoCache::no,
|
||||
{ static_cast<const std::byte*> (mb.getData()), mb.getSize() },
|
||||
(unsigned int) info.index);
|
||||
|
||||
if (result == nullptr)
|
||||
return {};
|
||||
|
||||
const auto tech = result->getColourGlyphFormats();
|
||||
const auto hasSupportedColours = (tech & (colourGlyphFormatCOLRv0 | colourGlyphFormatBitmap)) != 0;
|
||||
|
||||
// If the font only uses unsupported colour technologies, assume it's the system emoji font
|
||||
// and try to return a compatible version of the font
|
||||
if (tech != 0 && ! hasSupportedColours)
|
||||
if (auto fallback = from (FontOptions { "NotoColorEmojiLegacy", FontValues::defaultFontHeight, Font::plain }); fallback != nullptr)
|
||||
return fallback;
|
||||
|
||||
return result;
|
||||
return fromMemory (DoCache::no,
|
||||
{ static_cast<const std::byte*> (mb.getData()), mb.getSize() },
|
||||
(unsigned int) info.index);
|
||||
}
|
||||
|
||||
static Typeface::Ptr fromMemory (DoCache cache, Span<const std::byte> blob, unsigned int index = 0)
|
||||
/* The originalSource arg allows the font data to be read again if necessary, perhaps to create a
|
||||
Java Font instance. Pass a default-constructed File if the font data isn't backed by a
|
||||
persistent file.
|
||||
*/
|
||||
static Typeface::Ptr fromMemory (DoCache cache,
|
||||
Span<const std::byte> blob,
|
||||
unsigned int index = 0)
|
||||
{
|
||||
auto face = FontStyleHelpers::getFaceForBlob ({ reinterpret_cast<const char*> (blob.data()), blob.size() }, index);
|
||||
|
||||
|
|
@ -408,7 +461,8 @@ private:
|
|||
HbFont { hb_font_create (face.get()) },
|
||||
metrics,
|
||||
readFontName (face.get(), HB_OT_NAME_ID_FONT_FAMILY, nullptr),
|
||||
readFontName (face.get(), HB_OT_NAME_ID_FONT_SUBFAMILY, nullptr));
|
||||
readFontName (face.get(), HB_OT_NAME_ID_FONT_SUBFAMILY, nullptr),
|
||||
shouldStoreAndroidFont (face.get()) ? makeAndroidFont (blob, index) : GlobalRef{});
|
||||
}
|
||||
|
||||
static String readFontName (hb_face_t* face, hb_ot_name_id_t nameId, hb_language_t language)
|
||||
|
|
@ -426,15 +480,17 @@ private:
|
|||
std::shared_ptr<hb_font_t> fontIn,
|
||||
TypefaceAscentDescent nonPortableMetricsIn,
|
||||
const String& name,
|
||||
const String& style)
|
||||
const String& style,
|
||||
GlobalRef javaFontIn)
|
||||
: Typeface (name, style),
|
||||
hbFont (std::move (fontIn)),
|
||||
doCache (cache),
|
||||
nonPortableMetrics (nonPortableMetricsIn)
|
||||
nonPortableMetrics (nonPortableMetricsIn),
|
||||
javaFont (std::move (javaFontIn))
|
||||
{
|
||||
if (doCache == DoCache::yes)
|
||||
if (auto* c = MemoryFontCache::getInstance())
|
||||
c->add ({ name, style }, { hbFont, nonPortableMetrics });
|
||||
c->add ({ name, style }, { hbFont, javaFont, nonPortableMetrics });
|
||||
}
|
||||
|
||||
static std::tuple<MemoryBlock, TypefaceAscentDescent> getBlobForFont (const Font& font)
|
||||
|
|
@ -615,9 +671,115 @@ private:
|
|||
fullDescent / referenceFontSize };
|
||||
}
|
||||
|
||||
std::vector<GlyphLayer> getFallbackColourGlyphLayers (int glyph,
|
||||
const AffineTransform& transform) const override
|
||||
{
|
||||
// Canvas.drawGlyphs is only available from API 31
|
||||
if (getAndroidSDKVersion() < 31)
|
||||
return {};
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
hb_glyph_extents_t extents{};
|
||||
|
||||
if (! hb_font_get_glyph_extents (hbFont.get(), (hb_codepoint_t) glyph, &extents))
|
||||
{
|
||||
// Trying to retrieve an image for a glyph that's not present in the font?
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto upem = (jint) hb_face_get_upem (hb_font_get_face (hbFont.get()));
|
||||
constexpr jint referenceSize = 128;
|
||||
|
||||
const jint pixelW = (referenceSize * abs (extents.width)) / upem;
|
||||
const jint pixelH = (referenceSize * abs (extents.height)) / upem;
|
||||
const jint pixelBearingX = (referenceSize * extents.x_bearing) / upem;
|
||||
const jint pixelBearingY = (referenceSize * extents.y_bearing) / upem;
|
||||
|
||||
const jint pixelPadding = 2;
|
||||
|
||||
const auto totalW = (size_t) (pixelW + pixelPadding * 2);
|
||||
const auto totalH = (size_t) (pixelH + pixelPadding * 2);
|
||||
|
||||
LocalRef<jobject> bitmapConfig { env->CallStaticObjectMethod (AndroidBitmapConfig,
|
||||
AndroidBitmapConfig.valueOf,
|
||||
javaString ("ARGB_8888").get()) };
|
||||
|
||||
LocalRef<jobject> bitmap { env->CallStaticObjectMethod (AndroidBitmap,
|
||||
AndroidBitmap.createBitmap,
|
||||
totalW,
|
||||
totalH,
|
||||
bitmapConfig.get()) };
|
||||
|
||||
LocalRef<jobject> canvas { env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get())};
|
||||
|
||||
const jint glyphIdsIn[] { glyph };
|
||||
LocalRef<jintArray> glyphIds { env->NewIntArray (std::size (glyphIdsIn)) };
|
||||
env->SetIntArrayRegion (glyphIds, 0, std::size (glyphIdsIn), glyphIdsIn);
|
||||
|
||||
const jfloat pos[] { (float) (pixelPadding - pixelBearingX),
|
||||
(float) (pixelPadding + pixelBearingY) };
|
||||
LocalRef<jfloatArray> positions { env->NewFloatArray (std::size (pos)) };
|
||||
env->SetFloatArrayRegion (positions, 0, std::size (pos), pos);
|
||||
|
||||
LocalRef<jobject> paint { env->NewObject (AndroidPaint, AndroidPaint.defaultConstructor) };
|
||||
env->CallVoidMethod (paint, AndroidPaint.setTextSize, (jfloat) referenceSize);
|
||||
|
||||
env->CallVoidMethod (canvas,
|
||||
AndroidCanvas31.drawGlyphs,
|
||||
glyphIds.get(),
|
||||
0,
|
||||
positions.get(),
|
||||
0,
|
||||
(jint) std::size (glyphIdsIn),
|
||||
javaFont.get(),
|
||||
paint.get());
|
||||
|
||||
LocalRef<jintArray> pixels { env->NewIntArray ((jint) totalW * (jint) totalH) };
|
||||
env->CallVoidMethod (bitmap,
|
||||
AndroidBitmap.getPixels,
|
||||
pixels.get(),
|
||||
0,
|
||||
totalW,
|
||||
0,
|
||||
0,
|
||||
totalW,
|
||||
totalH);
|
||||
|
||||
auto* colours = env->GetIntArrayElements (pixels, nullptr);
|
||||
|
||||
ScopeGuard scope { [&] { env->ReleaseIntArrayElements (pixels, colours, JNI_ABORT); } };
|
||||
|
||||
Image resultImage { Image::ARGB, (int) totalW, (int) totalH, false };
|
||||
|
||||
// This image will be upside-down, but we'll use the final transform to flip it
|
||||
{
|
||||
Image::BitmapData bitmapData { resultImage, Image::BitmapData::writeOnly };
|
||||
|
||||
for (size_t y = 0; y < totalH; ++y)
|
||||
{
|
||||
for (size_t x = 0; x < totalW; ++x)
|
||||
{
|
||||
bitmapData.setPixelColour ((int) x,
|
||||
(int) y,
|
||||
Colour ((uint32) colours[x + y * totalW]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto scaleFactor = (float) upem / (float) referenceSize;
|
||||
return { GlyphLayer { ImageLayer { resultImage,
|
||||
AffineTransform::translation ((float) pixelBearingX,
|
||||
(float) -pixelBearingY)
|
||||
.scaled (scaleFactor, -scaleFactor)
|
||||
.followedBy (transform) } } };
|
||||
}
|
||||
|
||||
std::shared_ptr<hb_font_t> hbFont;
|
||||
DoCache doCache;
|
||||
TypefaceAscentDescent nonPortableMetrics;
|
||||
GlobalRef javaFont;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -210,8 +210,7 @@ public:
|
|||
return typeface->getLayersForGlyph (key.font.getMetricsKind(),
|
||||
key.glyph,
|
||||
AffineTransform::scale (fontHeight * key.font.getHorizontalScale(),
|
||||
fontHeight),
|
||||
fontHeight);
|
||||
fontHeight));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2668,7 +2667,7 @@ protected:
|
|||
const auto fontTransform = AffineTransform::scale (fontHeight * stack->font.getHorizontalScale(),
|
||||
fontHeight).followedBy (t);
|
||||
const auto fullTransform = stack->transform.getTransformWith (fontTransform);
|
||||
return std::tuple (stack->font.getTypefacePtr()->getLayersForGlyph (stack->font.getMetricsKind(), i, fullTransform, fontHeight), Point<float>{});
|
||||
return std::tuple (stack->font.getTypefacePtr()->getLayersForGlyph (stack->font.getMetricsKind(), i, fullTransform), Point<float>{});
|
||||
}();
|
||||
|
||||
const auto initialFill = stack->fillType;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue