pdf转图片,支持设置转换字体,解决乱码问题

该工具使用了fontbox。

1、解决提示:pdfbox font STSong-Light转换图片缺少字体导致乱码问题

2、不需要设置系统的字体,如可以安装系统字体就不需要使用该方法设置字体的

引入依赖

<dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>fontbox</artifactId>
            <version>2.0.9</version>
        </dependency>

        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.9</version>
        </dependency>
  <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>

自定义继承实现了pdfbox字体信息类,用于实现设置字体信息

package cn.xx.common.pdf;

import cn.xx.RuntimeException;
import org.apache.fontbox.FontBoxFont;
import org.apache.fontbox.ttf.OTFParser;
import org.apache.fontbox.ttf.TTFParser;
import org.apache.fontbox.ttf.TrueTypeCollection;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.fontbox.type1.Type1Font;
import org.apache.pdfbox.pdmodel.font.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 自定义pdf字体信息
 * @author xwt
 * @date 2022/10/19 17:23
 */
public class MyFontInfo extends FontInfo{

    private final String postScriptName;
    private final FontFormat format;
    private final CIDSystemInfo cidSystemInfo;
    private final int usWeightClass;
    private final int sFamilyClass;
    private final int ulCodePageRange1;
    private final int ulCodePageRange2;
    private final int macStyle;
    private final File file;
    private final FontBoxFont boxFont;

    public MyFontInfo(File file, FontFormat format, String postScriptName,
                       CIDSystemInfo cidSystemInfo, int usWeightClass, int sFamilyClass,
                       int ulCodePageRange1, int ulCodePageRange2, int macStyle)
    {
        this.file = file;
        this.format = format;
        this.postScriptName = postScriptName;
        this.cidSystemInfo = cidSystemInfo;
        this.usWeightClass = usWeightClass;
        this.sFamilyClass = sFamilyClass;
        this.ulCodePageRange1 = ulCodePageRange1;
        this.ulCodePageRange2 = ulCodePageRange2;
        this.macStyle = macStyle;
        try {
            switch (format) {
                case PFB: this.boxFont = Type1Font.createWithPFB(new FileInputStream(file)); break;
                case OTF: this.boxFont = new OTFParser(false, true).parse(file); break;
                case TTF: this.boxFont = new TTFParser(false, true).parse(file); break;
                default: throw new CustomMsgException("can't happen");
            }
        }catch (IOException e){
            e.printStackTrace();
            throw new RuntimeException("pdfbox自定义字体失败!");
        }
    }

    public MyFontInfo(File file, FontFormat format, String postScriptName) {
        this(file, format, postScriptName, null, 0, 0, 0, 0, 0);
    }

    @Override
    public String getPostScriptName()
    {
        return postScriptName;
    }

    @Override
    public FontFormat getFormat()
    {
        return format;
    }

    @Override
    public CIDSystemInfo getCIDSystemInfo()
    {
        return cidSystemInfo;
    }

    /**
     * {@inheritDoc}
     * <p>
     * The method returns null if there is there was an error opening the font.
     *
     */
    @Override
    public FontBoxFont getFont(){
        return this.boxFont;
    }

    @Override
    public int getFamilyClass()
    {
        return sFamilyClass;
    }

    @Override
    public int getWeightClass()
    {
        return usWeightClass;
    }

    @Override
    public int getCodePageRange1()
    {
        return ulCodePageRange1;
    }

    @Override
    public int getCodePageRange2()
    {
        return ulCodePageRange2;
    }

    @Override
    public int getMacStyle()
    {
        return macStyle;
    }

    @Override
    public PDPanoseClassification getPanose()
    {
        return null;
    }

    @Override
    public String toString()
    {
        return super.toString() + " " + file;
    }
}

pdf工具类

package cn.xxx.utils;

import cn.xx.RuntimeException;
import cn.xx.FileExtEnum;
import cn.xxx.MyFontInfo; // 自定义字体信息类
import cn.hutool.core.text.CharSequenceUtil;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.font.*;
import org.apache.pdfbox.rendering.PDFRenderer;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

/**
 * pdf工具类
 *
 * @author xwt
 * @date 2022/12/7 11:00
 */
