Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_android_Fonts.cpp 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. struct DefaultFontNames
  16. {
  17. DefaultFontNames()
  18. : defaultSans ("sans"),
  19. defaultSerif ("serif"),
  20. defaultFixed ("monospace"),
  21. defaultFallback ("sans")
  22. {
  23. }
  24. String getRealFontName (const String& faceName) const
  25. {
  26. if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
  27. if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
  28. if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
  29. return faceName;
  30. }
  31. String defaultSans, defaultSerif, defaultFixed, defaultFallback;
  32. };
  33. Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
  34. {
  35. static DefaultFontNames defaultNames;
  36. Font f (font);
  37. f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
  38. return Typeface::createSystemTypefaceFor (f);
  39. }
  40. //==============================================================================
  41. #if JUCE_USE_FREETYPE
  42. StringArray FTTypefaceList::getDefaultFontDirectories()
  43. {
  44. return StringArray ("/system/fonts");
  45. }
  46. Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
  47. {
  48. return new FreeTypeTypeface (font);
  49. }
  50. void Typeface::scanFolderForFonts (const File& folder)
  51. {
  52. FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
  53. }
  54. StringArray Font::findAllTypefaceNames()
  55. {
  56. return FTTypefaceList::getInstance()->findAllFamilyNames();
  57. }
  58. StringArray Font::findAllTypefaceStyles (const String& family)
  59. {
  60. return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
  61. }
  62. bool TextLayout::createNativeLayout (const AttributedString&)
  63. {
  64. return false;
  65. }
  66. #else
  67. //==============================================================================
  68. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  69. STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
  70. STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
  71. STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;")
  72. DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
  73. #undef JNI_CLASS_MEMBERS
  74. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  75. METHOD (constructor, "<init>", "()V") \
  76. METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V")
  77. DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path")
  78. #undef JNI_CLASS_MEMBERS
  79. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  80. METHOD (constructor, "<init>", "()V") \
  81. FIELD (left, "left", "F") \
  82. FIELD (right, "right", "F") \
  83. FIELD (top, "top", "F") \
  84. FIELD (bottom, "bottom", "F") \
  85. METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V")
  86. DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF")
  87. #undef JNI_CLASS_MEMBERS
  88. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  89. STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \
  90. METHOD (update, "update", "([B)V") \
  91. METHOD (digest, "digest", "()[B")
  92. DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest")
  93. #undef JNI_CLASS_MEMBERS
  94. //==============================================================================
  95. StringArray Font::findAllTypefaceNames()
  96. {
  97. StringArray results;
  98. for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
  99. results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
  100. return results;
  101. }
  102. StringArray Font::findAllTypefaceStyles (const String& family)
  103. {
  104. StringArray results ("Regular");
  105. for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
  106. results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false));
  107. return results;
  108. }
  109. const float referenceFontSize = 256.0f;
  110. const float referenceFontToUnits = 1.0f / referenceFontSize;
  111. //==============================================================================
  112. class AndroidTypeface : public Typeface
  113. {
  114. public:
  115. AndroidTypeface (const Font& font)
  116. : Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
  117. ascent (0), descent (0), heightToPointsFactor (1.0f)
  118. {
  119. JNIEnv* const env = getEnv();
  120. // First check whether there's an embedded asset with this font name:
  121. typeface = GlobalRef (getTypefaceFromAsset (name));
  122. if (typeface.get() == nullptr)
  123. {
  124. const bool isBold = style.contains ("Bold");
  125. const bool isItalic = style.contains ("Italic");
  126. File fontFile (getFontFile (name, style));
  127. if (! fontFile.exists())
  128. fontFile = findFontFile (name, isBold, isItalic);
  129. if (fontFile.exists())
  130. typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
  131. javaString (fontFile.getFullPathName()).get())));
  132. else
  133. typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create,
  134. javaString (getName()).get(),
  135. (isBold ? 1 : 0) + (isItalic ? 2 : 0))));
  136. }
  137. initialise (env);
  138. }
  139. AndroidTypeface (const void* data, size_t size)
  140. : Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String())
  141. {
  142. auto* env = getEnv();
  143. auto cacheFile = getCacheFileForData (data, size);
  144. typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
  145. javaString (cacheFile.getFullPathName()).get())));
  146. initialise (env);
  147. }
  148. void initialise (JNIEnv* const env)
  149. {
  150. rect = GlobalRef (LocalRef<jobject>(env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0)));
  151. paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
  152. const LocalRef<jobject> ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get()));
  153. charArray = GlobalRef (LocalRef<jobject>((jobject) env->NewCharArray (2)));
  154. paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize);
  155. const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent));
  156. const float fullDescent = paint.callFloatMethod (AndroidPaint.descent);
  157. const float totalHeight = fullAscent + fullDescent;
  158. ascent = fullAscent / totalHeight;
  159. descent = fullDescent / totalHeight;
  160. heightToPointsFactor = referenceFontSize / totalHeight;
  161. }
  162. float getAscent() const override { return ascent; }
  163. float getDescent() const override { return descent; }
  164. float getHeightToPointsFactor() const override { return heightToPointsFactor; }
  165. float getStringWidth (const String& text) override
  166. {
  167. JNIEnv* env = getEnv();
  168. auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
  169. jfloatArray widths = env->NewFloatArray ((int) numChars);
  170. const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
  171. HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
  172. env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
  173. env->DeleteLocalRef (widths);
  174. float x = 0;
  175. for (int i = 0; i < numDone; ++i)
  176. x += localWidths[i];
  177. return x * referenceFontToUnits;
  178. }
  179. void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
  180. {
  181. JNIEnv* env = getEnv();
  182. auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
  183. jfloatArray widths = env->NewFloatArray ((int) numChars);
  184. const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
  185. HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
  186. env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
  187. env->DeleteLocalRef (widths);
  188. auto s = text.getCharPointer();
  189. xOffsets.add (0);
  190. float x = 0;
  191. for (int i = 0; i < numDone; ++i)
  192. {
  193. const float local = localWidths[i];
  194. // Android uses jchar (UTF-16) characters
  195. jchar ch = (jchar) s.getAndAdvance();
  196. // Android has no proper glyph support, so we have to do
  197. // a hacky workaround for ligature detection
  198. #if JUCE_STRING_UTF_TYPE <= 16
  199. static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph");
  200. // if the width of this glyph is zero inside the string but has
  201. // a width on it's own, then it's probably due to ligature
  202. if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f)
  203. {
  204. // modify the previous glyph
  205. int& glyphNumber = glyphs.getReference (glyphs.size() - 1);
  206. // make sure this is not a three character ligature
  207. if (glyphNumber < std::numeric_limits<jchar>::max())
  208. {
  209. const unsigned int previousGlyph
  210. = static_cast<unsigned int> (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U);
  211. const unsigned int thisGlyph
  212. = static_cast<unsigned int> (ch) & ((1U << (sizeof (jchar) * 8U)) - 1U);
  213. glyphNumber = static_cast<int> ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph);
  214. ch = 0;
  215. }
  216. }
  217. #endif
  218. glyphs.add ((int) ch);
  219. x += local;
  220. xOffsets.add (x * referenceFontToUnits);
  221. }
  222. }
  223. bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
  224. {
  225. return false;
  226. }
  227. EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
  228. {
  229. #if JUCE_STRING_UTF_TYPE <= 16
  230. static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int");
  231. // glyphNumber of zero is used to indicate that the last character was a ligature
  232. if (glyphNumber == 0) return nullptr;
  233. jchar ch1 = (static_cast<unsigned int> (glyphNumber) >> 0) & ((1U << (sizeof (jchar) * 8U)) - 1U);
  234. jchar ch2 = (static_cast<unsigned int> (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U);
  235. #else
  236. jchar ch1 = glyphNumber, ch2 = 0;
  237. #endif
  238. Rectangle<int> bounds;
  239. auto* env = getEnv();
  240. {
  241. LocalRef<jobject> matrix (GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t)));
  242. jboolean isCopy;
  243. auto* buffer = env->GetCharArrayElements ((jcharArray) charArray.get(), &isCopy);
  244. buffer[0] = ch1; buffer[1] = ch2;
  245. env->ReleaseCharArrayElements ((jcharArray) charArray.get(), buffer, 0);
  246. LocalRef<jobject> path (env->NewObject (AndroidPath, AndroidPath.constructor));
  247. LocalRef<jobject> boundsF (env->NewObject (AndroidRectF, AndroidRectF.constructor));
  248. env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get());
  249. env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1);
  250. env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get());
  251. env->CallVoidMethod (boundsF.get(), AndroidRectF.roundOut, rect.get());
  252. bounds = Rectangle<int>::leftTopRightBottom (env->GetIntField (rect.get(), AndroidRect.left) - 1,
  253. env->GetIntField (rect.get(), AndroidRect.top),
  254. env->GetIntField (rect.get(), AndroidRect.right) + 1,
  255. env->GetIntField (rect.get(), AndroidRect.bottom));
  256. auto w = bounds.getWidth();
  257. auto h = jmax (1, bounds.getHeight());
  258. LocalRef<jobject> bitmapConfig (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get()));
  259. LocalRef<jobject> bitmap (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, w, h, bitmapConfig.get()));
  260. LocalRef<jobject> canvas (env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get()));
  261. env->CallBooleanMethod (matrix.get(), AndroidMatrix.postTranslate, bounds.getX() * -1.0f, bounds.getY() * -1.0f);
  262. env->CallVoidMethod (canvas.get(), AndroidCanvas.setMatrix, matrix.get());
  263. env->CallVoidMethod (canvas.get(), AndroidCanvas.drawPath, path.get(), paint.get());
  264. int requiredRenderArraySize = w * h;
  265. if (requiredRenderArraySize > lastCachedRenderArraySize)
  266. {
  267. cachedRenderArray = GlobalRef (LocalRef<jobject> ((jobject) env->NewIntArray (requiredRenderArraySize)));
  268. lastCachedRenderArraySize = requiredRenderArraySize;
  269. }
  270. env->CallVoidMethod (bitmap.get(), AndroidBitmap.getPixels, cachedRenderArray.get(), 0, w, 0, 0, w, h);
  271. env->CallVoidMethod (bitmap.get(), AndroidBitmap.recycle);
  272. }
  273. EdgeTable* et = nullptr;
  274. if (! bounds.isEmpty())
  275. {
  276. et = new EdgeTable (bounds);
  277. jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), nullptr);
  278. const jint* mask = maskDataElements;
  279. for (int y = bounds.getY(); y < bounds.getBottom(); ++y)
  280. {
  281. #if JUCE_LITTLE_ENDIAN
  282. const uint8* const lineBytes = ((const uint8*) mask) + 3;
  283. #else
  284. const uint8* const lineBytes = (const uint8*) mask;
  285. #endif
  286. et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth());
  287. mask += bounds.getWidth();
  288. }
  289. env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0);
  290. }
  291. return et;
  292. }
  293. GlobalRef typeface, paint, rect, charArray, cachedRenderArray;
  294. float ascent, descent, heightToPointsFactor;
  295. int lastCachedRenderArraySize = -1;
  296. private:
  297. static File findFontFile (const String& family,
  298. const bool bold, const bool italic)
  299. {
  300. File file;
  301. if (bold || italic)
  302. {
  303. String suffix;
  304. if (bold) suffix = "Bold";
  305. if (italic) suffix << "Italic";
  306. file = getFontFile (family, suffix);
  307. if (file.exists())
  308. return file;
  309. }
  310. file = getFontFile (family, "Regular");
  311. if (! file.exists())
  312. file = getFontFile (family, String());
  313. return file;
  314. }
  315. static File getFontFile (const String& family, const String& fontStyle)
  316. {
  317. String path ("/system/fonts/" + family);
  318. if (fontStyle.isNotEmpty())
  319. path << '-' << fontStyle;
  320. return File (path + ".ttf");
  321. }
  322. static LocalRef<jobject> getTypefaceFromAsset (const String& typefaceName)
  323. {
  324. auto* env = getEnv();
  325. LocalRef<jobject> assetManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets));
  326. if (assetManager == nullptr)
  327. return LocalRef<jobject>();
  328. auto assetTypeface = env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromAsset, assetManager.get(),
  329. javaString ("fonts/" + typefaceName).get());
  330. // this may throw
  331. if (env->ExceptionCheck() != 0)
  332. {
  333. env->ExceptionClear();
  334. return LocalRef<jobject>();
  335. }
  336. return LocalRef<jobject> (assetTypeface);
  337. }
  338. static File getCacheDirectory()
  339. {
  340. static File result = [] ()
  341. {
  342. auto appContext = getAppContext();
  343. if (appContext != nullptr)
  344. {
  345. auto* env = getEnv();
  346. LocalRef<jobject> cacheFile (env->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir));
  347. LocalRef<jstring> jPath ((jstring) env->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath));
  348. return File (juceString (env, jPath.get()));
  349. }
  350. jassertfalse;
  351. return File();
  352. } ();
  353. return result;
  354. }
  355. static HashMap<String, File>& getInMemoryFontCache()
  356. {
  357. static HashMap<String, File> cache;
  358. return cache;
  359. }
  360. static File getCacheFileForData (const void* data, size_t size)
  361. {
  362. static CriticalSection cs;
  363. JNIEnv* const env = getEnv();
  364. String key;
  365. {
  366. LocalRef<jobject> digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString("MD5").get()));
  367. LocalRef<jbyteArray> bytes(env->NewByteArray ((int) size));
  368. jboolean ignore;
  369. auto* jbytes = env->GetByteArrayElements(bytes.get(), &ignore);
  370. memcpy(jbytes, data, size);
  371. env->ReleaseByteArrayElements(bytes.get(), jbytes, 0);
  372. env->CallVoidMethod(digest.get(), JavaMessageDigest.update, bytes.get());
  373. LocalRef<jbyteArray> result((jbyteArray) env->CallObjectMethod(digest.get(), JavaMessageDigest.digest));
  374. auto* md5Bytes = env->GetByteArrayElements(result.get(), &ignore);
  375. key = String::toHexString(md5Bytes, env->GetArrayLength(result.get()), 0);
  376. env->ReleaseByteArrayElements(result.get(), md5Bytes, 0);
  377. }
  378. ScopedLock lock (cs);
  379. auto& mapEntry = getInMemoryFontCache().getReference (key);
  380. if (mapEntry == File())
  381. {
  382. mapEntry = getCacheDirectory().getChildFile ("bindata_" + key);
  383. mapEntry.replaceWithData (data, size);
  384. }
  385. return mapEntry;
  386. }
  387. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface)
  388. };
  389. //==============================================================================
  390. Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
  391. {
  392. return new AndroidTypeface (font);
  393. }
  394. Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)
  395. {
  396. return new AndroidTypeface (data, size);
  397. }
  398. void Typeface::scanFolderForFonts (const File&)
  399. {
  400. jassertfalse; // not available unless using FreeType
  401. }
  402. bool TextLayout::createNativeLayout (const AttributedString&)
  403. {
  404. return false;
  405. }
  406. #endif
  407. } // namespace juce