|
- /*
- ==============================================================================
-
- This file is part of the JUCE 6 technical preview.
- Copyright (c) 2020 - Raw Material Software Limited
-
- You may use this code under the terms of the GPL v3
- (see www.gnu.org/licenses).
-
- For this technical preview, this file is not subject to commercial licensing.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- 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, CALLBACK) \
- STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
- STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
- STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;")
-
- DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
- #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")
-
- DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path")
- #undef JNI_CLASS_MEMBERS
-
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- METHOD (constructor, "<init>", "()V") \
- FIELD (left, "left", "F") \
- FIELD (right, "right", "F") \
- FIELD (top, "top", "F") \
- FIELD (bottom, "bottom", "F") \
- METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V")
-
- DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF")
- #undef JNI_CLASS_MEMBERS
-
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \
- METHOD (update, "update", "([B)V") \
- METHOD (digest, "digest", "()[B")
- DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest")
- #undef JNI_CLASS_MEMBERS
-
- //==============================================================================
- StringArray Font::findAllTypefaceNames()
- {
- StringArray results;
-
- for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
- results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
-
- return results;
- }
-
- StringArray Font::findAllTypefaceStyles (const String& family)
- {
- StringArray results ("Regular");
-
- for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
- results.addIfNotAlreadyThere (f.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 (getTypefaceFromAsset (name));
-
- 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 (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
- javaString (fontFile.getFullPathName()).get())));
- else
- typeface = GlobalRef (LocalRef<jobject>(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())
- {
- auto* env = getEnv();
- auto cacheFile = getCacheFileForData (data, size);
-
- typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
- javaString (cacheFile.getFullPathName()).get())));
-
- initialise (env);
- }
-
- void initialise (JNIEnv* const env)
- {
- rect = GlobalRef (LocalRef<jobject>(env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0)));
-
- paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
- const LocalRef<jobject> ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get()));
-
- charArray = GlobalRef (LocalRef<jobject>((jobject) env->NewCharArray (2)));
-
- paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize);
-
- const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent));
- const float fullDescent = paint.callFloatMethod (AndroidPaint.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();
- auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
- jfloatArray widths = env->NewFloatArray ((int) numChars);
-
- const int numDone = paint.callIntMethod (AndroidPaint.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();
- auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
- jfloatArray widths = env->NewFloatArray ((int) numChars);
-
- const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
-
- HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
- env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
- env->DeleteLocalRef (widths);
-
- auto 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
- Rectangle<int> bounds;
- auto* env = getEnv();
-
- {
- LocalRef<jobject> matrix (GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t)));
-
- jboolean isCopy;
- auto* buffer = env->GetCharArrayElements ((jcharArray) charArray.get(), &isCopy);
-
- buffer[0] = ch1; buffer[1] = ch2;
- env->ReleaseCharArrayElements ((jcharArray) charArray.get(), buffer, 0);
-
- LocalRef<jobject> path (env->NewObject (AndroidPath, AndroidPath.constructor));
- LocalRef<jobject> boundsF (env->NewObject (AndroidRectF, AndroidRectF.constructor));
-
-
- env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get());
-
- env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1);
-
- env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get());
-
- env->CallVoidMethod (boundsF.get(), AndroidRectF.roundOut, rect.get());
-
- bounds = Rectangle<int>::leftTopRightBottom (env->GetIntField (rect.get(), AndroidRect.left) - 1,
- env->GetIntField (rect.get(), AndroidRect.top),
- env->GetIntField (rect.get(), AndroidRect.right) + 1,
- env->GetIntField (rect.get(), AndroidRect.bottom));
-
- auto w = bounds.getWidth();
- auto h = jmax (1, bounds.getHeight());
-
- LocalRef<jobject> bitmapConfig (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get()));
- LocalRef<jobject> bitmap (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, w, h, bitmapConfig.get()));
- LocalRef<jobject> canvas (env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get()));
-
- env->CallBooleanMethod (matrix.get(), AndroidMatrix.postTranslate, bounds.getX() * -1.0f, bounds.getY() * -1.0f);
- env->CallVoidMethod (canvas.get(), AndroidCanvas.setMatrix, matrix.get());
- env->CallVoidMethod (canvas.get(), AndroidCanvas.drawPath, path.get(), paint.get());
-
- int requiredRenderArraySize = w * h;
- if (requiredRenderArraySize > lastCachedRenderArraySize)
- {
- cachedRenderArray = GlobalRef (LocalRef<jobject> ((jobject) env->NewIntArray (requiredRenderArraySize)));
- lastCachedRenderArraySize = requiredRenderArraySize;
- }
-
- env->CallVoidMethod (bitmap.get(), AndroidBitmap.getPixels, cachedRenderArray.get(), 0, w, 0, 0, w, h);
- env->CallVoidMethod (bitmap.get(), AndroidBitmap.recycle);
- }
-
- EdgeTable* et = nullptr;
-
- if (! bounds.isEmpty())
- {
- et = new EdgeTable (bounds);
-
- jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), nullptr);
- const jint* mask = maskDataElements;
-
- for (int y = bounds.getY(); y < bounds.getBottom(); ++y)
- {
- #if JUCE_LITTLE_ENDIAN
- const uint8* const lineBytes = ((const uint8*) mask) + 3;
- #else
- const uint8* const lineBytes = (const uint8*) mask;
- #endif
-
- et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth());
- mask += bounds.getWidth();
- }
-
- env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0);
- }
-
- return et;
- }
-
- GlobalRef typeface, paint, rect, charArray, cachedRenderArray;
- float ascent, descent, heightToPointsFactor;
- int lastCachedRenderArraySize = -1;
-
- 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& fontStyle)
- {
- String path ("/system/fonts/" + family);
-
- if (fontStyle.isNotEmpty())
- path << '-' << fontStyle;
-
- return File (path + ".ttf");
- }
-
- static LocalRef<jobject> getTypefaceFromAsset (const String& typefaceName)
- {
- auto* env = getEnv();
-
- LocalRef<jobject> assetManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets));
-
- if (assetManager == nullptr)
- return LocalRef<jobject>();
-
- auto assetTypeface = env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromAsset, assetManager.get(),
- javaString ("fonts/" + typefaceName).get());
-
- // this may throw
- if (env->ExceptionCheck() != 0)
- {
- env->ExceptionClear();
- return LocalRef<jobject>();
- }
-
- return LocalRef<jobject> (assetTypeface);
- }
-
- static File getCacheDirectory()
- {
- static File result = [] ()
- {
- auto appContext = getAppContext();
-
- if (appContext != nullptr)
- {
- auto* env = getEnv();
-
- LocalRef<jobject> cacheFile (env->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir));
- LocalRef<jstring> jPath ((jstring) env->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath));
-
- return File (juceString (env, jPath.get()));
- }
-
- jassertfalse;
- return File();
- } ();
-
- return result;
- }
-
- static HashMap<String, File>& getInMemoryFontCache()
- {
- static HashMap<String, File> cache;
- return cache;
- }
-
- static File getCacheFileForData (const void* data, size_t size)
- {
- static CriticalSection cs;
- JNIEnv* const env = getEnv();
-
- String key;
- {
- LocalRef<jobject> digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString("MD5").get()));
- LocalRef<jbyteArray> bytes(env->NewByteArray ((int) size));
-
- jboolean ignore;
- auto* jbytes = env->GetByteArrayElements(bytes.get(), &ignore);
- memcpy(jbytes, data, size);
- env->ReleaseByteArrayElements(bytes.get(), jbytes, 0);
-
- env->CallVoidMethod(digest.get(), JavaMessageDigest.update, bytes.get());
- LocalRef<jbyteArray> result((jbyteArray) env->CallObjectMethod(digest.get(), JavaMessageDigest.digest));
-
- auto* md5Bytes = env->GetByteArrayElements(result.get(), &ignore);
- key = String::toHexString(md5Bytes, env->GetArrayLength(result.get()), 0);
- env->ReleaseByteArrayElements(result.get(), md5Bytes, 0);
- }
-
- ScopedLock lock (cs);
- auto& mapEntry = getInMemoryFontCache().getReference (key);
-
- if (mapEntry == File())
- {
- mapEntry = getCacheDirectory().getChildFile ("bindata_" + key);
- mapEntry.replaceWithData (data, size);
- }
-
- return mapEntry;
- }
-
- 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
-
- } // namespace juce
|