富文本导出为Word 和 PDF格式

61 阅读3分钟

前言

在 Open-Idea 的上个版本我们实现了将文档内容导出为 Word 格式和 PDF 格式,今天给大家揭秘下我是如何实现的。富文本文档支持导出为 Word 和 PDF 后端能做,其实前端也能做,我这里选中通过后端来实现,原因有以下两点:

  1. 后端处理大文本导出会更快
  2. 后端校验导出权限更方便

1. 下面是用到的依赖及版本

<!-- itext7html转pdf -->  
<dependency>  
    <groupId>com.itextpdf</groupId>  
    <artifactId>html2pdf</artifactId>  
    <version>5.0.3</version>  
</dependency>  
  
<!-- 中文字体支持 -->  
<dependency>  
    <groupId>com.itextpdf</groupId>  
    <artifactId>font-asian</artifactId>  
    <version>8.0.3</version>  
</dependency>  
  
<!-- html 转 word -->  
<dependency>  
    <groupId>org.apache.poi</groupId>  
    <artifactId>poi</artifactId>  
    <version>4.1.2</version>  
</dependency>  
  
<dependency>  
    <groupId>org.apache.poi</groupId>  
    <artifactId>poi-ooxml</artifactId>  
    <version>4.1.2</version>  
</dependency>  
  
<dependency>  
    <groupId>org.apache.poi</groupId>  
    <artifactId>poi-ooxml-schemas</artifactId>  
    <version>4.1.2</version>  
</dependency>  
  1. Controller 处理请求
@PostMapping("/word")  
public void exportWord(HttpServletRequest request, HttpServletResponse response, @RequestBody @Valid IdeaReportDTO reportDTO) throws Exception {  
    ideaDocExportService.exportWord(request, response, reportDTO);  
}  
  
@PostMapping("/pdf")  
public void exportPdf(HttpServletRequest request, HttpServletResponse response, @RequestBody @Valid IdeaReportDTO reportDTO) throws Exception {  
    ideaDocExportService.exportPdf(request, response, reportDTO);  
}  
  1. 导出 Word
/**  
* @param request  
* @param response  
* @param content 富文本内容(html)  
* @param fileName 生成word文件  
* @throws Exception  
*/  
private static void exportWord(HttpServletRequest request, HttpServletResponse response, String content, String fileName) throws Exception {  
    ByteArrayInputStream byteArrayInputStream = null;  
    POIFSFileSystem poifsFileSystem = null;  
    ServletOutputStream servletOutputStream = null;  
    try {  
        byte b[] = content.getBytes("GBK"); //这里是必须要设置编码的,不然导出中文就会乱码。  
        byteArrayInputStream = new ByteArrayInputStream(b);//将字节数组包装到流中  
        poifsFileSystem = new POIFSFileSystem();  
        DirectoryEntry directory = poifsFileSystem.getRoot();  
        directory.createDocument("WordDocument", byteArrayInputStream); //该步骤不可省略,否则会出现乱码。  
        //输出文件  
        request.setCharacterEncoding("utf-8");  
        response.setContentType("application/msword");//导出word格式  
        response.addHeader("Content-Disposition", "attachment;filename=" + fileName + ".doc");  
        servletOutputStream = response.getOutputStream();  
        poifsFileSystem.writeFilesystem(servletOutputStream);  
    } catch (Exception e) {  
        log.error("exportWord 生成word出错:",e);  
        resetResponse(response, e);  
    } finally {  
        byteArrayInputStream.close();  
        servletOutputStream.close();  
        poifsFileSystem.close();  
    }  
}  
  
private static void resetResponse(HttpServletResponse response, Exception exception)throws IOException {  
    // 重置response  
    response.reset();  
    response.setContentType("application/json");  
    response.setCharacterEncoding("utf-8");  
    String result = "下载文件失败," + exception.getMessage();  
    response.getWriter().println(result);  
}
  1. 导出 PDF
