1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.support.v4.graphics; 18 19 import android.graphics.Paint; 20 import android.graphics.Rect; 21 import android.support.annotation.NonNull; 22 import android.support.v4.util.Pair; 23 24 class PaintCompatApi14 { 25 // U+DFFFD which is very end of unassigned plane. 26 private static final String TOFU_STRING = "\uDB3F\uDFFD"; 27 28 private static final ThreadLocal<Pair<Rect, Rect>> sRectThreadLocal = new ThreadLocal<>(); 29 30 static boolean hasGlyph(@NonNull Paint paint, @NonNull String string) { 31 final int length = string.length(); 32 33 if (length == 1 && Character.isWhitespace(string.charAt(0))) { 34 // measureText + getTextBounds skips whitespace so we need to special case it here 35 return true; 36 } 37 38 final float missingGlyphWidth = paint.measureText(TOFU_STRING); 39 final float width = paint.measureText(string); 40 41 if (width == 0f) { 42 // If the string width is 0, it can't be rendered 43 return false; 44 } 45 46 if (string.codePointCount(0, string.length()) > 1) { 47 // Heuristic to detect fallback glyphs for ligatures like flags and ZWJ sequences 48 // Return false if string is rendered too widely 49 if (width > 2 * missingGlyphWidth) { 50 return false; 51 } 52 53 // Heuristic to detect fallback glyphs for ligatures like flags and ZWJ sequences (2). 54 // If width is greater than or equal to the sum of width of each code point, it is very 55 // likely that the system is using fallback fonts to draw {@code string} in two or more 56 // glyphs instead of a single ligature glyph. (hasGlyph returns false in this case.) 57 // False detections are possible (the ligature glyph may happen to have the same width 58 // as the sum width), but there are no good way to avoid them. 59 // NOTE: This heuristic does not work with proportional glyphs. 60 // NOTE: This heuristic does not work when a ZWJ sequence is partially combined. 61 // E.g. If system has a glyph for "A ZWJ B" and not for "A ZWJ B ZWJ C", this heuristic 62 // returns true for "A ZWJ B ZWJ C". 63 float sumWidth = 0; 64 int i = 0; 65 while (i < length) { 66 int charCount = Character.charCount(string.codePointAt(i)); 67 sumWidth += paint.measureText(string, i, i + charCount); 68 i += charCount; 69 } 70 if (width >= sumWidth) { 71 return false; 72 } 73 } 74 75 if (width != missingGlyphWidth) { 76 // If the widths are different then its not tofu 77 return true; 78 } 79 80 // If the widths are the same, lets check the bounds. The chance of them being 81 // different chars with the same bounds is extremely small 82 final Pair<Rect, Rect> rects = obtainEmptyRects(); 83 paint.getTextBounds(TOFU_STRING, 0, TOFU_STRING.length(), rects.first); 84 paint.getTextBounds(string, 0, length, rects.second); 85 return !rects.first.equals(rects.second); 86 } 87 88 private static Pair<Rect, Rect> obtainEmptyRects() { 89 Pair<Rect, Rect> rects = sRectThreadLocal.get(); 90 if (rects == null) { 91 rects = new Pair<>(new Rect(), new Rect()); 92 sRectThreadLocal.set(rects); 93 } else { 94 rects.first.setEmpty(); 95 rects.second.setEmpty(); 96 } 97 return rects; 98 } 99 } 100