|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.
   JUCE is an open source library subject to commercial or open-source
   licensing.
   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).
   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy
   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.
  ==============================================================================
*/
struct DefaultFontNames
{
    DefaultFontNames()
        : defaultSans  ("sans"),
          defaultSerif ("serif"),
          defaultFixed ("monospace"),
          defaultFallback ("sans")
    {
    }
    String getRealFontName (const String& faceName) const
    {
        if (faceName == Font::getDefaultSansSerifFontName())    return defaultSans;
        if (faceName == Font::getDefaultSerifFontName())        return defaultSerif;
        if (faceName == Font::getDefaultMonospacedFontName())   return defaultFixed;
        return faceName;
    }
    String defaultSans, defaultSerif, defaultFixed, defaultFallback;
};
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
    static DefaultFontNames defaultNames;
    Font f (font);
    f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
    return Typeface::createSystemTypefaceFor (f);
}
//==============================================================================
#if JUCE_USE_FREETYPE
StringArray FTTypefaceList::getDefaultFontDirectories()
{
    return StringArray ("/system/fonts");
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
    return new FreeTypeTypeface (font);
}
void Typeface::scanFolderForFonts (const File& folder)
{
    FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
}
StringArray Font::findAllTypefaceNames()
{
    return FTTypefaceList::getInstance()->findAllFamilyNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
    return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
    return false;
}
#else
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 STATICMETHOD (create,          "create",           "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
 STATICMETHOD (createFromFile,  "createFromFile",   "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface");
#undef JNI_CLASS_MEMBERS
//==============================================================================
StringArray Font::findAllTypefaceNames()
{
    StringArray results;
    Array<File> fonts;
    File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, "*.ttf");
    for (int i = 0; i < fonts.size(); ++i)
        results.addIfNotAlreadyThere (fonts.getReference(i).getFileNameWithoutExtension()
                                        .upToLastOccurrenceOf ("-", false, false));
    return results;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
    StringArray results ("Regular");
    Array<File> fonts;
    File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, family + "-*.ttf");
    for (int i = 0; i < fonts.size(); ++i)
        results.addIfNotAlreadyThere (fonts.getReference(i).getFileNameWithoutExtension()
                                        .fromLastOccurrenceOf ("-", false, false));
    return results;
}
const float referenceFontSize = 256.0f;
const float referenceFontToUnits = 1.0f / referenceFontSize;
//==============================================================================
class AndroidTypeface   : public Typeface
{
public:
    AndroidTypeface (const Font& font)
        : Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
          ascent (0), descent (0), heightToPointsFactor (1.0f)
    {
        JNIEnv* const env = getEnv();
        // First check whether there's an embedded asset with this font name:
        typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromAsset,
                                                                 javaString ("fonts/" + name).get()));
        if (typeface.get() == nullptr)
        {
            const bool isBold   = style.contains ("Bold");
            const bool isItalic = style.contains ("Italic");
            File fontFile (getFontFile (name, style));
            if (! fontFile.exists())
                fontFile = findFontFile (name, isBold, isItalic);
            if (fontFile.exists())
                typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
                                                                   javaString (fontFile.getFullPathName()).get()));
            else
                typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create,
                                                                   javaString (getName()).get(),
                                                                   (isBold ? 1 : 0) + (isItalic ? 2 : 0)));
        }
        initialise (env);
    }
    AndroidTypeface (const void* data, size_t size)
        : Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String())
    {
        JNIEnv* const env = getEnv();
        LocalRef<jbyteArray> bytes (env->NewByteArray ((jsize) size));
        env->SetByteArrayRegion (bytes, 0, (jsize) size, (const jbyte*) data);
        typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromByteArray, bytes.get()));
        initialise (env);
    }
    void initialise (JNIEnv* const env)
    {
        rect = GlobalRef (env->NewObject (RectClass, RectClass.constructor, 0, 0, 0, 0));
        paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
        const LocalRef<jobject> ignored (paint.callObjectMethod (Paint.setTypeface, typeface.get()));
        paint.callVoidMethod (Paint.setTextSize, referenceFontSize);
        const float fullAscent = std::abs (paint.callFloatMethod (Paint.ascent));
        const float fullDescent = paint.callFloatMethod (Paint.descent);
        const float totalHeight = fullAscent + fullDescent;
        ascent  = fullAscent / totalHeight;
        descent = fullDescent / totalHeight;
        heightToPointsFactor = referenceFontSize / totalHeight;
    }
    float getAscent() const override                 { return ascent; }
    float getDescent() const override                { return descent; }
    float getHeightToPointsFactor() const override   { return heightToPointsFactor; }
    float getStringWidth (const String& text) override
    {
        JNIEnv* env = getEnv();
        const int numChars = text.length();
        jfloatArray widths = env->NewFloatArray (numChars);
        const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths);
        HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
        env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
        env->DeleteLocalRef (widths);
        float x = 0;
        for (int i = 0; i < numDone; ++i)
            x += localWidths[i];
        return x * referenceFontToUnits;
    }
    void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
    {
        JNIEnv* env = getEnv();
        const int numChars = text.length();
        jfloatArray widths = env->NewFloatArray (numChars);
        const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths);
        HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
        env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
        env->DeleteLocalRef (widths);
        String::CharPointerType s (text.getCharPointer());
        xOffsets.add (0);
        float x = 0;
        for (int i = 0; i < numDone; ++i)
        {
            const float local = localWidths[i];
            // Android uses jchar (UTF-16) characters
            jchar ch = (jchar) s.getAndAdvance();
            // Android has no proper glyph support, so we have to do
            // a hacky workaround for ligature detection
           #if JUCE_STRING_UTF_TYPE <= 16
            static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph");
            // if the width of this glyph is zero inside the string but has
            // a width on it's own, then it's probably due to ligature
            if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f)
            {
                // modify the previous glyph
                int& glyphNumber = glyphs.getReference (glyphs.size() - 1);
                // make sure this is not a three character ligature
                if (glyphNumber < std::numeric_limits<jchar>::max())
                {
                    const unsigned int previousGlyph
                        = static_cast<unsigned int> (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U);
                    const unsigned int thisGlyph
                        = static_cast<unsigned int> (ch)          & ((1U << (sizeof (jchar) * 8U)) - 1U);
                    glyphNumber = static_cast<int> ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph);
                    ch = 0;
                }
            }
           #endif
            glyphs.add ((int) ch);
            x += local;
            xOffsets.add (x * referenceFontToUnits);
        }
    }
    bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
    {
        return false;
    }
    EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
    {
       #if JUCE_STRING_UTF_TYPE <= 16
        static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int");
        // glyphNumber of zero is used to indicate that the last character was a ligature
        if (glyphNumber == 0) return nullptr;
        jchar ch1 = (static_cast<unsigned int> (glyphNumber) >> 0)                     & ((1U << (sizeof (jchar) * 8U)) - 1U);
        jchar ch2 = (static_cast<unsigned int> (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U);
       #else
        jchar ch1 = glyphNumber, ch2 = 0;
       #endif
        JNIEnv* env = getEnv();
        jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t));
        jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, ch1, ch2, paint.get(), matrix, rect.get());
        env->DeleteLocalRef (matrix);
        const int left   = env->GetIntField (rect.get(), RectClass.left);
        const int top    = env->GetIntField (rect.get(), RectClass.top);
        const int right  = env->GetIntField (rect.get(), RectClass.right);
        const int bottom = env->GetIntField (rect.get(), RectClass.bottom);
        const Rectangle<int> bounds (left, top, right - left, bottom - top);
        EdgeTable* et = nullptr;
        if (! bounds.isEmpty())
        {
            et = new EdgeTable (bounds);
            jint* const maskDataElements = env->GetIntArrayElements (maskData, 0);
            const jint* mask = maskDataElements;
            for (int y = top; y < bottom; ++y)
            {
               #if JUCE_LITTLE_ENDIAN
                const uint8* const lineBytes = ((const uint8*) mask) + 3;
               #else
                const uint8* const lineBytes = (const uint8*) mask;
               #endif
                et->clipLineToMask (left, y, lineBytes, 4, bounds.getWidth());
                mask += bounds.getWidth();
            }
            env->ReleaseIntArrayElements (maskData, maskDataElements, 0);
        }
        env->DeleteLocalRef (maskData);
        return et;
    }
    GlobalRef typeface, paint, rect;
    float ascent, descent, heightToPointsFactor;
private:
    static File findFontFile (const String& family,
                              const bool bold, const bool italic)
    {
        File file;
        if (bold || italic)
        {
            String suffix;
            if (bold)   suffix = "Bold";
            if (italic) suffix << "Italic";
            file = getFontFile (family, suffix);
            if (file.exists())
                return file;
        }
        file = getFontFile (family, "Regular");
        if (! file.exists())
            file = getFontFile (family, String());
        return file;
    }
    static File getFontFile (const String& family, const String& style)
    {
        String path ("/system/fonts/" + family);
        if (style.isNotEmpty())
            path << '-' << style;
        return File (path + ".ttf");
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface)
};
//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
    return new AndroidTypeface (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)
{
    return new AndroidTypeface (data, size);
}
void Typeface::scanFolderForFonts (const File&)
{
    jassertfalse; // not available unless using FreeType
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
    return false;
}
#endif
 |