Java全面解析ttf字体的信息

 更新时间:2025年01月14日 15:05:00   作者:念九_ysl  
文章介绍了Java如何解析TTF字体文件,提取字体的基本信息、版本信息、版权和许可证、字符映射以及各种表格信息,通过代码解析,可以获取字体的详细描述和度量数据

Java解析ttf字体的信息

TTF(TrueType Font)文件格式复杂,包含了多个表格,每个表格存储了不同的字体信息。

前提是ttf要包含这些表格信息。

通过代码解析 TTF 文件,你可以获取以下内容:

public static final int COPYRIGHT = 0; // 版权信息
public static final int FAMILY_NAME = 1; // 字体家族名称
public static final int FONT_SUBFAMILY_NAME = 2; // 字体子家族名称(例如:粗体、斜体等)
public static final int UNIQUE_FONT_IDENTIFIER = 3; // 唯一字体标识符
public static final int FULL_FONT_NAME = 4; // 完整字体名称
public static final int VERSION = 5; // 字体版本信息
public static final int POSTSCRIPT_NAME = 6; // PostScript名称
public static final int TRADEMARK = 7; // 商标信息
public static final int MANUFACTURER = 8; // 制造商信息
public static final int DESIGNER = 9; // 设计师信息
public static final int DESCRIPTION = 10; // 字体描述
public static final int URL_VENDOR = 11; // 制造商的URL
public static final int URL_DESIGNER = 12; // 设计师的URL
public static final int LICENSE_DESCRIPTION = 13; // 授权描述
public static final int LICENSE_INFO_URL = 14; // 授权信息的URL  

以下是数据示例:

