You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Android中如何通过字体家族名或PostScript名查询字体并获取其文件路径/缓冲区?

在Android中如何通过字体家族名或PostScript名查询字体并获取其文件路径/缓冲区?

我来帮你梳理下可行的解决方案,不管是Java/Kotlin还是C语言层面都有对应的办法,咱们一步步来:

一、Java/Kotlin 实现方案(API 26+ 适用)

Android 8.0 之后提供了 FontManager 类,可以直接获取系统中所有已安装的字体,然后通过遍历匹配你需要的家族名或PostScript名,进而拿到字体的文件路径或缓冲区。另外,如果你已经用 Typeface.create() 创建了字体实例,也可以从它里面提取出对应的字体信息。

1. 直接通过FontManager查询系统字体

import android.graphics.fonts.Font
import android.graphics.fonts.FontManager
import android.graphics.fonts.FontFamily
import android.os.Build
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.O)
fun findFontByFamilyOrPostScriptName(targetName: String): Font? {
    val fontManager = FontManager.getInstance()
    // 获取系统所有字体家族
    val systemFontFamilies = fontManager.systemFontFamilies
    for (fontFamily in systemFontFamilies) {
        // 遍历字体家族中的每个字体实例
        for (font in fontFamily.fonts) {
            // 匹配家族名或PostScript名(忽略大小写)
            if (font.name.equals(targetName, ignoreCase = true) || 
                font.postScriptName.equals(targetName, ignoreCase = true)) {
                return font
            }
        }
    }
    return null
}

// 使用示例:获取字体路径和缓冲区
@RequiresApi(Build.VERSION_CODES.O)
fun getFontInfo(targetName: String) {
    val font = findFontByFamilyOrPostScriptName(targetName) ?: return
    
    // 获取字体文件路径
    val filePath = font.filePath
    println("字体文件路径:$filePath")
    
    // 获取字体缓冲区(如果字体是内存加载的)
    font.buffer?.let { buffer ->
        println("字体缓冲区长度:${buffer.limit()}")
        // 可将缓冲区转为字节数组使用
        val fontBytes = ByteArray(buffer.remaining())
        buffer.get(fontBytes)
    }
    
    // 也可通过FileDescriptor读取字体文件
    font.fontFileDescriptor?.let { fd ->
        // 这里可以用FileInputStream读取fd对应的文件内容
    }
}

2. 从已创建的Typeface中提取字体信息

如果你已经通过 Typeface.create(familyName, style) 创建了Typeface,也可以这样获取字体的具体信息:

@RequiresApi(Build.VERSION_CODES.O)
fun getFontFromTypeface(typeface: Typeface): Font? {
    val fontFamilies = typeface.fontFamilies
    if (fontFamilies.isNotEmpty()) {
        // 通常取第一个字体家族中的第一个字体实例(可根据style调整)
        return fontFamilies[0].fonts.firstOrNull()
    }
    return null
}

二、C语言(NDK)实现方案

在NDK层面,Android 10(API 29)及以上提供了 AFontManager 相关API,可以直接遍历系统字体;如果是更低版本,你可以通过JNI调用上面的Java代码,或者使用底层的字体检索逻辑。

1. 基于AFontManager的直接查询(API 29+)

#include <android/font_manager.h>
#include <android/log.h>
#include <string.h>
#include <stdlib.h>

#define LOG_TAG "FontQuery"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

AFont* find_font_by_name(const char* target_name) {
    AFontManager* font_manager = AFontManager_getInstance();
    if (!font_manager) {
        LOGD("Failed to get AFontManager instance");
        return NULL;
    }

    // 获取系统字体家族列表
    AFontFamily** families = NULL;
    int family_count = 0;
    if (AFontManager_getSystemFontFamilies(font_manager, &families, &family_count) != ANDROID_OK) {
        LOGD("Failed to get system font families");
        AFontManager_delete(font_manager);
        return NULL;
    }

    AFont* matched_font = NULL;
    for (int i = 0; i < family_count; i++) {
        AFontFamily* family = families[i];
        int font_count = AFontFamily_getFontCount(family);
        for (int j = 0; j < font_count; j++) {
            AFont* font = AFontFamily_getFont(family, j);
            // 获取字体家族名和PostScript名
            const char* family_name = AFont_getFamilyName(font);
            const char* postscript_name = AFont_getPostScriptName(font);
            
            // 匹配目标名称(忽略大小写)
            if ((family_name && strcasecmp(family_name, target_name) == 0) ||
                (postscript_name && strcasecmp(postscript_name, target_name) == 0)) {
                matched_font = font;
                // 注意:返回的font是family持有的引用,不要提前释放
                goto cleanup;
            }
        }
    }

cleanup:
    // 释放家族列表资源
    for (int i = 0; i < family_count; i++) {
        AFontFamily_delete(families[i]);
    }
    free(families);
    AFontManager_delete(font_manager);
    return matched_font;
}

// 使用示例:获取字体路径或缓冲区
void get_font_info(AFont* font) {
    if (!font) return;

    // 获取字体文件路径
    const char* file_path = AFont_getFilePath(font);
    if (file_path) {
        LOGD("Font file path: %s", file_path);
    }

    // 获取字体缓冲区(如果存在)
    const uint8_t* buffer = NULL;
    size_t buffer_size = 0;
    if (AFont_getBuffer(font, &buffer, &buffer_size) == ANDROID_OK) {
        LOGD("Font buffer size: %zu", buffer_size);
        // buffer是只读的,可直接使用
    }
}

2. 低版本NDK兼容方案(API <29)

如果你的应用需要兼容Android 10以下的版本,最稳妥的方式是通过JNI调用前面的Java/Kotlin代码,这样可以复用Android框架的字体检索逻辑,避免自己实现底层的字体扫描。

关于你提到的几个问题的补充说明

  • AFontMatcher_match的局限:这个API确实只能匹配W3C定义的通用字体家族(比如sans-serifserif),无法用于匹配具体的自定义家族名或PostScript名,所以遍历系统字体列表是更合适的方案。
  • Typeface.create后的信息提取:在API26+的版本中,Typeface提供了getFontFamilies()方法,可以拿到对应的FontFamily集合,进而提取出Font实例,再获取路径或缓冲区。
  • Font类的创建限制:Font类本身确实不能直接通过家族名或PostScript名创建,但可以通过FontManager获取系统字体列表后筛选得到,这也是上面方案的核心逻辑。

备注:内容来源于stack exchange,提问作者jeremie bergeron

火山引擎 最新活动