Check if custom font can display character

2019-01-15 19:14发布

问题:

I have a custom font which is displaying the box character. The font I am using does not support all languages apparently. I want to check if the String I am about to display can be displayed by my custom font. If it cannot then I want to use the standard Android font (which I know can display the characters). I can't find a method to check if my Typeface can display a particular String though. I am sure I have seen a method around that does this somewhere. Anyone know?

回答1:

Update: If you're coding for API level 23 or above please use Paint.hasGlyph() as mentioned in the answer by Orson Baines. Otherwise see my original answer below.


As you mentioned in your own answer there is no built in methods available for checking this. However if you have some control over what string/characters you want to check, it is actually possible to do this with a slightly more creative approach.

You could draw a character that you know are missing in the font you want to check and then draw a character that you want to know if it exists, and then finally compare them and see if they look the same.

Here is a code sample that illustrates how I implemented this check:

public Bitmap drawBitmap(String text){
    Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ALPHA_8);
    Canvas c = new Canvas(b);
    c.drawText(text, 0, BITMAP_HEIGHT / 2, paint);
    return b;
}

public byte[] getPixels(Bitmap b) {
    ByteBuffer buffer = ByteBuffer.allocate(b.getByteCount());
    b.copyPixelsToBuffer(buffer);
    return buffer.array();
}
public boolean isCharacterMissingInFont(String ch) {
    String missingCharacter = "\u0978"; // reserved code point in the devanagari block (should not exist).
    byte[] b1 = getPixels(drawBitmap(ch));
    byte[] b2 = getPixels(drawBitmap(missingCharacter));
    return Arrays.equals(b1, b2);
}

There are some important limitations to keep in mind with this method:

  • The character you check (argument to isCharacterMissing) must be non whitespace (can be detected by using Character.isWhitespace()). In some fonts missing characters are rendered as whitespace, so if you would compare a whitespace character that exists it would incorrectly be reported as a missing character in fonts like these.
  • The missingCharacter member must be a character that is known to be missing in the font to be checked. The code point U+0978 that I am using is a reserved code point in the middle of the Devanagari Unicode block so it should currently be missing in all Unicode compatible fonts, however in the future when new characters are added to Unicode this code point might be assigned to a real character. So this approach is not future proof, but if you supply the font yourself you can make sure you are using a missing character whenever you change the font of your application.
  • Since this check is done by drawing bitmaps and comparing them it's not very efficient so it should only be used to check a few characters and not all strings in your application.

Update:

The solution to look at U+0978 did not work long as pointed out by others. That character was added in the Unicode 7.0 release in June 2014. Another option is to use a glyph that exists in unicode but that is very unlikely to be used in a normal font.

U+124AB in the Early Dynastic Cuneiform block is probably something that doesn't exist in many fonts at all.



回答2:

As of Android version 23, You can test it like this:

Typeface typeface;
//initialize the custom font here

//enter the character to test
String charToTest="\u0978";
Paint paint=new Paint();
paint.setTypeface(typeface);
boolean hasGlyph=paint.hasGlyph(charToTest);


回答3:

@nibarius answer works well, though with a different character: "\u2936".

However, that solution is heavy in memory, cpu and time. I needed a solution for a very small number of characters so I implemented a "quick and dirty" solution by checking only the bounds. It works about 50x times faster, so it is more suitable for my case:

public boolean isCharGlyphAvailable(char... c) {
      Rect missingCharBounds = new Rect();
      char missingCharacter = '\u2936';
      paint.getTextBounds(c, 0, 1, missingCharBounds); // takes array as argument, but I need only one char.
      Rect testCharsBounds = new Rect();
      paint.getTextBounds(c, 0, 1, testCharsBounds);
      return !testCharsBounds.equals(missingCharBounds);
}

I should warn that this method is less accurate than @nibarius, and there are some false negatives and false positives, but if you just need to test a few characters in order to be able to fallback to other characters - this solution is great.



回答4:

please check the source code from Googles Mozc project. The EmojiRenderableChecker class seems to work pretty well!

it's like a compat version for Paint.hasGlypgh (added in Marshmallow).



回答5:

For those of you who would still like your app to be compatible with APIs less than 23 and still use the hasGlyph function in your code, you can use the @TargetApi(23) in the following way:

@TargetApi(23)
public boolean isPrintable( String c ) {
    Paint paint=new Paint();
    boolean hasGlyph=true;
    hasGlyph=paint.hasGlyph(c);
    return hasGlyph;
}

And then in your code:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if(isPrintable(yourString)) {
        //Do Something
    }
}

The only problem is that in APIs lower than 23, your app may show blank symbols. But at least in API23+ your app will be perfect.



回答6:

To check whether the music symbol ♫ is being displayed in a string (if it is not being displayed on some devices), you can try measuring the string width; if width == 0 then the symbol is absent.

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Rect rtt = new Rect();
paint.getTextBounds( "♫", 0, 1, rtt );
    if( rtt.width() == 0 ){
}