itextpdf7 使用之 html转pdf,生成目录、添加页眉页脚

最近有个需求,生成信用报告。

需求:

1、生成pdf有页眉页脚
2、生成目录
3、目录加锚点可跳转。

难点:

1、生成的目录不能实时读取页码
2、目录是后生成的,属于两份pdf拼接的,不能添加锚点跳转

思路:

1、freemaker进行html页面布局及动态变量替换
2、生成一份pdf文档,用于关键字查询,获取所在页码
3、再生成一份目录+内容的pdf,目录的页码从刚生成的文档中查询,由于此文档目录和内容是同一个文档,所以可以添加锚点

实现:

1、引入 freemaker

		
            org.springframework.boot
            spring-boot-starter-freemarker
        

2、freemaker 默认 文件路径是 resources/tempates
itextpdf7 使用之 html转pdf,生成目录、添加页眉页脚_第1张图片
3、读取freemaker页面布局及变量替换

	Map appendix = new HashMap<>();
	appendix.put("iprTrademarkList", iprTrademarkList);
	appendix.put("iprPatentList", iprPatentList);

	Template  appendixtemp = configurer.getConfiguration().getTemplate("risk_appendix.html");
	content += FreeMarkerTemplateUtils.processTemplateIntoString(appendixtemp, appendix);

4、html转为pdf,添加页眉页脚

	public void html2Pdf(String content, String outPath, String type) {
		try {
			BufferedOutputStream outputStream = FileUtil.getOutputStream(outPath);
			if ("head".equals(type) || "content".equals(type)) { // 封面、临时content文件   无页眉,无水印,无页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, "", "", false,PageSize.A4, outputStream);
			} else if ("directAndContent".equals(type)) {  // 目录+内容  有页眉、有水印,有页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, true,PageSize.A4, outputStream);
			} else { // 说明、概览     有页眉、有水印,无页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, false,PageSize.A4, outputStream);
			}
		} catch (Exception e) {
			System.out.println("生成模板内容失败"+e.fillInStackTrace());
		}
	}

/**
 * Itext7转换pdf工具类
 */
@Slf4j
public class PdfUtils {

    // 字体文件路径
    private static final String fontPath = "pdf/font/MSYH.TTF";
    // 字体文件 列表
    private static final Set fontSet = new HashSet<>();

    static {
		ClassPathResource classPathResource = new ClassPathResource(fontPath);
        fontSet.add(classPathResource.getPath());
    }
	
