pdfbox pdf转图片中的字体问题

问题

在使用pdfbox转图片时,出现字体不支持的问题。通用的解决方式是将需要的字体安装到服务器来解决,但是这种方式比较鸡肋,如果我们是集群部署或者需要迁移服务器时,就必须多次安装字体,这是一种很不好的体验。

原因

pdfbox在转换pdf时,会从系统的字体库去初始化字体到内存中,如果要转换的字体已经存在,则直接使用该字体,如果该字体不存在,在源码中对该字体的替补字体进行配置,寻找可替代的字体转换,如果没有可替代的字体则不会输出文字内容。

解决

方案1:安装字体到系统

具体操作,将字体copy到/usr/share/fonts/路径下,使用命令   fc-cache -fv   刷新字体库即可。

方案2:为字体添加已存在的替补字体(需修改源码)。具体操作如下:

在pdfbox 项目中寻找类FontMapperImpl,按照图中红框的代码添加需要的字体规则

 

方案3:自定义添加字体,这里笔者做了两种处理。
一种是定义一个绝对路径,将字体放在该路径下,初始化时扫描加载这个字体库。具体做法如下: 找到FileSystemFontProvider,在他的构造其中添加绝对路径的字体扫描。可以看到笔者是通过系统变量pdfbox.fontsUrl来获取绝对变量的路径,默认路径为/usr/local/fonts/。这里可以自定义,怎么方便怎么来。eg:-Dpdfbox.fontsUrl=/root/pdf/resources/fonts 

 /**
     * Constructor.
     */
    FileSystemFontProvider(FontCache cache) {
        this.cache = cache;
        try {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Will search the local system for fonts");
            }

            //定义一个用于字体去重的set集合
            HashSet<String> fileSet = new HashSet<>();

            // scan the local system for font files
            List<File> files = new ArrayList<File>();
            FontFileFinder fontFileFinder = new FontFileFinder();
            List<URI> fonts = fontFileFinder.find();
            for (URI font : fonts) {
                File file = new File(font);
                files.add(file);
                fileSet.add(file.getName());
            }

            int localFonts=files.size();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Found " + localFonts + " fonts on the local system");
            }
            // scan pdfbox.fontsUrl for font files
            try {
                String customerFontsUrl = System.getProperty("pdfbox.fontsUrl");
                if (customerFontsUrl == null || customerFontsUrl.length() <= 0) {
                    customerFontsUrl = "/usr/local/fonts/";
                }
                File fontPath = new File(customerFontsUrl);
                LOG.warn("scan " + fontPath.getAbsolutePath() + " for font files");
                if (fontPath.exists()) {
                    File[] tempFile = fontPath.listFiles();
                    Arrays.asList(tempFile)
                            .forEach(item -> {
                                if (!fileSet.contains(item.getName())) {
                                    fileSet.add(item.getName());
                                    files.add(item);
                                }
                            });
                    LOG.warn("Found " + tempFile.length + " fonts on " + fontPath.getAbsolutePath());
                }else{
                    LOG.warn("Found 0 fonts on " + fontPath.getAbsolutePath());
                }
            } catch (Exception e) {
                LOG.warn("can't scan pdfbox.fontsUrl for font files", e);
            }

            //scan customer fonts for font files
            List<File> customerFonts = MyFontsScanner.scanCustomerFonts();
            customerFonts.forEach(item -> {
                        if (!fileSet.contains(item.getName())) {
                            fileSet.add(item.getName());
                            files.add(item);
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("san font " + item.getAbsolutePath());
                            }
                        }
                    });
            LOG.warn("Found " + customerFonts.size() + " fonts on resources/fonts");
            LOG.warn("Custom fonts total size:" + (files.size() - localFonts));

            // load cached FontInfo objects 将磁盘上缓存文件里的字体加载进来
            List<FSFontInfo> cachedInfos = loadDiskCache(files);
            if (cachedInfos != null && !cachedInfos.isEmpty()) {
                fontInfoList.addAll(cachedInfos);
            } else {
                //cachedInfos为空 ,代表有新的字体加入,重新构建缓存文件
                LOG.warn("Building on-disk font cache, this may take a while");
                LOG.warn("fonts total size:" + files.size());
                scanFonts(files);
                //重新构建缓存文件
                saveDiskCache();
                LOG.warn("Finished building on-disk font cache, found " +
                        fontInfoList.size() + " fonts");
                if (LOG.isDebugEnabled()) {
                    fontInfoList.forEach(x -> {
                        try {
                            LOG.debug(" include font: " + x.getFont().getName());
                        } catch (IOException e) {
                            LOG.warn(e);
                        }
                    });
                }
            }
        } catch (AccessControlException e) {
            LOG.error("Error accessing the file system", e);
        }
    }

 