public class PdfUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(PdfUtils.class);

    public static final Base64.Decoder DECODER = Base64.getDecoder();

    private static Map<String, FontInfo> fontInfoMap = null;

    private static final String TTC_TYPE = "TTC";

    private static final String OTC_TYPE = "OTC";

    /***
     * pdfBase64转图片,返回base64编码集合
     *
     * @param pdfBase64  pdf类型base64
     * @return 图片base64
     */
    public static List<String> pdfToImageBase64(String pdfBase64){
        List<String> list = new ArrayList<>();
        if (CharSequenceUtil.isEmpty(pdfBase64)){
            return list;
        }
        return ImageUtils.imageToBase64(PdfUtils.pdfToImage(pdfBase64));
    }

    /***
     * pdfBase64转图片,返回图片对象
     *
     * @param pdfBase64  pdf类型base64
     * @return BufferedImage
     */
    public static List<BufferedImage> pdfToImage(String pdfBase64){
        List<BufferedImage> list = new ArrayList<>();
        if (CharSequenceUtil.isEmpty(pdfBase64)){
            return list;
        }
        byte[] decode = DECODER.decode(pdfBase64.getBytes(StandardCharsets.UTF_8));
        try(
                ByteArrayInputStream stream = new ByteArrayInputStream(decode);
                // 加载解析PDF文件
                PDDocument doc = PDDocument.load(stream);
        ) {
            // 业务处理
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            PDPageTree pages = doc.getPages();
            int pageCount = pages.getCount();
            for (int i = 0; i < pageCount; i++) {
                list.add(pdfRenderer.renderImageWithDPI(i, 200));
            }
        } catch (Exception e) {
            LOGGER.error("pdfBase64转图片异常", e);
        }
        return list;
    }

    /***
     * 设置字体
     * @param fontFormat 字体文件类型
     * @param fontName 字体名称
     * @param file 字体文件
     */
    public static void setFonts(FontFormat fontFormat, String fontName, File file){
        FontInfo fontInfo = getFontInfoMap().get(fontName);
        if(fontInfo != null){
            LOGGER.warn("pdfFont添加字体已经存在");
            return;
        }
        // 后缀
        LOGGER.info("pdfFont 添加字体{}", file.getName());
        String suffix = FileUtil.getSuffix(file);
        if(TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)){
            try(TrueTypeCollection trueTypeCollection = new TrueTypeCollection(file);) {
                trueTypeCollection.processAllFonts(trueTypeFont -> addTrueTypeFontImpl(trueTypeFont, file));
            } catch (IOException e) {
                LOGGER.warn("无法加载字体:{}", file.getAbsolutePath());
            }
            return;
        }
        getFontInfoMap().put(fontName, new MyFontInfo(file, fontFormat, fontName));
    }

    /***
     * 设置字体,扫描目录下所有字体
     * @param dir 目录
     */
    public static void setFonts(File dir){
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            LOGGER.warn("pdfFont添加字体为空");
            return;
        }
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                setFonts(file);
            } else {
                String fileName = file.getName();
                // 后缀
                String suffix = FileUtil.getSuffix(fileName);
                // 文件名称,不带后缀
                String prefix = FileUtil.getPrefix(fileName);
                if(FontFormat.OTF.name().equalsIgnoreCase(suffix)){
                    setFonts(FontFormat.OTF, prefix, file);
                }else if(FontFormat.TTF.name().equalsIgnoreCase(suffix) || TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.TTF, prefix, file);
                }else if(FontFormat.PFB.name().equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.PFB, prefix, file);
                }else{
                    LOGGER.warn("无法识别字体:{}", file.getAbsolutePath());
                }
            }
        }
    }

    /***
     * 获取系统字体缓存
     * @return 字体缓存
     */
    private static Map<String, FontInfo> getFontInfoMap(){
        if(fontInfoMap != null){
            return fontInfoMap;
        }
        FontMapper instance = FontMappers.instance();
        // 初始化加载系统字体
        instance.getCIDFont("STSong-Light", null, null);
        Class<? extends FontMapper> aClass = instance.getClass();
        try {
            Field field = aClass.getDeclaredField("fontInfoByName");
            if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
                field.setAccessible(true);
            }
            // 获取系统字体缓存
            fontInfoMap = (Map<String, FontInfo>) field.get(instance);
        }catch (Exception e){
            LOGGER.error("PdfUtils初始化异常", e);
            throw new CustomMsgException("PdfUtils初始化异常");
        }
        return fontInfoMap;
    }

    /***
     * 将OTF或TTF字体添加到文件缓存
     * @param ttf 字体
     * @param file 字体文件
     * @throws IOException 异常
     */
    private static void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException {
        if(ttf.getName() != null){
            if (ttf.getHeader() == null) {
                getFontInfoMap().put(ttf.getName(), new MyFontInfo(file, FontFormat.TTF, ttf.getName()));
                return;
            }
            int macStyle = ttf.getHeader().getMacStyle();

            int sFamilyClass = -1;
            int usWeightClass = -1;
            int ulCodePageRange1 = 0;
            int ulCodePageRange2 = 0;
            // Apple's AAT fonts don't have an OS/2 table
            if (ttf.getOS2Windows() != null)
            {
                sFamilyClass = ttf.getOS2Windows().getFamilyClass();
                usWeightClass = ttf.getOS2Windows().getWeightClass();
                ulCodePageRange1 = (int)ttf.getOS2Windows().getCodePageRange1();
                ulCodePageRange2 = (int)ttf.getOS2Windows().getCodePageRange2();
            }

            CIDSystemInfo ros = null;
            String registry = null;
            String ordering = null;
            int supplement = 0;
            FontFormat fontFormat;
            if (ttf instanceof OpenTypeFont && ((OpenTypeFont)ttf).isPostScript()) {
                CFFFont cff = ((OpenTypeFont)ttf).getCFF().getFont();
                fontFormat = FontFormat.OTF;
                if (cff instanceof CFFCIDFont) {
                    CFFCIDFont cidFont = (CFFCIDFont)cff;
                    registry = cidFont.getRegistry();
                    ordering = cidFont.getOrdering();
                    supplement = cidFont.getSupplement();
                }
            } else {
                fontFormat = FontFormat.TTF;
                if (ttf.getTableMap().containsKey("gcid")) {
                    // Apple's AAT fonts have a "gcid" table with CID info
                    byte[] bytes = ttf.getTableBytes(ttf.getTableMap().get("gcid"));
                    String reg = new String(bytes, 10, 64, Charsets.US_ASCII);
                    registry = reg.substring(0, reg.indexOf('\0'));
                    String ord = new String(bytes, 76, 64, Charsets.US_ASCII);
                    ordering = ord.substring(0, ord.indexOf('\0'));
                    supplement = bytes[140] << 8 & bytes[141];
                }
            }
            try {
                Constructor<CIDSystemInfo> constructor = CIDSystemInfo.class.getDeclaredConstructor(String.class, String.class, int.class);
                if ((!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers()) || Modifier.isFinal(constructor.getModifiers())) && !constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                ros = constructor.newInstance(registry, ordering, supplement);
            }catch (Exception e){
                e.printStackTrace();
            }
            getFontInfoMap().put(ttf.getName(), new MyFontInfo(file, fontFormat, ttf.getName(), ros, usWeightClass, sFamilyClass, ulCodePageRange1, ulCodePageRange2,
                    macStyle));
        }
    }
}