	public static void convertHtmlToPdf(String htmlStr, String logoUrl, String headerText, String waterMark, boolean hasFooter, PageSize pageSize, OutputStream outputStream) {
		convertHtmlToPdf(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), logoUrl, headerText, waterMark, hasFooter, pageSize, fontSet, outputStream);
	}

    /**
     * html转 pdf
     * @param inputStream  输入流
     * @param headerText    页眉
     * @param waterMark    水印
     * @param pageSize 纸张大小
     * @param fontPathList  字体路径列表,ttc后缀的字体需要添加,0 例:C:\front\msyh.ttc,0
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(InputStream inputStream, String logoUrl, String headerText, String waterMark, boolean hasFooter,PageSize pageSize,
                                        Set fontPathList, OutputStream outputStream) {

        // 验空
        Objects.requireNonNull(inputStream);
        Objects.requireNonNull(outputStream);

        PdfWriter pdfWriter = new PdfWriter(outputStream);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        //设置纸张大小
        pdfDocument.setDefaultPageSize(pageSize);

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

        //添加自定义字体,例如微软雅黑
        if (CollectionUtils.isNotEmpty(fontPathList)) {
            fontPathList.forEach(e -> fontProvider.addFont(e, PdfEncodings.IDENTITY_H));
        }

        PdfFont pdfFont = fontProvider.getFontSet()
                .getFonts()
                .stream()
                .findFirst()
                .map(fontProvider::getPdfFont)
                .orElse(null);


			// 添加页眉
			pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeaderMarkerEventHandler(pdfFont, headerText, logoUrl));
			// 添加水印
			if (StringUtils.isNotBlank(waterMark)) {
				pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PdfWaterMarkEventHandler(pdfFont, waterMark));
			}
			if (hasFooter) {
				// 添加页脚
				pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PdfPageMarkerEventHandler(pdfFont));
			}
        properties.setFontProvider(fontProvider);
        // 读取Html文件流,查找出当中的 或出现类似的符号空格字符
        inputStream = readInputStrem(inputStream);

        try {
            // 生成pdf文档
            HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("错误信息:pdf转换失败{}", e.getMessage());
        } finally {
            try {
                pdfWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }
    }

    /**
     * 读取HTML 流文件,并查询当中的 或类似符号直接替换为空格
     *
     * @param inputStream 输入流
     * @return 去掉特殊标记的输入流
     */
    private static InputStream readInputStrem(InputStream inputStream) {
        // 定义一些特殊字符的正则表达式 如:
        String regEx_special = "\\&[a-zA-Z]{1,10};";
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            // 创建缓存大小
            byte[] buffer = new byte[1024]; // 1KB
            // 每次读取到内容的长度
            int len = -1;
            // 开始读取输入流中的内容
            while ((len = inputStream.read(buffer)) != -1) { //当等于-1说明没有数据可以读取了
                baos.write(buffer, 0, len);   //把读取到的内容写到输出流中
            }
            // 把字节数组转换为字符串 设置utf-8字符编码
            String content = baos.toString(String.valueOf(StandardCharsets.UTF_8));
            // 关闭输入流和输出流
            inputStream.close();
            // 判断HTML内容是否具有HTML的特殊字符标记
            Pattern compile = Pattern.compile(regEx_special, Pattern.CASE_INSENSITIVE);
            Matcher matcher = compile.matcher(content);
            String replaceAll = matcher.replaceAll("");
            // 将字符串转化为输入流返回
            return getStringStream(replaceAll);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("错误信息:pdf字符串格式化特殊字符失败{}", e.getMessage());
            return null;
        }
    }

    /**
     * 将一个字符串转化为输入流
     * @param sInputString 字符串
     * @return 字符串对应的输入流
     */
    public static InputStream getStringStream(String sInputString) {
        if (sInputString != null && !sInputString.trim().equals("")) {
            try {
                return new ByteArrayInputStream(sInputString.getBytes(StandardCharsets.UTF_8)); // 设置utf-8字符编码
            } catch (Exception e) {
                e.printStackTrace();
                log.error("错误信息:pdf字符串转输入流失败,{}", e.getMessage());
            }
        }
        return null;
    }


	/**
	 * 将给定List集合中的pdf文档,按照顺序依次合并,生成最终的目标PDF文档
	 *
	 * @param pdfPathLists 待合并的PDF文档路径集合,可以是本地PDF文档,也可以是网络上的PDF文档
	 * @param destPath     目标合并生成的PDF文档路径
	 */
	public static void mergeMultiplePdfs(List pdfPathLists, String destPath) {
		try {
			int size = pdfPathLists.size();
			byte[] pdfData = getPdfBytes(pdfPathLists.get(0));
			for (int i = 1; i < size; i++) {
				pdfData = mergePdfBytes(pdfData, getPdfBytes(pdfPathLists.get(i)));
			}
			if (pdfData != null) {
				FileOutputStream fis = new FileOutputStream(destPath);
				fis.write(pdfData);
				fis.close();
			}
		} catch (Exception e) {
			log.error("合并PDF异常:", e);
		}
	}

	/**
	 * 基于内存中的字节数组进行PDF文档的合并
	 * @param firstPdf 第一个PDF文档
	 * @param secondPdf 第二个PDF文档
	 */
	private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) throws IOException {
		if (firstPdf != null && secondPdf != null) {
			// 创建字节数组,基于内存进行合并
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			PdfDocument destDoc = new PdfDocument(new PdfWriter(baos));
			// 合并的pdf文件对象
			PdfDocument firstDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(firstPdf)));
			PdfDocument secondDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(secondPdf)));
			// 合并对象
			PdfMerger merger = new PdfMerger(destDoc);
			merger.merge(firstDoc, 1, firstDoc.getNumberOfPages());
			merger.merge(secondDoc, 1, secondDoc.getNumberOfPages());
			// 关闭文档流
			merger.close();
			firstDoc.close();
			secondDoc.close();
			destDoc.close();
			return baos.toByteArray();
		}
		return null;
	}

	/**
	 * 将pdf文档转换成字节数组
	 * @param pdf PDF文档路径
	 * @return 返回对应PDF文档的字节数组
	 */
	private static byte[] getPdfBytes(String pdf) throws Exception {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		InputStream is = Files.newInputStream(Paths.get(pdf));

		byte[] data = new byte[2048];
		int len;
		while ((len = is.read(data)) != -1) {
			out.write(data, 0, len);
		}
		return out.toByteArray();
	}

	/**
	 * 远程文件转化为字节
	 * @param pdf
	 * @return
	 * @throws Exception
	 */
	public static byte[] getRemotePdfBytes(String pdf) throws Exception {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		URL url = new URL(pdf);

		InputStream is =url.openStream();

		byte[] data = new byte[2048];
		int len;
		while ((len = is.read(data)) != -1) {
			out.write(data, 0, len);
		}
		return out.toByteArray();
	}
}
/**
 * 页眉实现类
 */
