xhtmlrenderer + iText-HTML转PDF

xhtmlrenderer + iText - HTML转PDF

xhtmlrendere+itext2.0.8 将html转成pdf,带样式、图片(也支持二维码、条形码)等

主要步骤
  1. 生成html(css样式直接放在style中)
  2. html转换pdf方法
  3. 数据返回给前端
详细过程
  1. html模板:
private static final String DEFAULT_HTML = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
            "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
            "<head>\n" +
            "<meta charset=\"utf-8\" />\n" +
            "<style> \n" +
            "  body{ padding:0; margin:0; font-family:Microsoft YaHei; @page {size:20mm, 35mm;}} \n" +
            "</style>\n" +
            "</head>\n" +
            "<body>\n" +
            "  ${CONTENT}\n" +
            "</body>\n" +
            "</html>";

实际内容替换DEFAULT_HTML中的${CONTENT}

2.html转pdf
在这里插入图片描述
方法代码:

    public static void htmlToPdf2(String html, ByteArrayOutputStream os) throws IOException {
            try {
                ITextRenderer renderer = new ITextRenderer();
                renderer.setDocumentFromString(html);
                ITextFontResolver fontResolver = renderer.getFontResolver();
                // 获取字体文件路径
                fontResolver.addFont(getFontPath2(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
                renderer.getSharedContext().setReplacedElementFactory(new ImgReplacedElementFactory());
                renderer.layout();
                renderer.createPDF(os);
                os.flush();
            } catch (Exception e) {
                logger.error("Html:" + html);
                logger.error("Html To Pdf Failed", e);
    //            throw new CommonException("Html To Pdf Failed:" + e.getMessage());
            } finally {
                if (os != null) {
                    os.close();
                }
            }
        }
        
    // 字体路径  
    private static String getFontPath2() {
            return HrptConstants.FONT_PATH + File.separator + HrptConstants.TTF_NAME_2;
        }
    /**
     * <p>
     * 图片处理优化-支持html中img标签的src为url或者base64
     * </p>
     */
    public class ImgReplacedElementFactory implements ReplacedElementFactory {
    
        private final static String IMG_ELEMENT_NAME = "img";
        private final static String SRC_ATTR_NAME = "src";
        private final static String URL_PREFIX_NAME = "data:image";
        private final static String URL_BASE64 = "base64,";
    
        private final static Logger LOGGER = LoggerFactory.getLogger(ImgReplacedElementFactory.class);
    
        @Override
        public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) {
            Element e = box.getElement();
            if (e == null) {
                return null;
            }
            String nodeName = e.getNodeName();
            // 找到img标签
            if (nodeName.equals(IMG_ELEMENT_NAME)) {
                String url = e.getAttribute(SRC_ATTR_NAME);
                FSImage fsImage;
                try {
                    InputStream imageStream = this.getImageStream(url, BaseConstants.Digital.ZERO);
                    byte[] bytes = IOUtils.toByteArray(imageStream);
                    // 生成itext图像
                    fsImage = new ITextFSImage(Image.getInstance(bytes));
                } catch (Exception e1) {
                    fsImage = null;
                }
                if (fsImage != null) {
                    // 对图像进行缩放
                    if (cssWidth != -1 || cssHeight != -1) {
                        fsImage.scale(cssWidth, cssHeight);
                    }
                    return new ITextImageElement(fsImage);
                }
            }
            return null;
        }
    
        @Override
        public void reset() {
    
        }
    
        @Override
        public void remove(Element e) {
    
        }
    
        @Override
        public void setFormSubmissionListener(FormSubmissionListener listener) {
    
        }
    
        /**
         * 重复获取网络图片3次,若三次失败则不再获取
         * @param url
         * @param tryCount
         * @return
         */
        private InputStream getImageStream(String url, int tryCount) {
            if (tryCount > BaseConstants.Digital.TWO) {
                return null;
            }
            if (URL_PREFIX_NAME.equals(url.substring(0, 10))) {
                byte[] bytes = Base64.decode(url.substring(url.indexOf(URL_BASE64) + 7));
                //转化为输入流
                return new ByteArrayInputStream(bytes);
            }
            try {
                HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
                connection.setReadTimeout(5000);
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
                if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = connection.getInputStream();
                    return inputStream;
                } else {
                    tryCount += 1;
                    LOGGER.info("connectionError : {} , msg : {}", connection.getResponseCode(), connection.getResponseMessage());
                    return getImageStream(url, tryCount);
                }
            } catch (IOException e) {
                LOGGER.error("connectionIOException : {} , trace : {}", e.getMessage(), e.getStackTrace());
            }
            return null;
        }
    
    }

