使用itextpdf将html内容转PDF文档,并签章

本文介绍如何使用iText库将HTML内容转换为带有数字签名的PDF文档,涵盖依赖配置、转换过程及签名实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

依赖com.itextpdf

		<dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
		<dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.13</version>
        </dependency>

第一步:将指定的html内容转化成pdf文档

	/**
     * 将指定的html内容转化成pdf文档之后,写入到指定的输出流.
     */
    public static void write(String htmlContent, OutputStream os) {
        if (htmlContent == null || htmlContent.length() == 0) {
            return;
        }
        if (os == null) {
            return;
        }
        htmlContent = getIntactHtml(htmlContent);
        doWrite(htmlContent, os);
    }

	/**
     * html完整内容的前缀标识
     */
    private static final String INTACT_FLAG = "<html>";
    /**
     * html模板,当待转换的html只是片断时,需将其插入到模板的body内.
     */
    private static final String TEMPLATE_HTML = "<html><head><style>h2,h3,h4{text-align: center}</style></head><body style=\"font-family:微软雅黑;\">${content}</body></html>";
	
	/**
     * 根据提供的html内容,获取完整的html内容.
     *
     * @param htmlContent html内容
     * @return 完整的html内容
     */
    private static String getIntactHtml(String htmlContent) {
        boolean intact = htmlContent.trim().toLowerCase().startsWith(INTACT_FLAG);
        if (!intact) {
            htmlContent = TEMPLATE_HTML.replaceFirst("\\$\\{content}", htmlContent);
        }
        return htmlContent;
    }

	// pdf密码
    private static final byte[] PASSWORD_BYTES = "password".getBytes();
	/**
     * 实施写操作.
     *
     * @param htmlContent html内容
     * @param os          outputStream
     */
    private static void doWrite(String htmlContent, OutputStream os) {
        InputStream is = new ByteArrayInputStream(htmlContent.getBytes(Charset.forName("UTF-8")));
        Document document = new Document();
        PdfWriter writer = null;
        try {
            writer = PdfWriter.getInstance(document, os);
            // 不可打印
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_PRINTING, STANDARD_ENCRYPTION_40);
            // 不可更改
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_MODIFY_CONTENTS, STANDARD_ENCRYPTION_40);
            // 不可复制
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_COPY, STANDARD_ENCRYPTION_40);
            // 不可注释
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_MODIFY_ANNOTATIONS, STANDARD_ENCRYPTION_40);
            // 不可填写表单域
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_FILL_IN, STANDARD_ENCRYPTION_40);
            // 不可页面提取
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_SCREENREADERS, STANDARD_ENCRYPTION_40);
            // 不可文档组合
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_ASSEMBLY, STANDARD_ENCRYPTION_40);
            // 不可签名
            writer.setEncryption(null, PASSWORD_BYTES, PdfWriter.ALLOW_DEGRADED_PRINTING, STANDARD_ENCRYPTION_40);
            // 标题
            document.addTitle("title");
            // 作者
            document.addAuthor("author");
            // 关键字
            document.addKeywords("keywords");
            // 主题
            document.addSubject("subject");
            document.open();
            // html设置<body style="font-family:微软雅黑;">
            // 或加载字体
            /*XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider() {

                @Override
                public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {
                    //你的字体文件的位置
                    //这里把所有字体都设置为宋体了,可以根据fontname的值设置字体
                    String yaHeiFontName = "msyh.ttc";
                    //如果是ttc需要这一行,ttf不需要
                    yaHeiFontName += ",1";
                    Font font = null;
                    try {
                        font = new Font(BaseFont.createFont(yaHeiFontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED));
                        font.setStyle(style);
                        font.setColor(color);
                        if (size > 0) {
                            font.setSize(size);
                        }
                    } catch (DocumentException | IOException e) {
                    }
                    return font;
                }
            };*/
            XMLWorkerHelper.getInstance().parseXHtml(writer, document, is, Charset.forName(CommonConstants.DEFAULT_ENCODING));
        } catch (DocumentException | IOException e) {
            throw new BizException(10061);
        } finally {
            IOUtils.closeQuietly(is);
            if (null != writer) {
                writer.flush();           
            }
            document.close();
        }
    }