public class PdfHeaderMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;
    /**
     * 页眉显示
     */
    private final String title;

	private final String logoUrl;

    public PdfHeaderMarkerEventHandler(PdfFont pdfFont, String title, String logoUrl) {
        this.pdfFont = pdfFont;
        this.title = title;
        this.logoUrl = logoUrl;
    }

	@Override
	public void handleEvent(Event event) {

		if (StringUtils.isEmpty(title)) return;

		PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
		PdfDocument pdf = docEvent.getDocument();
		PdfPage page = docEvent.getPage();
		Rectangle pageSize = page.getPageSize();
		PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
		Canvas canvas = new Canvas(pdfCanvas, pageSize);
		float x = pageSize.getRight() -60;
		float y = pageSize.getTop() - 32;
		Paragraph p = new Paragraph(title)
			.setFontSize(9)
			.setFont(pdfFont);
		// 显示在顶部右侧位置
		canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);
		URL url = null;
		try {
			url = new URL(logoUrl);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
		Image logo = new Image(ImageDataFactory.create(url));
		logo.scaleAbsolute(100, 20);
		logo.setMarginLeft(30);
		logo.setMarginTop(15);
		canvas.add(logo);
		canvas.close();
	}

}
/**
 * 页脚(页码)实现类
 */
public class PdfPageMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;

    public PdfPageMarkerEventHandler(PdfFont pdfFont) {
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdf.getPageNumber(page);
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float y = pageSize.getBottom() + 15;

		Paragraph p = new Paragraph();
		if (1 == pageNumber) {
			p = new Paragraph("")
				.setFontSize(10f)
				.setFont(pdfFont);
		} else {
			p = new Paragraph("第" + (pageNumber -1) + "页")
				.setFontSize(10f)
				.setFont(pdfFont);
		}
        // 绘制到底部中间位置
        canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
        canvas.close();
    }
}

/**
 * pdf水印
 */
public class PdfWaterMarkEventHandler implements IEventHandler {

    /**
     * 水印内容
     */
    private final String waterMarkContent;

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

    /**
     * 一页中每列有多少水印
     */
    private final int waterMarkY;
    /**
     * Pdf字体
     */
    private final PdfFont pdfFont;

    /**
     * 默认水印效果5行5列
     * @param pdfFont pdf字体
     * @param waterMarkContent 水印内容
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent) {
        this(pdfFont, waterMarkContent, 5, 5);
    }

    /**
     * 水印效果
     *
     * @param pdfFont          pdf字体
     * @param waterMarkContent 水印内容
     * @param waterMarkX       一页中有多少行水印
     * @param waterMarkY       一页中有多少列水印
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {

        if (StringUtils.isEmpty(waterMarkContent)) return;

        // 获取pdf对象、页面属性
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        // 设置画布属性、水印属性
        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);

        float y = pageSize.getHeight(); // 页面高度
        float x = pageSize.getWidth(); // 页面宽度

        // 根据行列进行绘制
        for (int i = 0; i < waterMarkX; i++) {
            float width = (x / waterMarkX)  * i + 50;
            for (int j = waterMarkY; j > 0; j--) {
                float height = (y / waterMarkY)  * j - 50;
//                System.out.println("waterMarkX: "+width+" waterMarkY: "+height);
                canvas.showTextAligned(waterMark, width, height, document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 120);
            }
        }
        canvas.close();
    }

5、 内容html页面上特定字符及锚点id:

	
directory_1.企业信息

6、查询特定内容所在页码

	int companyInfoNum = searchFileNum(fileName, zongtiNum, "directory_1.企业信息");


	 * 查询标题所在页码方法
	 * @param fileUrl
	 * @param startPage
	 * @param content
	 * @return
	 * @throws IOException
	 */
	public int searchFileNum(String fileUrl, int startPage, String content) throws IOException {
		int curent = 0;
		// 创建PdfReader对象
		PdfReader pdfWriter = new PdfReader(fileUrl);
		PdfDocument pdfDocument = new PdfDocument(pdfWriter);
		int numberOfPages = pdfDocument.getNumberOfPages();
		for (int i = startPage; i <= numberOfPages; i++) {
			PdfPage page = pdfDocument.getPage(i);
			String textFromPage = PdfTextExtractor.getTextFromPage(page);
			int index = textFromPage.indexOf(content);
			if (index >= 0) {
				curent = i;
			}
		}
		pdfDocument.close();
		pdfWriter.close();

		return curent;
	}

7、目录html页面替换变量、添加锚点

	
1.企业信息 ${companyInfoNum}

8、合并生成的多个pdf

PdfUtils.mergeMultiplePdfs(Arrays.asList(headerName, pdfDescName, pdfOverViewName, directoryAndContentName), pdfAllName);

9、删除多余的pdf文件

	File headerFile = new File(headerName);

	if (headerFile.exists()) {
		headerFile.delete();
	}

你可能感兴趣的:(java,html,pdf,前端)