private static void exportPDF(HttpServletRequest request, HttpServletResponse response, String content, String fileName) throws IOException {  
    try{  
        //输出文件  
        request.setCharacterEncoding("utf-8");  
        response.setContentType("application/pdf");  
        response.setCharacterEncoding("utf-8");  
        String encodeFileName = URLEncoder.encode(fileName, "UTF-8");  
        response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName);  
        InputStream stringStream = HtmlToPdfUtils.getStringStream(content);  
        HtmlToPdfUtils.convertToPdf(stringStream, "OpenByteCode", null, response.getOutputStream());  
    } catch (Exception e) {  
        log.error("exportPdf生成pdf出错:",e);  
        resetResponse(response, e);  
    }  
  
}  
/**  
* Itext7转换工具类  
*/  
@Slf4j  
public class HtmlToPdfUtils {  
  
    /**  
    * html转pdf  
    *  
    * @param inputStream 输入流  
    * @param waterMark 水印  
    * @param fontPath 字体路径,ttc后缀的字体需要添加<b>,0<b/>  
    * @param outputStream 输出流  
    * @date : 2021/1/15 14:07  
    */  
    public static void convertToPdf(InputStream inputStream, String waterMark, String fontPath, OutputStream outputStream) throws IOException {  
  
        PdfWriter pdfWriter = new PdfWriter(outputStream);  
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);  
        //设置为A4大小  
        pdfDocument.setDefaultPageSize(PageSize.A4);  
        //添加水印  
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new WaterMarkEventHandler(waterMark));  
        //添加分页器,但是目前分页器有点问题,总页数计算有问题  
        //pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PageEventHandler());  

        //添加中文字体支持  
        ConverterProperties properties = new ConverterProperties();  
        FontProvider fontProvider = new FontProvider();  

        //设置字体  
        PdfFont sysFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");  
        fontProvider.addFont(sysFont.getFontProgram(), "UniGB-UCS2-H");  

        //添加自定义字体,例如微软雅黑  
        if (StringUtils.isNotBlank(fontPath)) {  
            PdfFont microsoft = PdfFontFactory.createFont(fontPath, PdfEncodings.IDENTITY_H);  
            fontProvider.addFont(microsoft.getFontProgram(), PdfEncodings.IDENTITY_H);  
        }  

        properties.setFontProvider(fontProvider);  

        HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);  
        pdfWriter.close();  
        pdfDocument.close();  
        }  



        /**  
        * 将一个字符串转化为输入流  
        * @param sInputString 字符串  
        * @return  
        */  
        public static InputStream getStringStream(String sInputString) {  
        if (sInputString != null && !sInputString.trim().equals("")) {  
        try {  
        ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());  
        return tInputStringStream;  
        } catch (Exception e) {  
        e.printStackTrace();  
        }  
        }  
        return null;  
    }  
  
}  

生成的 PDF 支持水印功能

/**  
* 水印  
*/  
public class WaterMarkEventHandler implements IEventHandler {  
  
    /**  
    * 水印内容  
    */  
    private String waterMarkContent;  

    /**  
    * 一页中有几列水印  
    */  
    private int waterMarkX;  

    /**  
    * 一页中每列有多少水印  
    */  
    private int waterMarkY;  

    public WaterMarkEventHandler(String waterMarkContent) {  
        this(waterMarkContent, 5, 5);  
    }  

    public WaterMarkEventHandler(String waterMarkContent, int waterMarkX, int waterMarkY) {  
        this.waterMarkContent = waterMarkContent;  
        this.waterMarkX = waterMarkX;  
        this.waterMarkY = waterMarkY;  
    }  

    @Override  
    public void handleEvent(Event event) {  

        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;  
        PdfDocument document = documentEvent.getDocument();  
        PdfPage page = documentEvent.getPage();  
        Rectangle pageSize = page.getPageSize();  

        PdfFont pdfFont = null;  
        try {  
            pdfFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  

        PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);  

        Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.5f);  
        Canvas canvas = new Canvas(pdfCanvas, pageSize)  
            .setFontColor(WebColors.getRGBColor("lightgray"))  
            .setFontSize(16)  
            .setFont(pdfFont);  

        for (int i = 0; i < waterMarkX; i++) {  
            for (int j = 0; j < waterMarkY; j++) {  
                canvas.showTextAligned(waterMark, (150 + i * 300), (160 + j * 150), document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.BOTTOM, 120);  
            }  
        }  
        canvas.close();  
    }  
}  

导出 Word 效果

image.png

导出 PDF 效果

image.png

体验地址:www.openbytecode.com/open-idea