content: package com.ruoyi.work.service.impl;
  
  import cn.hutool.core.io.resource.ResourceUtil;
  import cn.hutool.core.util.ObjectUtil;
  import com.itextpdf.text.DocumentException;
  import com.itextpdf.text.pdf.PdfReader;
  import com.itextpdf.text.pdf.SimpleBookmark;
  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.ruoyi.common.config.RuoYiConfig;
  import com.ruoyi.common.constant.Constants;
  import com.ruoyi.common.exception.DataException;
  import com.ruoyi.common.utils.StringUtils;
  import com.ruoyi.work.config.PdfSignatureConfig;
  import com.ruoyi.work.domain.KeywordInfo;
  import com.ruoyi.work.domain.Stamp;
  import com.ruoyi.work.service.IPdfService;
  import com.ruoyi.work.utils.PdfUtils;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Service;
  
  import java.io.IOException;
  import java.security.GeneralSecurityException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  
  /**
   * pdfService业务层实现
   *
   * @author fengcheng
   */
  @Service
  public class PdfServiceImpl implements IPdfService {
  
      @Autowired
      private PdfSignatureConfig pdfSignatureConfig;
  
      /**
       * 获取pdf大纲
       *
       * @param downloadPath
       * @return
       */
      @Override
      public List<HashMap<String, Object>> getPdfOutline(String downloadPath) {
          return parsePDFOutlines(downloadPath);
      }
  
      /**
       * 通过关键字获取xy
       *
       * @param pdfReader
       * @param keyword
       * @return
       */
      @Override
      public List<KeywordInfo> getSealLocation(PdfReader pdfReader, String keyword) throws IOException {
          return getSealLocationKeyword(pdfReader, keyword);
      }
  
      /**
       * 完成盖章和签名
       *
       * @param stampList
       */
      @Override
      public void completeStamp(List<Stamp> stampList) {
          String localPath = RuoYiConfig.getProfile();
  
          for (Stamp stamp : stampList) {
              if (ObjectUtil.isNull(stamp.getX())) {
                  throw new DataException("x轴为空");
              }
  
              if (ObjectUtil.isNull(stamp.getPage())) {
                  throw new DataException("page为空");
              }
  
              if (ObjectUtil.isNull(stamp.getWidth())) {
                  throw new DataException("width为空");
              }
  
              if (ObjectUtil.isNull(stamp.getHeight())) {
                  throw new DataException("height为空");
              }
  
              // 获取pdf文件路径
              String pathPdf = StringUtils.substringAfter(stamp.getFilePath(), Constants.RESOURCE_PREFIX);
              String inputPdfPathPdf = localPath + pathPdf;
              String outPdfPathPdf = localPath + "/temp" + pathPdf;
              // 获取章图片路径
              String pathSura = StringUtils.substringAfter(stamp.getImagesUrl(), Constants.RESOURCE_PREFIX);
              String inputPdfPathSura = localPath + pathSura;
  
  
              PdfSignatureConfig pdfConfig =
                      new PdfSignatureConfig(
                              ResourceUtil.getStream(pdfSignatureConfig.getPath()),
                              pdfSignatureConfig.getPassword(),
                              pdfSignatureConfig.getName(),
                              pdfSignatureConfig.getPhone(),
                              pdfSignatureConfig.getPlace(),
                              pdfSignatureConfig.getCause());
  
              PdfUtils.stamp(pdfConfig, inputPdfPathPdf, outPdfPathPdf, inputPdfPathSura, stamp.getX(), stamp.getY(), stamp.getWidth(), stamp.getHeight(), stamp.getPage());
          }
      }
  
      /**
       * 获取签章位置（关键字位置）
       *
       * @param pdfReader
       * @param signKeyWord
       * @return List<KeywordInfo>
       * @throws GeneralSecurityException
       * @throws IOException
       * @throws DocumentException
       */
      private List<KeywordInfo> getSealLocationKeyword(PdfReader pdfReader, String signKeyWord) throws IOException {
          // 读取pdf内容
          int pageNum = pdfReader.getNumberOfPages();
          // 签章位置对象
          List<KeywordInfo> keywordList = new LinkedList<>();
  
          // 下标从1开始
          for (int page = 1; page <= pageNum; page++) {
              KeywordInfo rectangleCentreBase = new KeywordInfo();
              rectangleCentreBase.setPage(page);
              PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
              pdfReaderContentParser.processContent(page, new RenderListener() {
  
                  StringBuilder sb = new StringBuilder("");
                  int maxLength = signKeyWord.length();
  
                  @Override
                  public void renderText(TextRenderInfo textRenderInfo) {
                      // 只适用 单字块文档 以及 关键字整个为一个块的情况
                      // 设置 关键字文本为单独的块，不然会错位
                      boolean isKeywordChunk = textRenderInfo.getText().length() == maxLength;
                      if (isKeywordChunk) {
                          // 文档按照 块 读取
                          sb.delete(0, sb.length());
                          sb.append(textRenderInfo.getText());
                      } else if (textRenderInfo.getText().length() < maxLength) {
                          // 有些文档 单字一个块的情况
                          // 拼接字符串
                          sb.append(textRenderInfo.getText());
                          // 去除首部字符串，使长度等于关键字长度
                          if (sb.length() > maxLength) {
                              sb.delete(0, sb.length() - maxLength);
                          }
                      } else if (textRenderInfo.getText().contains(signKeyWord)) {
                          sb.delete(0, sb.length());
                          sb.append(signKeyWord);
                      }
                      // 判断是否匹配上
                      if (signKeyWord.equals(sb.toString())) {
                          KeywordInfo keywordInfo = rectangleCentreBase.copy();
  
                          // 计算中心点坐标
  
                          com.itextpdf.awt.geom.Rectangle2D.Float baseFloat = textRenderInfo.getBaseline()
                                  .getBoundingRectange();
                          com.itextpdf.awt.geom.Rectangle2D.Float ascentFloat = textRenderInfo.getAscentLine()
                                  .getBoundingRectange();
  
                          float centreX;
                          float centreY;
                          if (isKeywordChunk) {
                              centreX = baseFloat.x + ascentFloat.width / 2;
                              centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
                          } else if (textRenderInfo.getText().length() < maxLength) {
                              centreX = baseFloat.x + ascentFloat.width - (maxLength * ascentFloat.width / 2);
                              centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
                          } else {
                              int length = textRenderInfo.getText().length();
                              int index = textRenderInfo.getText().indexOf(signKeyWord);
                              centreX = baseFloat.x + ascentFloat.width / length * (index + 1);
                              centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
                          }
  
                          keywordInfo.setCentreX(centreX);
                          keywordInfo.setCentreY(centreY);
                          keywordList.add(keywordInfo);
                          // 匹配完后 清除
                          sb.delete(0, sb.length());
                      }
                  }
  
                  @Override
                  public void renderImage(ImageRenderInfo arg0) {
                  }
  
                  @Override
                  public void endTextBlock() {
                  }
  
                  @Override
                  public void beginTextBlock() {
                  }
              });
          }
          return keywordList;
      }
  
      private List<HashMap<String, Object>> parsePDFOutlines(String filePath) {
          List<HashMap<String, Object>> outlines = new ArrayList<>();
          try {
              PdfReader reader = new PdfReader(filePath);
              List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(reader);
              if (bookmarks != null) {
                  processOutlines(bookmarks, outlines);
              }
              reader.close();
          } catch (Exception e) {
              e.printStackTrace();
          }
          return outlines;
      }
  
      private void processOutlines(List<HashMap<String, Object>> bookmarks, List<HashMap<String, Object>> outlines) {
          for (HashMap<String, Object> bookmark : bookmarks) {
              HashMap<String, Object> outline = new HashMap<>(16);
              outline.put("title", bookmark.get("Title"));
              if (ObjectUtil.isNotEmpty(bookmark.get("Page"))) {
                  outline.put("page", bookmark.get("Page").toString().split(" ")[0]);
              }
              if (bookmark.containsKey("Kids")) {
                  List<HashMap<String, Object>> kids = (List<HashMap<String, Object>>) bookmark.get("Kids");
                  List<HashMap<String, Object>> children = new ArrayList<>();
                  outline.put("children", children);
                  processOutlines(kids, children);
              }
              outlines.add(outline);
          }
      }
  }
  