第二步:签名

	private static final String KEYSTORE = "keystore.p12";
    // keystore密码
    private static final char[] PASSWORD = "xxxyyy".toCharArray();
	/**
     * @param src    需要签章的pdf文件路径
     * @param dest   签完章的pdf文件路径
     *            chain           证书链
     *            pk              签名私钥
     *            digestAlgorithm 摘要算法名称,例如SHA-1
     *            provider        密钥算法提供者,可以为null
     *            cryptoStandard  数字签名格式,itext有2种
     *            reason          签名的原因,显示在pdf签名属性中,随便填
     *            location        签名的地点,显示在pdf签名属性中,随便填
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws DocumentException
     */
    public static void sign(String src, String dest) throws GeneralSecurityException, IOException, DocumentException {
        // Creating the reader and the stamper,开始pdf reader
        PdfReader reader = new PdfReader(src, PASSWORD_BYTES);
        // 目标文件输出流
        FileOutputStream os = new FileOutputStream(dest);
        // 创建签章工具PdfStamper ,最后一个boolean参数
        // false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
        // true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
        // 获取数字签章属性对象,设定数字签章的属性
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        String reason = "reason";
        String location = "location";
        appearance.setReason(reason);
        appearance.setLocation(location);
        // 设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
        // 签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
        // 四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
        //appearance.setVisibleSignature(new Rectangle(450, 747, 550, 847), 1, "sig1");
        // 读取图章图片,这个image是itext包的image
        //Image image = Image.getInstance(new ClassPathResource(SIGNATURE).getFile().getAbsolutePath());
        //appearance.setSignatureGraphic(image);
        appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
        // 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
        // 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
        // 摘要算法
        ExternalDigest digest = new BouncyCastleDigest();
        // 签名算法
        String provider = null;
        String digestAlgorithm = DigestAlgorithms.SHA1;
        KeyStore ks = KeyStore.getInstance("PKCS12");
        InputStream inputStream = new ClassPathResource(KEYSTORE).getInputStream();
        ks.load(inputStream, PASSWORD);
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
        ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, provider);
        // 调用itext签名方法完成pdf签章
        MakeSignature.CryptoStandard cryptoStandard = MakeSignature.CryptoStandard.CMS;
        Certificate[] chain = ks.getCertificateChain(alias);
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, cryptoStandard);
        // Closes the document. No more content can be written after the document is closed.
        stamper.close();
        inputStream.close();
    }

集成示例

	@RequestMapping(path = "pdf-get", method = RequestMethod.GET)
    public void getPdfContract(HttpServletResponse response) throws IOException {
        String fileName = "contract.pdf";
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/octet-stream");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        response.flushBuffer();
        File file = new File(fileName);
        // 需要填充的数据
        Object data = null;	
        ItextpdfUtils.generatePDFFromHtmlContent(template.getContent(), data, file);
        FileInputStream fileInputStream = new FileInputStream(file);
        byte b[] = new byte[1024];
        int j;
        ServletOutputStream os = response.getOutputStream();
        while ((j = fileInputStream.read(b)) != -1) {
            os.write(b, 0, j);
            os.flush();
        }
        fileInputStream.close();
        os.close();
        FileUtils.deleteQuietly(file);
    }
    
	/**
     * 把html形式的合同模板转换为PDF
     *
     * @param content 合同模板内容-未设值
     * @param object  数据(对象/Map)
     */
    public static void generatePDFFromHtmlContent(String content, Object object, File file) {
        File copyFile = new File("copy-" + file.getName());
        FileOutputStream os = null;
        try {
            if (!copyFile.exists()) {
                boolean result = copyFile.createNewFile();
                if (result) {
                    LOG.info("The named copy file does not exist and was successfully created");
                } else {
                    LOG.info("The named copy file already exists");
                }
            }
			// 如需要,可以使用freemarker对html文本进行数据填充,FreemarkerUtils可自行编写
			// content = FreemarkerUtils.parseString(content, object);
            os = new FileOutputStream(copyFile);
            // HTML转PDF
            ItextpdfUtils.write(content, os);
            // 数字签名
            ItextpdfUtils.sign(copyFile.getAbsolutePath(), file.getAbsolutePath());
        } catch (IOException | GeneralSecurityException | DocumentException e) {
            LOG.error(e.getMessage());
        } finally {
            IOUtils.closeQuietly(os);
            FileUtils.deleteQuietly(copyFile);
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值