{0=Digitized data copyright © 2007, Google Corporation., 1=Droid Sans
Mono, 2=Regular, 3=Ascender - Droid Sans Mono, 4=Droid Sans Mono,
5=Version 1.00 build 113, 6=DroidSansMono, 7=Droid is a trademark of
Google and may be registered in certain jurisdictions., 8=Ascender
Corporation, 10=Droid Sans is a humanist sans serif typeface
designed for user interfaces and electronic communication.,
11=http://www.ascendercorp.com/,
12=http://www.ascendercorp.com/typedesigners.html, 13=Licensed under
the Apache License, Version 2.0,
14=http://www.apache.org/licenses/LICENSE-2.0, 18=䑲潩搠卡湳⁍潮�}

{0=Copyright 上海字魂网络科技有限公司, 1=字魂再见怪兽体(商用需授权), 2=Regular, 3=ZHZJGST_T,
4=字魂再见怪兽体(商用需授权), 5=Version 2.00, 6=ZHZJGST_T, 7=字魂 ®,
8=上海字魂网络科技有限公司, 9=字魂网, 10=字魂联名系列, 11=https://izihun.com/,
12=https://izihun.com/, 13=商用需购买授权,请访问: https://izihun.com/,
16=字魂再见怪兽体(商用需授权), 17=常规体} {0=Digitized data copyright © 2007,
Google Corporation., 1=Droid Sans Mono, 2=Regular, 3=Ascender -
Droid Sans Mono, 4=Droid Sans Mono, 5=Version 1.00 build 113,
6=DroidSansMono, 7=Droid is a trademark of Google and may be
registered in certain jurisdictions., 8=Ascender Corporation,
10=Droid Sans is a humanist sans serif typeface designed for user
interfaces and electronic communication.,
11=http://www.ascendercorp.com/,
12=http://www.ascendercorp.com/typedesigners.html, 13=Licensed under
the Apache License, Version 2.0,
14=http://www.apache.org/licenses/LICENSE-2.0, 18=䑲潩搠卡湳⁍潮�}

1.基本信息:

  • 字体名称:包括全名、家族名、子家族名等。
  • 版本信息:字体的版本号。
  • 版权和许可证:版权声明、许可描述等信息。

2.字符映射:

  • 字形表(glyf):包含了每个字符的实际轮廓(即矢量数据)。
  • 字形数据:定义了每个字符的图形表示方式。

3.表格信息:

  • 表目录(table directory):包含所有表格的名称、偏移量和长度。
  • 字体头(head):字体的基本信息,如字体的最小和最大字形的边界框。

4.度量信息:

  • 水平度量表(hmtx):包含每个字形的水平度量数据。
  • 垂直度量表(vmtx):包含每个字形的垂直度量数据。

5.表格详情:

  • 名称表(name):存储字体的各种名称信息,如字体的全名、家族名、子家族名等。
  • 字符映射表(cmap):定义了字符代码与字形索引之间的映射关系。
  • 头表(head):提供了字体的一些基本信息,如字体的版本、创建时间等。
  • 控制点表(loca):提供字形轮廓数据的偏移量。

6.额外的表格:

  • 垂直和水平指标表(hhea 和 vhea):包含了字体的水平和垂直基线指标。
  • 轮廓图像表(glyf):存储了每个字形的实际轮廓。

代码解析示例

1.建立工具类

package com.example.psd.font;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * TTF 字体文件解析器
 */
public class TTFParser {

    public static final int COPYRIGHT = 0;
    public static final int FAMILY_NAME = 1;
    public static final int FONT_SUBFAMILY_NAME = 2;
    public static final int UNIQUE_FONT_IDENTIFIER = 3;
    public static final int FULL_FONT_NAME = 4;
    public static final int VERSION = 5;
    public static final int POSTSCRIPT_NAME = 6;
    public static final int TRADEMARK = 7;
    public static final int MANUFACTURER = 8;
    public static final int DESIGNER = 9;
    public static final int DESCRIPTION = 10;
    public static final int URL_VENDOR = 11;
    public static final int URL_DESIGNER = 12;
    public static final int LICENSE_DESCRIPTION = 13;
    public static final int LICENSE_INFO_URL = 14;

    private final Map<Integer, String> fontProperties = new HashMap<>();

    /**
     * 获取全字体名称,如果不可用则获取字体家族名称。
     *
     * @return 字体名称
     */
    public String getFontName() {
        return fontProperties.getOrDefault(FULL_FONT_NAME, fontProperties.get(FAMILY_NAME));
    }

    /**
     * 获取特定的字体属性。
     *
     * @param nameID 属性 ID
     * @return 属性值
     */
    public String getFontProperty(int nameID) {
        return fontProperties.get(nameID);
    }

    /**
     * 获取所有字体属性。
     *
     * @return 字体属性映射
     */
    public Map<Integer, String> getFontProperties() {
        return fontProperties;
    }

    /**
     * 解析 TTF 文件并提取字体属性。
     *
     * @param fileName TTF 文件路径
     * @throws IOException 如果发生 I/O 错误
     */
    public void parse(String fileName) throws IOException {
        fontProperties.clear();
        try (RandomAccessFile raf = new RandomAccessFile(fileName, "r")) {
            parseInner(raf);
        }
    }

    private void parseInner(RandomAccessFile raf) throws IOException {
        int majorVersion = raf.readUnsignedShort();
        int minorVersion = raf.readUnsignedShort();
        int numOfTables = raf.readUnsignedShort();

        if (majorVersion != 1 || minorVersion != 0) {
            return; // 不支持的版本
        }

        // 跳到表目录
        raf.seek(12); // 跳过版本信息和表数

        boolean found = false;
        TableDirectory tableDirectory = new TableDirectory();

        for (int i = 0; i < numOfTables; i++) {
            byte[] nameBytes = new byte[4];
            raf.readFully(nameBytes);
            tableDirectory.name = new String(nameBytes, StandardCharsets.ISO_8859_1);
            tableDirectory.checkSum = raf.readInt();
            tableDirectory.offset = raf.readInt();
            tableDirectory.length = raf.readInt();

            if ("name".equalsIgnoreCase(tableDirectory.name)) {
                found = true;
                break;
            }
        }

        if (!found) {
            return; // 未找到 'name' 表
        }

        raf.seek(tableDirectory.offset);
        NameTableHeader nameTableHeader = new NameTableHeader();
        nameTableHeader.fSelector = raf.readUnsignedShort();
        nameTableHeader.nRCount = raf.readUnsignedShort();
        nameTableHeader.storageOffset = raf.readUnsignedShort();

        for (int i = 0; i < nameTableHeader.nRCount; i++) {
            NameRecord nameRecord = new NameRecord();
            nameRecord.platformID = raf.readUnsignedShort();
            nameRecord.encodingID = raf.readUnsignedShort();
            nameRecord.languageID = raf.readUnsignedShort();
            nameRecord.nameID = raf.readUnsignedShort();
            nameRecord.stringLength = raf.readUnsignedShort();
            nameRecord.stringOffset = raf.readUnsignedShort();

            long pos = raf.getFilePointer();
            raf.seek(tableDirectory.offset + nameRecord.stringOffset + nameTableHeader.storageOffset);
            byte[] bf = new byte[nameRecord.stringLength];
            raf.readFully(bf);
            String value = new String(bf, StandardCharsets.UTF_16);
            fontProperties.put(nameRecord.nameID, value);
            // 打印出可能的授权信息
            if (nameRecord.nameID == TTFParser.COPYRIGHT || nameRecord.nameID == TTFParser.LICENSE_DESCRIPTION) {
                System.out.println("Found information: " + value);
                System.out.println();
            }
            raf.seek(pos);
        }
    }

    /**
     * 检查字体文件中是否包含版权声明。
     *
     * @return 如果找到版权声明,返回 true;否则返回 false
     */
    public boolean hasCopyrightInfo() {
        return fontProperties.containsKey(COPYRIGHT);
    }

    /**
     * 检查字体文件中是否包含商用授权信息。
     *
     * @return 返回一个包含两个布尔值的数组,第一个表示是否允许商用,第二个表示是否需要授权。
     */
    public boolean[] checkCommercialUseAndAuthorization() {
        boolean isCommercialUseAllowed = false;
        boolean isAuthorizationRequired = false;

        String licenseDescription = fontProperties.get(LICENSE_DESCRIPTION);
        if (licenseDescription != null) {
            // 解析 licenseDescription 中的关键词来判断
            String descriptionLower = licenseDescription.toLowerCase();
            if (descriptionLower.contains("commercial use allowed")) {
                isCommercialUseAllowed = true;
            }
            if (descriptionLower.contains("authorization required")) {
                isAuthorizationRequired = true;
            }
        }

        String licenseInfoURL = fontProperties.get(LICENSE_INFO_URL);
        System.out.println("============111111" + fontProperties);
        if (licenseInfoURL != null) {
            // 可以通过访问此URL来确认授权信息
            System.out.println("检查授权信息,请访问: " + licenseInfoURL);
            // 假设如果有 URL 说明需要进一步确认授权
            isAuthorizationRequired = true;
        }

        return new boolean[]{isCommercialUseAllowed, isAuthorizationRequired};
    }

    @Override
    public String toString() {
        return fontProperties.toString();
    }

    private static class TableDirectory {
        String name;
        int checkSum;
        int offset;
        int length;
    }

    private static class NameTableHeader {
        int fSelector;
        int nRCount;
        int storageOffset;
    }

    private static class NameRecord {
        int platformID;
        int encodingID;
        int languageID;
        int nameID;
        int stringLength;
        int stringOffset;
    }
}

2.demo

package com.example.psd.font;

import com.example.psd.font.TTFParser;

import java.io.File;
import java.io.FilenameFilter;

public class TTFParserExample {

    public static void main(String[] args) {
        // 指定 TTF 文件所在目录
        File fontDir = new File("D:\\测试");

        // 列出目录中的所有 TTF 文件
        File[] files = fontDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".ttf");
            }
        });

        // 处理每个 TTF 文件
        if (files != null) {
            for (File file : files) {
                TTFParser parser = new TTFParser();
                try {
                    parser.parse(file.getAbsolutePath());
                    // 获取并打印字体名称
                    String fontName = parser.getFontName();
                    System.out.println("Font name: " + fontName);

                    // 如果需要获取其他属性,可以使用类似的方式
                    String familyName = parser.getFontProperty(TTFParser.FAMILY_NAME);
                    System.out.println("Family name: " + familyName);
                } catch (Exception e) {
                    System.err.println("Error parsing font file: " + file.getAbsolutePath());
                    e.printStackTrace();
                }
            }
        } else {
            System.out.println("No TTF files found in the specified directory.");
        }
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 关于Java内存访问重排序的研究

    关于Java内存访问重排序的研究

    文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则
    2025-01-01
  • java如何读取某个文件夹中的全部文件(包括子文件夹)

    java如何读取某个文件夹中的全部文件(包括子文件夹)

    这篇文章主要介绍了java如何读取某个文件夹中的全部文件(包括子文件夹),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • SpringCloud修改Feign日志记录级别过程浅析

    SpringCloud修改Feign日志记录级别过程浅析

    OpenFeign源于Netflix的Feign,是http通信的客户端。屏蔽了网络通信的细节,直接面向接口的方式开发,让开发者感知不到网络通信细节。所有远程调用,都像调用本地方法一样完成
    2023-02-02
  • idea与eclipse项目相互导入的过程(图文教程)

    idea与eclipse项目相互导入的过程(图文教程)

    这篇文章主要介绍了idea与eclipse项目相互导入的过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • Java Elastic Job动态添加任务实现过程解析

    Java Elastic Job动态添加任务实现过程解析

    这篇文章主要介绍了Java Elastic Job动态添加任务实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Maven多模块之父子关系的创建

    Maven多模块之父子关系的创建

    这篇文章主要介绍了Maven多模块之父子关系的创建,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • java实现文件上传和下载

    java实现文件上传和下载

    这篇文章主要为大家详细介绍了java实现文件上传和下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • IDEA 2020 本土化,真的是全中文了(真香)

    IDEA 2020 本土化,真的是全中文了(真香)

    去年,JetBrains 网站进行了本地化,提供了 8 种不同的语言版本,而现在,团队正在对基于 IntelliJ 的 IDE 进行本地化
    2020-12-12
  • SpringBoot整合Web之CORS支持与配置类和 XML配置及注册拦截器

    SpringBoot整合Web之CORS支持与配置类和 XML配置及注册拦截器

    这篇文章主要介绍了SpringBoot整合Web开发中CORS支持与配置类和 XML配置及注册拦截器的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 异常try catch的常见四类方式(案例代码)

    异常try catch的常见四类方式(案例代码)

    这篇文章主要介绍了异常try catch的常见四类方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05

最新评论