另一种是将字体放在项目中的resources下,初始化时加载这些字体。具体做法如下,笔者先自定义了一个字体扫描类MyFontsScanner。这个扫描类的作用在于将resources下的字体库写入到一个临时目录中,然后再通过FileSystemFontProvider的加载方式进行加载。这里笔者固定了字体库的目录为resources/fonts/。注意:打包时不要编译字体文件,否则会导致不可用(可使用maven-resources-plugin插件的nonFilteredFileExtensions来排除)。

package org.apache.pdfbox.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author tangjianghua
 * date 2020/6/5
 * time 19:57
 */
public class MyFontsScanner {

    private static final Log LOG = LogFactory.getLog(MyFontsScanner.class);

    /**
     * java临时路径在系统变量中的key
     */
    public static final String JAVA_IO_TMPDIR = "java.io.tmpdir";

    /**
     * 默认自定义字体库路径在resources/fonts/下
     */
    public static final String BOOT_FONTS_PATH_REGEX = "^BOOT-INF\\/classes\\/fonts\\/[a-zA-Z_\\-0-9]+\\.(?i)(tt[cf])$";

    /**
     * scanCustomerFonts and return the list of fonts's absolute path.
     *
     * @author tangjianghua
     * date 2020/6/5
     */
    public static List<File> scanCustomerFonts() {
        List<File> fonts = new ArrayList<File>();
        try {
            String path = MyFontsScanner.class.getProtectionDomain().getCodeSource().getLocation().getPath();
            if (path.startsWith("file:")) {
                path = path.substring(5, path.length());
            }
            LOG.debug(path);
            if (path.contains("BOOT-INF")) {
                //springboot 模式
                path = path.split("!")[0];
                LOG.debug("项目所属路径:" + path);
                if (path.endsWith("jar")) {
                    @SuppressWarnings("resource")
                    //获得jar包路径
                            JarFile jFile = new JarFile(path);
                    Enumeration<JarEntry> jarEntrys = jFile.entries();
                    while (jarEntrys.hasMoreElements()) {
                        String name = jarEntrys.nextElement().getName();
                        LOG.trace("read jarEntry :" + name);
                        if (name.matches(BOOT_FONTS_PATH_REGEX)) {
                            File s = writeTempFonts(name);
                            if (LOG.isDebugEnabled()) {
                                LOG.trace("write " + name + " to " + s.getAbsolutePath());
                            }
                            fonts.add(s);
                        }
                    }
                }
            } else if (path.contains("WEB-INF")) {
                //tomcat模式
                path = path.substring(0, path.indexOf("WEB-INF")) + "WEB-INF/classes/fonts";
                LOG.debug("fonts path : " + path);
                File file = new File(path);
                if (file.exists() && file.isDirectory()) {
                    fonts.addAll(Arrays.asList(file.listFiles()));
                }
            } else {
                LOG.warn("请指定jar包所处路径,或者指定通过pdfbox.fontsUrl自定义字体库的路径");
            }
        } catch (IOException e) {
            LOG.error(e);
        }
        return fonts;
    }

