检查自定义字体是否能够显示字符

14

我有一个自定义字体,但它会显示方块字符。这个字体显然不支持所有语言。我想要检查一下即将显示的字符串是否可以被我的自定义字体显示。如果不能,则想要使用标准的Android字体(我知道它可以显示这些字符)。但是我找不到一种方法来检查我的字体是否可以显示特定的字符串。我确定我以前看过某个能做到这件事的方法。请问有人知道吗?


嗯...我认为我们在Android内部没有足够的低级字体访问权限来确定特定字形是否存在于特定字体文件中。 - CommonsWare
bahpo的回答对我有用:https://dev59.com/cWgt5IYBdhLWcg3wygab#41100873 - YellowJ
8个回答

10

更新: 如果您正在编写API级别为23或更高级别的代码,请使用Paint.hasGlyph(),如Orson Baines的答案中所述。否则,请参见下面的原始答案。


正如您在自己的答案中提到的,没有内置方法可用于检查这一点。但是,如果您可以控制要检查的字符串/字符,那么使用稍微更有创意的方法实际上是可能的。
您可以绘制一个您知道在要检查的字体中缺失的字符,然后绘制一个您想知道是否存在的字符,最后比较它们并查看它们是否相同。
以下是一个代码示例,说明了我如何实现此检查:
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);
}

使用这种方法时需要注意一些重要的限制:

  • 你要检查的字符(作为 isCharacterMissing 的参数)必须是非空格字符(可以使用 Character.isWhitespace() 进行检测)。在某些字体中,缺失的字符会被呈现为空格,因此如果您比较存在的空格字符,则在这些字体中,它将不正确地报告为缺失字符。
  • missingCharacter 成员必须是已知在要检查的字体中缺失的字符。我正在使用的代码点 U+0978 是位于天城文Unicode块的保留代码点,因此它目前应该在所有Unicode兼容字体中都缺失,但是在未来,当新字符添加到Unicode中时,此代码点可能会分配给实际字符。因此,这种方法不具备未来性,但如果您自己提供字体,则可以确保每当更改应用程序的字体时都使用缺失字符。
  • 由于此检查是通过绘制位图并进行比较来完成的,因此效率不高,因此应仅用于检查一些字符,而不是应用程序中的所有字符串。

更新:

像U+0978这样的解决方案并不可行,因为正如其他人指出的那样,该字符是在Unicode 7.0发布于2014年6月时添加的。另一个选择是使用存在于Unicode中但极不可能在普通字体中使用的字形。

U+124AB位于早期楔形文字块中,可能在很多字体中都不存在。


2
库存 Nexus 4 具有 "\u0978" 的字形。但是,"\u2936" 似乎仍然缺失。 - Amir Uval
我应该使用什么来表示“BITMAP_WIDTH”? - c0dehunter
任何足够大,每个字符都会以不同的方式绘制。例如,如果您尝试仅使用2x2像素绘制不同字母,则许多不同的字母可能看起来相同。我认为我可能使用了类似16或20这样的东西。 - nibarius

8

从Android版本23开始,您可以这样测试:

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);

5
根据JavaDoc的说明:“检查是在整个后备链上进行的,而不仅仅是立即引用的字体。”因此,如果您只想知道它是否在指定的字体中,则这样做行不通。 - ashughes
要检查具有回退的确切字体文件,请尝试使用CustomFallbackBuilder。API29+。 - Seva Alekseyev

3

@nibarius的答案很好,不过字符不同:"\u2936"。

然而,那个解决方案在内存、CPU和时间上都很重。我需要一个适用于非常少量字符的解决方案,所以我通过仅检查边界来实现了一个“快速而简单”的解决方案。它的速度大约是原来的50倍,因此更适合我的情况:

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);
}

我应该提醒您,这种方法比 @nibarius 的方法不太准确,并且会出现一些假阴性和假阳性的情况,但如果您只需要测试几个字符以便能够回退到其他字符 - 这个解决方案非常好。


3

2

如果您仍希望您的应用程序与低于23的API兼容并且在代码中仍使用hasGlyph函数,则可以以以下方式使用@TargetApi(23):

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

然后在您的代码中:

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

唯一的问题是,在低于23的API中,您的应用程序可能会显示空白符号。但至少在API23+中,您的应用程序将是完美的。


1

为了检查字符串中是否显示了音乐符号♫(如果在某些设备上未显示),您可以尝试测量字符串的宽度;如果宽度为0,则该符号不存在。

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

0

我需要为我正在编写的适用于API 19的应用程序执行此操作。我正在查看一个支持多种不同字体的日语汉字应用程序。我使用以下Kotlin扩展函数。请注意,toCodepointStrings是一个简单的函数,它基于其代码点拆分字符串,因此它与字体无关。

private fun String.charsExistInFont(
        typeFace: Typeface): Boolean {
    val chars: Array<String> = this.toCodepointStrings()
    val p = Paint(Paint.ANTI_ALIAS_FLAG)
    p.typeface = typeFace
    val chk = chars.firstOrNull() {
        val r = Rect()
        p.getTextBounds(it, 0, 1, r)
        Log.d(TAG, "char width = ${r.width()}")
        r.width() == 0 && it != " " // rect is 0 width for space.
    }
    return chk == null
}

0
使用PaintCompat.hasGlyph,它依赖于API 23及以上版本中的Paint.hasGlyph,但对于旧版Android,它具有向后兼容的实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接