The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

551 lines
20KB

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