3.最后通过response导出pdf给前端

最后,对于在开发过程中碰到的问题,做下记录和总结。

  • 字体问题,汉字不显示

html模板body里面font-family属性不要落了
在这里插入图片描述

字体路径要找的到你的字体文件
在这里插入图片描述

font-family属性中的字体要和应用的字体文件字体相对应,举例:font-family中设置的是Microsoft YaHei字体,那么添加的字体文件就一定要是微软雅黑的(图中的msyh.ttc就是微软雅黑的字体文件)

字体下载:常用的字体在windows的自带font文件夹下基本上都有,实在没有就去网上自己找吧

  • html中的img图片标签后缀问题

xhtmlrenderer会对html转成xml,所以对于html格式要求严格,现在前端生成的html中img标签往往都是不带后缀的,所以在接口调用时会报错,小问题,把html中的img加上后缀就好

        // templateContent -- html内容
        Document doc = Jsoup.parse(templateContent);
        // img标签后缀处理
        Elements img = doc.getElementsByTag("img");
        if (!img.isEmpty()) {
            for (Element element:img) {
                if (!element.toString().contains("/>") && !element.toString().contains("</img>")) {
                    templateContent = templateContent.replace(element.toString(),element.toString() + "</img>");
                }
            }
        }
  • 接口报错,html转pdf报错。
    Html To Pdf Failed:Cant load the XML resource (using TRaX transformer). org.w 3c.dom.DOMException: NOT_FOUND_ERR: An attempt is made to reference a node in a context where it does n ot exist.
    在这里插入图片描述

这个问题蛮困扰的,html明明没有问题,然后一步一步debug发现,是因为html中有些标签中加了id属性导致的,根据源码看到的是,id转xml默认给的namespace都是空字符串而导致,查看http://www.w3.org/1999/xhtml也没看到说div标签和img标签支持id属性,最后做了html字符串处理,把id替换成了title

    // id处理,Element对象不支持id属性
    templateContent = templateContent.replaceAll("id=","title=");
  • img图片src为base64 code出现的一点问题

这里的业务场景是HTML中会有二维码或者条形码,用的都是img标签,后端会使用实际数据(这里是资产的编码)的二进制内容转成base64编码然后放入src中
替换,主要注意要加前缀URL_PREFIX_NAME :

    private final static String ID_BAR = "stylesBarCode";
    private final static String ID_QR = "stylesQrCode";
    private final static String SRC_ATTR_NAME = "src";
    private final static String URL_PREFIX_NAME = "data:image/png;base64,";
    private final static String CHARACTER_ENCODING = "utf-8";

// img的src处理
        Element barElement = doc.getElementById(ID_BAR);
        Element qrElement = doc.getElementById(ID_QR);
        Base64.Encoder encoder = Base64.getEncoder();
        if (barElement != null) {
            byte[] bytes = CodeUtils.generateBarCode(assetVO.getAspAssetNum(), 60, 5, CHARACTER_ENCODING, "code39");
            templateContent = templateContent.replace(barElement.attr(SRC_ATTR_NAME), URL_PREFIX_NAME + encoder.encodeToString(bytes));
        }
        if (qrElement != null) {
            byte[] bytes = CodeUtils.generateQrCode(assetVO.getAspAssetNum(), 20, 20, CHARACTER_ENCODING);
            templateContent = templateContent.replace(qrElement.attr(SRC_ATTR_NAME), URL_PREFIX_NAME + encoder.encodeToString(bytes));
        }

工具方法,生成二维码,生成条形码(用的zxing):

