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