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.

550 lines
20KB

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