/**
     * 生成二维码
     *
     * @param text              内容
     * @param width             宽
     * @param height            高
     * @param characterEncoding 字符编码
     * @return 二进制内容
     */
    public static byte[] generateQrCode(String text, int width, int height, String characterEncoding) {
        QRCodeWriter writer = new QRCodeWriter();
        HashMap<EncodeHintType, Object> config = new HashMap<>(BaseConstants.Digital.SIXTEEN);
        config.put(EncodeHintType.CHARACTER_SET, characterEncoding);
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            BitMatrix bar = writer.encode(text, BarcodeFormat.QR_CODE, width, height, config);
            MatrixToImageWriter.writeToStream(bar, HrptConstants.ImageType.PNG, stream);
            return stream.toByteArray();
        } catch (Exception e) {
            throw new CommonException(HrptMessageConstants.ERROR_GENERATE_QRCODE);
        }
    }

    /**
     * 生成条形码
     *
     * @param text              内容
     * @param width             宽
     * @param height            高
     * @param characterEncoding 字符编码
     * @param barCodeType       条形码类型
     * @return 二进制内容
     */
    public static byte[] generateBarCode(String text, int width, int height, String characterEncoding, String barCodeType) {
        BarCodeType codeType = BarCodeType.valueOf2(barCodeType);
        switch (codeType) {
            case CODE_39:
                return generateBarCode39(text, width, height, characterEncoding);
            case CODE_93:
                return generateBarCode93(text, width, height, characterEncoding);
            case CODE_128:
                return generateBarCode128(text, width, height, characterEncoding);
            default:
                throw new CommonException(HrptMessageConstants.UNSUPPORTED_CODE_TYPE);
        }

    }
    
    /**
     * 生成Code39条形码
     *
     * @param text              内容
     * @param width             宽
     * @param height            高
     * @param characterEncoding 字符编码
     * @return 二进制内容
     */
    public static byte[] generateBarCode39(String text, int width, int height, String characterEncoding) {
        Code39Writer writer = new Code39Writer();
        HashMap<EncodeHintType, Object> config = new HashMap<>(BaseConstants.Digital.SIXTEEN);
        config.put(EncodeHintType.CHARACTER_SET, characterEncoding);
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            BitMatrix bar = writer.encode(text, BarcodeFormat.CODE_39, width, height, config);
            MatrixToImageWriter.writeToStream(bar, HrptConstants.ImageType.PNG, stream);
            return stream.toByteArray();
        } catch (Exception e) {
            throw new CommonException(HrptMessageConstants.ERROR_GENERATE_BARCODE);
        }
    }

    /**
     * 生成Code93条形码
     *
     * @param text              内容
     * @param width             宽
     * @param height            高
     * @param characterEncoding 字符编码
     * @return 二进制内容
     */
    public static byte[] generateBarCode93(String text, int width, int height, String characterEncoding) {
        Code93Writer writer = new Code93Writer();
        HashMap<EncodeHintType, Object> config = new HashMap<>(BaseConstants.Digital.SIXTEEN);
        config.put(EncodeHintType.CHARACTER_SET, characterEncoding);
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            BitMatrix bar = writer.encode(text, BarcodeFormat.CODE_93, width, height, config);
            MatrixToImageWriter.writeToStream(bar, HrptConstants.ImageType.PNG, stream);
            return stream.toByteArray();
        } catch (Exception e) {
            throw new CommonException(HrptMessageConstants.ERROR_GENERATE_BARCODE);
        }
    }

    /**
     * 生成Code128条形码
     *
     * @param text              内容
     * @param width             宽
     * @param height            高
     * @param characterEncoding 字符编码
     * @return 二进制内容
     */
    public static byte[] generateBarCode128(String text, int width, int height, String characterEncoding) {
        Code128Writer writer = new Code128Writer();
        HashMap<EncodeHintType, Object> config = new HashMap<>(BaseConstants.Digital.SIXTEEN);
        config.put(EncodeHintType.CHARACTER_SET, characterEncoding);
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            BitMatrix bar = writer.encode(text, BarcodeFormat.CODE_128, width, height, config);
            MatrixToImageWriter.writeToStream(bar, HrptConstants.ImageType.PNG, stream);
            return stream.toByteArray();
        } catch (Exception e) {
            throw new CommonException(HrptMessageConstants.ERROR_GENERATE_BARCODE);
        }
    }
  • ImgReplacedElementFactory类中的getImageStream方法(代码上文贴过了)