ImageUtils发布专栏的开发工具里面:https://mp.csdn.net/mp_blog/creation/editor/129165385

使用示例1,单文件,建议设置为单例

//设置字体 1、字体类型ttf\OTF\PFB,2、字体名称, 3、字体文件
// 尽量只设置一次字体,设置一次后会放到缓存中
 PdfUtils.setFonts(FontFormat.TTF, "STSong-Light", new File("D:\\ttf\\STSong-Light.ttf"));
 // 之后就可以使用转换为图片操作,不用再次重新设置
PdfUtils.pdfToImageBase64("");

使用示例2,目录下所有字体文件
 

// 1、目录下多文件字体使用
File file = new File("src/main/resources/fonts");
PdfUtils.setFonts(file);
// 注意spring boot,字体文件获取问题,下面有自用的解决办法

注意spring boot,jar内字体目录文件获取问题,下面是一种方法

http://t.csdn.cn/rZ5YD

然后使用获取目录下文件,传入设置字体

Application.class 文件所在jar包内class文件即可
File file = JarFileLoaderUtils.getResourcesFile("fonts", Application.class);
//设置字体 1、字体类型ttf\OTF\PFB,2、字体名称, 3、字体文件
// 尽量只设置一次字体,设置一次后会放到缓存中
 PdfUtils.setFonts(FontFormat.TTF, "STSong-Light", file);
 // 之后就可以使用转换为图片操作,不用再次重新设置
PdfUtils.pdfToImageBase64("");

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值