content: package com.ruoyi.work.utils;
  
  import com.itextpdf.awt.geom.Rectangle2D;
  import com.itextpdf.text.Font;
  import com.itextpdf.text.Image;
  import com.itextpdf.text.Rectangle;
  import com.itextpdf.text.*;
  import com.itextpdf.text.pdf.*;
  import com.itextpdf.text.pdf.parser.ImageRenderInfo;
  import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
  import com.itextpdf.text.pdf.parser.RenderListener;
  import com.itextpdf.text.pdf.parser.TextRenderInfo;
  import com.itextpdf.text.pdf.security.*;
  import com.ruoyi.work.config.PdfSignatureConfig;
  import com.ruoyi.work.param.PdfAddContentParam;
  
  import javax.imageio.ImageIO;
  import javax.swing.*;
  import java.awt.*;
  import java.awt.image.BufferedImage;
  import java.io.*;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.security.KeyStore;
  import java.security.PrivateKey;
  import java.security.cert.Certificate;
  import java.util.Calendar;
  import java.util.List;
  import java.util.UUID;
  
  /**
   * PDF工具类，提供 水印、签名、盖章、骑缝章 功能
   *
   * @author fengcheng
   */
  public class PdfUtils {
      /**
       * 往PDF上添加文字水印
       *
       * @param inputFile     原文件
       * @param outputFile    加水印后的文件
       * @param waterMarkName 水印字符
       */
      public static void addWaterMark(String inputFile, String outputFile, String waterMarkName) {
          PdfReader reader = null;
          PdfStamper stamper = null;
          try {
              reader = new PdfReader(inputFile);
              stamper = new PdfStamper(reader, Files.newOutputStream(Paths.get(outputFile)));
              //水印字体，放到服务器上对应文件夹下（arial中文不生效）
              //BaseFont base = BaseFont.createFont("D:/workspace/springboot/src/main/resources/arial.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
              BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
              Rectangle pageRect;
              PdfGState gs = new PdfGState();
              //填充不透明度  为1完全不透明
              gs.setFillOpacity(0.1f);
              //笔画不透明度， 为1完全不透明
              gs.setStrokeOpacity(0.1f);
              int total = reader.getNumberOfPages() + 1;
              JLabel label = new JLabel();
              FontMetrics metrics;
              int textH;
              int textW;
              label.setText(waterMarkName);
              metrics = label.getFontMetrics(label.getFont());
              textH = metrics.getHeight();
              textW = metrics.stringWidth(label.getText());
              PdfContentByte under;
              int interval = 0;
              for (int i = 1; i < total; i++) {
                  pageRect = reader.getPageSizeWithRotation(i);
                  //在字体下方加水印
                  under = stamper.getOverContent(i);
                  under.beginText();
                  under.saveState();
                  under.setGState(gs);
                  //这里修改水印字体大小
                  under.setFontAndSize(base, 36);
                  //这里设置字体上下间隔
                  for (int height = interval + textH; height < pageRect.getHeight(); height = height + textH * 9) {
                      //这里设置字体左右间隔
                      for (int width = interval + textW; width < pageRect.getWidth() + textW; width = width + textW * 4) {
                          //这里设置倾斜角度
                          under.showTextAligned(Element.ALIGN_LEFT, waterMarkName, width - textW, height - textH, 45);
                      }
                  }
                  under.endText();
              }
              stamper.close();
              reader.close();
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              try {
                  if (stamper != null) {
                      stamper.close();
                  }
              } catch (IOException | DocumentException e) {
                  e.printStackTrace();
              }
              if (reader != null) {
                  reader.close();
              }
          }
      }
  
      /**
       * 根据指定坐标往PDF上添加文字内容
       *
       * @param filePath            原文件路径
       * @param pdfAddContentParams 实体类
       */
      public static byte[] addText(String filePath, List<PdfAddContentParam> pdfAddContentParams) throws Exception {
          PdfReader pdfReader = new PdfReader(filePath);
          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          //设置输入文件以及输出文件地址
          PdfStamper stamper = new PdfStamper(pdfReader, byteArrayOutputStream);
          //设置字体
          BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
          for (PdfAddContentParam pdfAddContentParam : pdfAddContentParams) {
              Font font = new Font(baseFont, 10);
              if (pdfAddContentParam.getPageNum() > pdfReader.getNumberOfPages()) {
                  throw new RuntimeException("设置的页码数[" + pdfAddContentParam.getPageNum() + "]大于原文件页码数[" + pdfReader.getNumberOfPages() + "]！请重新输入");
              }
              PdfContentByte overContent = stamper.getOverContent(pdfAddContentParam.getPageNum());
              ColumnText columnText = new ColumnText(overContent);
              columnText.setSimpleColumn(pdfAddContentParam.getLlx(), pdfAddContentParam.getLly(), pdfAddContentParam.getUrx(), pdfAddContentParam.getUry());
              Paragraph elements = new Paragraph(pdfAddContentParam.getContent());
              elements.setFont(font);
              columnText.addElement(elements);
              columnText.go();
          }
          stamper.close();
          return byteArrayOutputStream.toByteArray();
      }
  
      /**
       * 根据关键字往PDF上添加文字内容
       *
       * @param filePath            原文件路径
       * @param pdfAddContentParams 实体类
       */
      public static byte[] addTextByKeyword(String filePath, List<PdfAddContentParam> pdfAddContentParams) throws Exception {
          PdfReader pdfReader = new PdfReader(filePath);
          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          //设置输入文件以及输出文件地址
          PdfStamper stamper = new PdfStamper(pdfReader, byteArrayOutputStream);
          // 获取PDF文件的总页数
          int pageNum = pdfReader.getNumberOfPages();
          PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
          for (PdfAddContentParam pdfAddContentParam : pdfAddContentParams) {
              StringBuilder stringBuilder = new StringBuilder();
              final boolean[] hasKeyword = {false, true};
              String keyword = pdfAddContentParam.getKeyword();
              int length = keyword.length();
              if (length == 0) {
                  throw new RuntimeException("请输入关键字！");
              }
              for (int page = 1; page <= pageNum; page++) {
                  if (hasKeyword[0]) {
                      break;
                  }
                  int finalPage = page;
                  pdfReaderContentParser.processContent(page, new RenderListener() {
                      @Override
                      public void beginTextBlock() {
                      }
  
  
                      @Override
                      public void renderText(TextRenderInfo renderInfo) {
                          // 读取PDF文件的内容
                          String text = renderInfo.getText().trim();
                          stringBuilder.append(text);
                          if (stringBuilder.toString().contains(keyword)) {
                              Rectangle2D.Float boundingRectangle = renderInfo.getBaseline().getBoundingRectange();
                              if (hasKeyword[1]) {
                                  // 关键字的坐标
                                  double x = boundingRectangle.getX();
                                  double y = boundingRectangle.getY();
                                  //设置字体
                                  BaseFont baseFont = null;
                                  try {
                                      baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                                  } catch (DocumentException e) {
                                      e.printStackTrace();
                                  } catch (IOException e) {
                                      e.printStackTrace();
                                  }
                                  Font font = new Font(baseFont, 10);
                                  PdfContentByte overContent = stamper.getOverContent(finalPage);
                                  ColumnText columnText = new ColumnText(overContent);
                                  // 根据关键字的坐标计算+偏移量得到批注字体的坐标
                                  columnText.setSimpleColumn((float) x + pdfAddContentParam.getLlx(), (float) y + pdfAddContentParam.getLly(), (float) x + 100 + pdfAddContentParam.getUrx(), (float) y + pdfAddContentParam.getUry());
                                  Paragraph elements = new Paragraph(pdfAddContentParam.getContent());
                                  elements.setFont(font);
                                  columnText.addElement(elements);
                                  try {
                                      columnText.go();
                                  } catch (DocumentException e) {
                                      e.printStackTrace();
                                  }
                                  hasKeyword[1] = false;
                              }
                              hasKeyword[0] = true;
                          }
                          if (stringBuilder.toString().length() > length * 3) {
                              stringBuilder.setLength(0);
                          }
                      }
  
                      @Override
                      public void endTextBlock() {
                      }
  
                      @Override
                      public void renderImage(ImageRenderInfo renderInfo) {
                      }
                  });
              }
          }
          stamper.close();
          pdfReader.close();
          return byteArrayOutputStream.toByteArray();
      }
  
      /**
       * pdf插入图片
       *
       * @param oldPath 插入图片前的pdf路径
       * @param newPath 插入图片后的pdf路径
       * @param imgPath 图片路径
       * @param x       x轴坐标
       * @param y       y轴坐标
       * @param width   图片宽度
       * @param height  图片高度
       * @param pageNum 当前页码
       * @throws IOException
       * @throws DocumentException
       */
      public static boolean addImage(String oldPath, String newPath, String imgPath, Float x, Float y, Integer width, Integer height, int pageNum) throws IOException, DocumentException {
          InputStream inputStream = Files.newInputStream(Paths.get(oldPath));
          FileOutputStream out = new FileOutputStream(newPath);
          PdfReader reader = new PdfReader(inputStream);
          //pdf页数
          int pdfPages = reader.getNumberOfPages();
  
          if (pageNum > pdfPages) {
              return false;
          }
  
          PdfStamper stamper = new PdfStamper(reader, out);
          //图片
          BufferedImage bufferedImage = ImageIO.read(Files.newInputStream(Paths.get(imgPath)));
          //图片放置的页码
          //图片处理
          Image img = Image.getInstance(ImageUtils.imageToBytes((bufferedImage), "png"));
  
          BufferedImage image = ImageIO.read(new File(imgPath));
          if (width == null || width == 0) {
              width = image.getWidth();
          }
  
          if (height == null || height == 0) {
              height = image.getHeight();
          }
  
          //设置图片大小
          img.scaleAbsolute(width, height);
  
          img.setTransparency(new int[0]);
          //设置图片位置
          img.setAbsolutePosition(x, y);
          stamper.getOverContent(pageNum).addImage(img);
  
          //关闭资源
          stamper.close();
          out.close();
          reader.close();
          return true;
      }
  
  
      /**
       * 获取文件后缀的方法
       *
       * @param filePath 文件路径
       * @return 文件后缀
       * @author fengcheng
       */
      private static String getFileExtension(String filePath) {
          File file = new File(filePath);
          String extension = "";
          try {
              if (file != null && file.exists()) {
                  String name = file.getName();
                  extension = name.substring(name.lastIndexOf(".") + 1);
              }
          } catch (Exception e) {
              extension = "";
          }
          return extension;
      }
  
  
      /**
       * 生成骑缝章
       *
       * @param oldPath   原pdf路径
       * @param newPath   新pdf路径
       * @param imagesUrl 印章路径
       * @param x         盖章x轴
       * @param y         盖章y轴
       * @param width     图片宽
       * @param height    图片高
       * @return 是否处理成功
       * @throws IOException
       */
      public static boolean generatePagingSeal(PdfSignatureConfig pdfSignatureConfig, String oldPath, String newPath, String imagesUrl, Float x, Float y, Float width, Float height, String storagePath) throws IOException {
          PdfReader pdfReader = new PdfReader(oldPath);
          int count = pdfReader.getNumberOfPages();
          if (count == 1) {
              return false;
          }
          pdfReader.close();
          String fileExtension = getFileExtension(imagesUrl);
          String name = new File(imagesUrl).getName();
          String nameWithoutExtension = name.substring(0, name.lastIndexOf("."));
          FileOutputStream fileOutputStream = null;
          try {
  
              KeyStore keyStore = KeyStore.getInstance("JKS");
              keyStore.load(pdfSignatureConfig.getPathInputStream(), pdfSignatureConfig.getPassword().toCharArray());
              String alias = (String) keyStore.aliases().nextElement();
              PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, pdfSignatureConfig.getPassword().toCharArray());
              Certificate[] chain = keyStore.getCertificateChain(alias);
              for (int i = 1; i <= count; i++) {
                  String imageUrl = storagePath + "/sura/" + nameWithoutExtension + i + "." + fileExtension;
                  PdfReader doc = new PdfReader(oldPath);
                  Rectangle pageSize = doc.getPageSize(i);
  
                  Float xAxle = (float) (pageSize.getWidth()) - width;
                  if (y > (float) pageSize.getHeight()) {
                      return false;
                  }
  
                  FileUtils.createFile(newPath);
                  fileOutputStream = new FileOutputStream(newPath);
                  /**
                   * 1 参数依次为：文件名、文件输入流、文件版本号、临时文件、是否可以追加签名
                   *  1.1 false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
                   *  1.2 true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
                   */
                  PdfStamper stamper = PdfStamper.createSignature(doc, fileOutputStream, '\0', null, true);
  
  
                  PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
                  appearance.setReason(pdfSignatureConfig.getCause());
                  appearance.setLocation(pdfSignatureConfig.getPlace());
                  appearance.setSignDate(Calendar.getInstance());
                  appearance.setLayer2Text(pdfSignatureConfig.getPlace());
                  appearance.setLayer2Text(pdfSignatureConfig.getName());
  
                  appearance.setSignatureGraphic(Image.getInstance(imageUrl));
                  /**
                   * 1 三个参数依次为：设置签名的位置、页码、签名域名称，多次追加签名的时候，签名域名称不能一样
                   * 1.1 签名的位置四个参数：印章左下角的X、Y轴坐标，印章右上角的X、Y轴坐标,
                   * 	这个位置是相对于PDF页面的位置坐标，即该坐标距PDF当前页左下角的坐标
                   */
                  appearance.setVisibleSignature(new Rectangle(xAxle + 60, pageSize.getHeight() - height - y, xAxle + width + 20, pageSize.getHeight() - y), i, String.valueOf(UUID.randomUUID()));
  
                  /**
                   * 设置认证等级，共4种，分别为：s
                   *  NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、
                   *  CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
                   *
                   * 需要用哪一种根据业务流程自行选择
                   */
                  appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
  
                  /**
                   * 印章的渲染方式，同样有4种：
                   *  DESCRIPTION、NAME_AND_DESCRIPTION,
                   *  GRAPHIC_AND_DESCRIPTION,GRAPHIC;
                   * 这里选择只显示印章
                   */
                  appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
  
                  /**
                   * 算法主要为：RSA、DSA、ECDSA
                   * 摘要算法,这里的itext提供了2个用于签名的接口，可以自己实现
                   */
                  ExternalDigest digest = new BouncyCastleDigest();
                  /**
                   * 签名算法，参数依次为：证书秘钥、摘要算法名称，例如MD5 | SHA-1 | SHA-2.... 以及 提供者
                   */
                  ExternalSignature signature = new PrivateKeySignature(PrivateKey, DigestAlgorithms.SHA1, null);
                  /**
                   * 最重要的来了,调用itext签名方法完成pdf签章
                   */
                  MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
  
                  fileOutputStream.close();
                  // 复制文件
                  FileUtils.copyDir(newPath, oldPath);
                  FileUtils.deleteFile(newPath);
  
                  pdfReader.close();
                  fileOutputStream.close();
              }
          } catch (Exception e) {
              e.printStackTrace();
              return false;
          }
          return true;
      }
  
      /**
       * 定义GetImage方法，根据PDF页数分割印章图片
       *
       * @param num PDF页数
       * @return
       * @throws IOException
       */
      public static BufferedImage[] getImage(String imagesUrl, int num, Integer width, Integer height) throws IOException {
          BufferedImage image = ImageIO.read(new File(imagesUrl));
          int rows = 1;
          int chunks = rows * num;
          if (width == null || width == 0) {
              width = image.getWidth() / num;
          }
  
          if (height == null || height == 0) {
              height = image.getHeight() / rows;
          }
  
          int count = 0;
          BufferedImage[] imgs = new BufferedImage[chunks];
          for (int x = 0; x < rows; x++) {
              for (int y = 0; y < num; y++) {
                  imgs[count] = new BufferedImage(width, height, image.getType());
                  Graphics2D gr = imgs[count++].createGraphics();
                  gr.drawImage(image, 0, 0, width, height,
                          width * y, height * x,
                          width * y + width, height * x + height, Color.WHITE, null);
                  gr.dispose();
              }
          }
          return imgs;
      }
  
      /**
       * pdf插入图片
       *
       * @param pdfSignatureConfig pdf签名配置
       * @param oldPath            插入图片前的pdf路径
       * @param newPath            插入图片后的pdf路径
       * @param imgPath            图片路径
       * @param x                  x轴坐标
       * @param y                  y轴坐标
       * @param width              图片宽度
       * @param height             图片高度
       * @param pageNum            当前页码
       * @throws IOException
       * @throws DocumentException
       */
      public static boolean stamp(PdfSignatureConfig pdfSignatureConfig, String oldPath, String newPath, String imgPath, Float x, Float y, Float width, Float height, int pageNum) {
          try {
              PdfReader pdfReader = new PdfReader(oldPath);
              int count = pdfReader.getNumberOfPages();
              if (pageNum > count) {
                  return false;
              }
              FileUtils.createFile(newPath);
              FileOutputStream fileOutputStream = new FileOutputStream(newPath);
              /**
               * 1 参数依次为：文件名、文件输入流、文件版本号、临时文件、是否可以追加签名
               *  1.1 false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
               *  1.2 true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
               */
              PdfStamper stamper = PdfStamper.createSignature(pdfReader, fileOutputStream, '\0', null, true);
  
              KeyStore keyStore = KeyStore.getInstance("JKS");
              keyStore.load(pdfSignatureConfig.getPathInputStream(), pdfSignatureConfig.getPassword().toCharArray());
              String alias = keyStore.aliases().nextElement();
              PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, pdfSignatureConfig.getPassword().toCharArray());
              Certificate[] chain = keyStore.getCertificateChain(alias);
  
              // 获取数字签章属性对象，设定数字签章的属性
              PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
              appearance.setReason(pdfSignatureConfig.getCause());
              appearance.setLocation(pdfSignatureConfig.getPlace());
              appearance.setSignDate(Calendar.getInstance());
              appearance.setLayer2Text(pdfSignatureConfig.getPlace());
              appearance.setLayer2Text(pdfSignatureConfig.getName());
  
  //            /**
  //             * 用于盖章的印章图片，引包的时候要引入itext包的image
  //             */
  //            BufferedImage image = ImageIO.read(new File(imgPath));
  //            if (width == null || width == 0) {
  //                width = image.getWidth();
  //            }
  //
  //            if (height == null || height == 0) {
  //                height = image.getHeight();
  //            }
  
              appearance.setSignatureGraphic(Image.getInstance(imgPath));
  
              Rectangle pageSize = pdfReader.getPageSize(pageNum);
              /**
               * 1 三个参数依次为：设置签名的位置、页码、签名域名称，多次追加签名的时候，签名域名称不能一样
               *  1.1 签名的位置四个参数：印章左下角的X、Y轴坐标，印章右上角的X、Y轴坐标,
               * 		这个位置是相对于PDF页面的位置坐标，即该坐标距PDF当前页左下角的坐标
               */
              appearance.setVisibleSignature(new Rectangle(x, pageSize.getHeight() - height - y, x + width, pageSize.getHeight() - y), pageNum, String.valueOf(UUID.randomUUID()));
  
              /**
               * 设置认证等级，共4种，分别为：s
               *  NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、
               *  CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
               *
               * 需要用哪一种根据业务流程自行选择
               */
              appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
  
              /**
               * 印章的渲染方式，同样有4种：
               *  DESCRIPTION、NAME_AND_DESCRIPTION,
               *  GRAPHIC_AND_DESCRIPTION,GRAPHIC;
               * 这里选择只显示印章
               */
              appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
  
              /**
               * 算法主要为：RSA、DSA、ECDSA
               * 摘要算法,这里的itext提供了2个用于签名的接口，可以自己实现
               */
              ExternalDigest digest = new BouncyCastleDigest();
              /**
               * 签名算法，参数依次为：证书秘钥、摘要算法名称，例如MD5 | SHA-1 | SHA-2.... 以及 提供者
               */
              ExternalSignature signature = new PrivateKeySignature(PrivateKey, DigestAlgorithms.SHA1, null);
              /**
               * 最重要的来了,调用itext签名方法完成pdf签章
               */
              MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
              pdfReader.close();
              fileOutputStream.close();
              // 复制文件
              FileUtils.copyDir(newPath, oldPath);
              FileUtils.deleteFile(newPath);
          } catch (Exception e) {
              e.printStackTrace();
              return false;
          }
          return true;
  
      }
  }
  