    /**
     * scanCustomerFonts and return the list of fonts's absolute path.
     *
     * @author tangjianghua
     * date 2020/6/5
     */
    public static Set<String> scanCustomerFontsName() {
        Set<String> fonts = new HashSet<>();
        try {
            String path = MyFontsScanner.class.getProtectionDomain().getCodeSource().getLocation().getPath();
            if (path.startsWith("file:")) {
                path = path.substring(5, path.length());
            }
            LOG.debug(path);
            if (path.contains("BOOT-INF")) {
                //springboot 模式
                path = path.split("!")[0];
                LOG.debug("项目所属路径:" + path);
                if (path.endsWith("jar")) {
                    @SuppressWarnings("resource")
                    //获得jar包路径
                            JarFile jFile = new JarFile(path);
                    Enumeration<JarEntry> jarEntrys = jFile.entries();
                    while (jarEntrys.hasMoreElements()) {
                        String name = jarEntrys.nextElement().getName();
                        LOG.trace("read jarEntry :" + name);
                        if (name.matches(BOOT_FONTS_PATH_REGEX)) {
                            fonts.add(name);
                        }
                    }
                }
            } else if (path.contains("WEB-INF")) {
                //tomcat模式
                path = path.substring(0, path.indexOf("WEB-INF")) + "WEB-INF/classes/fonts";
                LOG.debug("fonts path : " + path);
                File file = new File(path);
                if (file.exists() && file.isDirectory()) {
                    Arrays.asList(file.listFiles())
                            .forEach(item -> {
                                fonts.add(item.getAbsolutePath().split("classes")[1]);
                            });
                }
            } else {
                LOG.warn("请指定jar包所处路径,或者指定通过pdfbox.fontsUrl自定义字体库的路径");
            }
        } catch (IOException e) {
            LOG.error(e);
        }
        return fonts;
    }

    /**
     * 将字体写入临时文件夹,并返回绝对路径
     *
     * @param entryName
     * @return 绝对路径
     * @author tangjianghua
     * date 2020/6/5
     */
    private static File writeTempFonts(String entryName) {
        String[] split = entryName.split("/");
        String fileName = split[split.length - 1];
        InputStream resourceAsStream = null;
        try {
            String fontsTempPath = System.getProperty(JAVA_IO_TMPDIR);
            if (fontsTempPath == null || fontsTempPath.length() <= 0) {
                String os = System.getProperty("os.name");
                if (os.contains("Linux")) {
                    fontsTempPath = "/tmp";
                } else if (os.contains("Windows")) {
                    fontsTempPath = System.getProperty("user.dir");
                }
            }
            fontsTempPath = fontsTempPath + File.separator + "pdfbox" + File.separator + "fonts";
            File fontsTempPathFile = new File(fontsTempPath);
            if (!fontsTempPathFile.exists()) {
                fontsTempPathFile.mkdirs();
            }
            File file = new File(fontsTempPath + File.separator + fileName);
            if (file.exists()) {
                return file;
            }
            resourceAsStream = Thread.currentThread().getClass().getResourceAsStream("/" + entryName);
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            byte[] bytes = new byte[1024];
            int i;
            while ((i = resourceAsStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, i);
            }
            fileOutputStream.flush();
            fileOutputStream.close();
            resourceAsStream.close();
            return file;
        } catch (Exception e) {
            LOG.warn("fail to write font into tmp path!", e);
            return null;
        } finally {
            if (resourceAsStream != null) {
                try {
                    resourceAsStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用时可以根据自己的情况进行选择使用。

git地址:https://github.com/tang-jianghua/pdfbox 可直接打包

您好,对于 PDFBox 解析报错问题,可以提供以下解决方案: 1. 检查 PDF 文件是否损坏,可以通过其他工具如 Adobe Acrobat Pro 等打开该 PDF 文件进行检查。 2. 检查 PDF 文件是否加密,如果是则需要提供密码进行解密。 3. 检查 PDF 文件是否包含非标准字体,如果是则需要将字体文件手动添加到 PDFBox字体目录下。 对于 PDFBox 图片报错 JPEG2000 image 的问题,可能是由于缺少相关依赖库导致的。您可以尝试在项目添加以下依赖: ```xml <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox-tools</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>jbig2-imageio</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>fontbox</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>xmpbox</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox-reactor</artifactId> <version>2.0.23</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox-debugger</artifactId> <version>2.0.23</version> </dependency> ``` 如果还是无法解决问题,可以尝试升级 PDFBox 版本或者使用其他 PDF 换工具。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值