如果是base64地址的就不用http请求了,直接转二进制再转InputStream,需要注意的是Base64的import不要用错了,否则图片解析不出

import com.lowagie.text.pdf.codec.Base64;

以上就是我在实际开发过程中遇到的问题和解决方法,在这里做个记录,也希望对其他人有所帮助。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: 好的,以下是一个使用iTextHTML换为PDF的帮助类的示例代码: ``` import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.PageSize; import com.itextpdf.text.html.simpleparser.HTMLWorker; import com.itextpdf.text.pdf.PdfWriter; import java.io.*; import java.util.List; public class HtmlToPdfConverter { public static void convert(String htmlFilePath, String pdfFilePath) throws IOException, DocumentException { Document document = new Document(PageSize.A4); PdfWriter.getInstance(document, new FileOutputStream(pdfFilePath)); document.open(); HTMLWorker htmlWorker = new HTMLWorker(document); FileInputStream fileInputStream = new FileInputStream(htmlFilePath); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); List<?> htmlList = htmlWorker.parseToList(inputStreamReader, null); for (Object element : htmlList) { document.add((com.itextpdf.text.Element) element); } document.close(); } } ``` 在这个帮助类中,我们使用了iText库的`Document`、`PdfWriter`和`HTMLWorker`类来将HTML换为PDF。该类有一个名为`convert`的静态方法,该方法接受两个参数:HTML文件的路径和生成PDF文件的路径。在方法中,我们首先创建一个A4大小的`Document`对象,然后使用`PdfWriter`将其与输出文件流关联起来。接着,我们创建一个`HTMLWorker`对象,然后使用`FileInputStream`和`InputStreamReader`将HTML文件读取为`List`类型的对象,最后通过循环将其添加到`Document`中。最后,我们调用`Document`的`close`方法来关闭生成的PDF文件。 请注意,此示例代码仅用于演示目的,并不是生产环境下完整的代码,您需要根据您的实际需求进行调整和扩展。 ### 回答2: 帮助类的设计思路如下: 首先,我们需要引入iText库,将其添加到项目的依赖中。 接下来定义一个名为HtmlToPdfHelper的类,该类包含一个convert方法,用于将HTML文件换为PDF文件。该方法需要接收两个参数:htmlFilePath(表示待换的HTML文件路径)和pdfFilePath(表示生成的PDF文件路径)。 在convert方法内部,我们需要进行以下操作: 1. 创建一个Document对象,并设置页面大小和页边距。 2. 创建一个PdfWriter对象,将Document对象与pdfFilePath绑定。 3. 使用ITextRenderer类创建一个ITextRenderer对象,并将htmlFilePath传入其构造函数。 4. 调用ITextRenderer对象的layout方法,将HTML文件布局。 5. 调用ITextRenderer对象的createPDF方法,将布局后的文件保存到Document对象中。 6. 关闭Document对象和PdfWriter对象,完成PDF文件的生成。 下面是完整的HtmlToPdfHelper类的代码示例: ```java import com.lowagie.text.Document; import com.lowagie.text.PageSize; import com.lowagie.text.pdf.PdfWriter; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.FileOutputStream; public class HtmlToPdfHelper { public static void convert(String htmlFilePath, String pdfFilePath) throws Exception { Document document = new Document(PageSize.A4); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(pdfFilePath)); document.open(); ITextRenderer renderer = new ITextRenderer(); renderer.setDocument(htmlFilePath); renderer.layout(); renderer.createPDF(writer.getDirectContent(), writer.getPageSize()); document.close(); writer.close(); } } ``` 使用上述的帮助类,我们可以将HTML文件换为PDF文件,只需调用convert方法,并传入合适的参数即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值