概述
在開發過程中,word轉pdf的方式有很多種有jar包的方式,有安裝openoffice的方式,但是使用有的jar包有license認證,不然會生成水印,綜合幾種方法我采用了libreoffice的方式
本項目為springboot項目
依賴包
<dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency> <dependency> <groupId>org.libreoffice</groupId> <artifactId>juh</artifactId> <version>5.4.2</version> </dependency> <dependency> <groupId>org.libreoffice</groupId> <artifactId>jurt</artifactId> <version>5.4.2</version> </dependency> <dependency> <groupId>org.libreoffice</groupId> <artifactId>ridl</artifactId> <version>5.4.2</version> </dependency> <dependency> <groupId>org.libreoffice</groupId> <artifactId>unoil</artifactId> <version>5.4.2</version> </dependency>
application.yml配置
jodconverter:
enabled: true
#office-home: /opt/libreoffice7.0
port-numbers: 8200
max-tasks-per-process: 100
working-dir:
office-home: C:\Program Files\LibreOffice
java代碼
在類中引入DocumentConverter
@Resource private DocumentConverter documentConverter; //在使用處使用 documentConverter.convert(new File(sourcePath)).to(new File(targetPath)).execute();
docker制作libreoffice鏡像
在linux中配置libreoffcie,因為這邊采用了docker,所以這邊簡單介紹下,docker中如何制作libreoffice鏡像,並發布項目
首先准備Dockerfile文件,該鏡像是以基於centos7版本鏡像(注:沒有擴展名)
#基於centos7版本鏡像 FROM centos:7 #以下設置中文語言環境與修改時區 ENV LANG=zh_CN.UTF-8 \ LANGUAGE=zh_CN:zh \ LC_ALL=zh_CN.UTF-8 RUN yum update -y && \ yum reinstall -y glibc-common && \ yum install -y telnet net-tools && \ yum clean all && \ rm -rf /tmp/* rm -rf /var/cache/yum/* && \ localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 && \ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime #加入windows字體包 ADD chinese.tar.gz /usr/share/fonts/ #將下載好的包解壓到相應文件下 ADD LibreOffice_7.0.1_Linux_x86-64_rpm.tar.gz /home/ #執行安裝 RUN cd /home/LibreOffice_7.0.1.2_Linux_x86-64_rpm/RPMS/ \ && yum localinstall *.rpm -y \ #安裝依賴 && yum install ibus -y \ #加入中文字體支持並賦權限 && cd /usr/share/fonts/ \ && chmod -R 755 /usr/share/fonts \ && yum install mkfontscale -y \ && mkfontscale \ && yum install fontconfig -y \ && mkfontdir \ && fc-cache -fv \ && mkdir /usr/local/java/ \ #清理緩存,減少鏡像大小 && yum clean all #加入安裝java環境 ADD jdk-8u121-linux-x64.tar.gz /usr/local/java/ RUN ln -s /usr/local/java/jdk1.8.0_121 /usr/local/java/jdk #配置環境變量 ENV JAVA_HOME /usr/local/java/jdk ENV JRE_HOME ${JAVA_HOME}/jre ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib ENV PATH ${JAVA_HOME}/bin:$PATH CMD ["bash"]
還有chinese.tar.gz,jdk-8u121-linux-x64.tar.gz,LibreOffice_7.0.1_Linux_x86-64_rpm.tar.gz這些包,需要和Dockerfile文件放在同一目錄下 執行
相關文件下載方式:
鏈接:https://pan.baidu.com/s/18XjsvvrJfsle9DEcDJKQUQ
提取碼:9hke
docker build -t libreoffice:v1 .
命令,注意后邊有一個點
另外部署你所要用到libreoffice的項目,相應的Dockerfile文件:
FROM libreoffice:v1 VOLUME /tmp ADD xxx.jar/ app.jar RUN bash -c 'touch /app.jar' ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone EXPOSE 9501 ENTRYPOINT ["java","-Xmx640M","-Xms640M","-Xmn240M","-XX:MaxMetaspaceSize=192M","-XX:MetaspaceSize=192M","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
並執行
docker build -t xxx .
然后啟動容器即可使用。
加蓋印章
概述
加蓋印章是在轉為pdf的情況下,進行加蓋的,采用的技術是itext,該加蓋電子印章的優點是相較於之前划定區域來加蓋印章的方式來說,可以通過關鍵字來定位蓋章的區域,並根據x軸和y軸的偏移量來調整相對位置,來實現。
之前划定區域的方式也可以,但是如果文檔加蓋印章的上方,如果寫了內容,導致下邊文檔向下移動,這樣會將章的位置蓋偏,達不到想要的效果;
相關依賴
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency>
GetKeyWordPosition.java
import com.itextpdf.awt.geom.Rectangle2D; import com.itextpdf.text.pdf.PdfDictionary; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.parser.*; public class GetKeyWordPosition { public static void main(String[] args) throws IOException { //1.給定文件 File pdfFile = new File("C:\\Users\\xxx\\Desktop\\結題報告.pdf"); //2.定義一個byte數組,長度為文件的長度 byte[] pdfData = new byte[(int) pdfFile.length()]; //3.IO流讀取文件內容到byte數組 FileInputStream inputStream = null; try { inputStream = new FileInputStream(pdfFile); inputStream.read(pdfData); } catch (IOException e) { throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } //4.指定關鍵字 String keyword = "公章"; //5.調用方法,給定關鍵字和文件 List<float[]> positions = findKeywordPostions(pdfData, keyword); //6.返回值類型是 List<float[]> 每個list元素代表一個匹配的位置,分別為 float[0]所在頁碼 float[1]所在x軸 float[2]所在y軸 System.out.println("total:" + positions.size()); if (positions != null && positions.size() > 0) { for (float[] position : positions) { System.out.print("pageNum: " + (int) position[0]); System.out.print("\tx: " + position[1]); System.out.println("\ty: " + position[2]); } } } /** * findKeywordPostions * @param pdfData 通過IO流 PDF文件轉化的byte數組 * @param keyword 關鍵字 * @return List<float [ ]> : float[0]:pageNum float[1]:x float[2]:y * @throws IOException */ public static List<float[]> findKeywordPostions(byte[] pdfData, String keyword) throws IOException { List<float[]> result = new ArrayList<>(); List<PdfPageContentPositions> pdfPageContentPositions = getPdfContentPostionsList(pdfData); for (PdfPageContentPositions pdfPageContentPosition : pdfPageContentPositions) { List<float[]> charPositions = findPositions(keyword, pdfPageContentPosition); if (charPositions == null || charPositions.size() < 1) { continue; } result.addAll(charPositions); } return result; } private static List<PdfPageContentPositions> getPdfContentPostionsList(byte[] pdfData) throws IOException { PdfReader reader = new PdfReader(pdfData); List<PdfPageContentPositions> result = new ArrayList<>(); int pages = reader.getNumberOfPages(); for (int pageNum = 1; pageNum <= pages; pageNum++) { float width = reader.getPageSize(pageNum).getWidth(); float height = reader.getPageSize(pageNum).getHeight(); PdfRenderListener pdfRenderListener = new PdfRenderListener(pageNum, width, height); //解析pdf,定位位置 PdfContentStreamProcessor processor = new PdfContentStreamProcessor(pdfRenderListener); PdfDictionary pageDic = reader.getPageN(pageNum); PdfDictionary resourcesDic = pageDic.getAsDict(PdfName.RESOURCES); try { processor.processContent(ContentByteUtils.getContentBytesForPage(reader, pageNum), resourcesDic); } catch (IOException e) { reader.close(); throw e; } String content = pdfRenderListener.getContent(); List<CharPosition> charPositions = pdfRenderListener.getcharPositions(); List<float[]> positionsList = new ArrayList<>(); for (CharPosition charPosition : charPositions) { float[] positions = new float[]{charPosition.getPageNum(), charPosition.getX(), charPosition.getY()}; positionsList.add(positions); } PdfPageContentPositions pdfPageContentPositions = new PdfPageContentPositions(); pdfPageContentPositions.setContent(content); pdfPageContentPositions.setPostions(positionsList); result.add(pdfPageContentPositions); } reader.close(); return result; } private static List<float[]> findPositions(String keyword, PdfPageContentPositions pdfPageContentPositions) { List<float[]> result = new ArrayList<>(); String content = pdfPageContentPositions.getContent(); List<float[]> charPositions = pdfPageContentPositions.getPositions(); for (int pos = 0; pos < content.length(); ) { int positionIndex = content.indexOf(keyword, pos); if (positionIndex == -1) { break; } float[] postions = charPositions.get(positionIndex); result.add(postions); pos = positionIndex + 1; } return result; } private static class PdfPageContentPositions { private String content; private List<float[]> positions; public String getContent() { return content; } public void setContent(String content) { this.content = content; } public List<float[]> getPositions() { return positions; } public void setPostions(List<float[]> positions) { this.positions = positions; } } private static class PdfRenderListener implements RenderListener { private int pageNum; private float pageWidth; private float pageHeight; private StringBuilder contentBuilder = new StringBuilder(); private List<CharPosition> charPositions = new ArrayList<>(); public PdfRenderListener(int pageNum, float pageWidth, float pageHeight) { this.pageNum = pageNum; this.pageWidth = pageWidth; this.pageHeight = pageHeight; } public void beginTextBlock() { } public void renderText(TextRenderInfo renderInfo) { List<TextRenderInfo> characterRenderInfos = renderInfo.getCharacterRenderInfos(); for (TextRenderInfo textRenderInfo : characterRenderInfos) { String word = textRenderInfo.getText(); if (word.length() > 1) { word = word.substring(word.length() - 1, word.length()); } Rectangle2D.Float rectangle = textRenderInfo.getAscentLine().getBoundingRectange(); float x = (float)rectangle.getX(); float y = (float)rectangle.getY(); // float x = (float)rectangle.getCenterX(); // float y = (float)rectangle.getCenterY(); // double x = rectangle.getMinX(); // double y = rectangle.getMaxY(); //這兩個是關鍵字在所在頁面的XY軸的百分比 float xPercent = Math.round(x / pageWidth * 10000) / 10000f; float yPercent = Math.round((1 - y / pageHeight) * 10000) / 10000f; // CharPosition charPosition = new CharPosition(pageNum, xPercent, yPercent); CharPosition charPosition = new CharPosition(pageNum, (float)x, (float)y); charPositions.add(charPosition); contentBuilder.append(word); } } public void endTextBlock() { } public void renderImage(ImageRenderInfo renderInfo) { } public String getContent() { return contentBuilder.toString(); } public List<CharPosition> getcharPositions() { return charPositions; } } private static class CharPosition { private int pageNum = 0; private float x = 0; private float y = 0; public CharPosition(int pageNum, float x, float y) { this.pageNum = pageNum; this.x = x; this.y = y; } public int getPageNum() { return pageNum; } public float getX() { return x; } public float getY() { return y; } @Override public String toString() { return "[pageNum=" + this.pageNum + ",x=" + this.x + ",y=" + this.y + "]"; } } }
StampUtil.java
import com.itextpdf.text.DocumentException; import com.itextpdf.text.Image; import com.itextpdf.text.pdf.*; public class StampUtil { public static void main(String[] args) throws Exception { //pt3.pdf你在resource目錄下找得到,放到絕對路徑下就好了 findKeyWordAndAddImage("C:\\Users\\wanggang\\Desktop\\2020年 湖南省衛健委科研項目合同書.pdf", "C:\\Users\\wanggang\\Desktop\\2020年 湖南省衛健委科研項目合同書seal.pdf", "湖南省衛生健康委(甲方)科技主管部門:(公章)", "C:\\Users\\wanggang\\Desktop\\timg.png", 0, -130); } /** * 尋找指定的關鍵字后給其所在位置蓋章 * @param source 源pdf文件 * @param target 目標pdf文件 * @param keyword 關鍵字 * @param image 印章路徑 * @param xOffset x軸偏移量(沒有則指定為0) * @param yOffset y軸偏移量(沒有則指定為0) * @return 返回結果 * @throws IOException * @throws DocumentException */ public static boolean findKeyWordAndAddImage(String source, String target, String keyword, String image, float xOffset, float yOffset) throws IOException, DocumentException { boolean result = false; File pdfFile = new File(source); byte[] pdfData = new byte[(int) pdfFile.length()]; FileInputStream fis = null; try { fis = new FileInputStream(pdfFile); fis.read(pdfData); } catch (IOException e) { e.printStackTrace(); System.out.println("文件不存在"); return result; }finally { fis.close(); } System.out.println("keyword:"+keyword); //查到關鍵字返回位置 List<float[]> positions = findKeywordPostions(pdfData, keyword); if(positions.size() == 0){ System.out.println("關鍵字不存在"); return result; } //添加水印 //會查詢到多個關鍵字定位,固定取最后一個 result = addImage(source, target, image, positions.get(positions.size()-1), xOffset, yOffset); return true; } //添加水印(簽章) private static boolean addImage(String source, String target, String imagePath, float[] positions, float xOffset, float yOffset) throws IOException, DocumentException { // 讀取模板文件 InputStream input = new FileInputStream(new File(source)); PdfReader reader = new PdfReader(input); FileOutputStream fileOutputStream = new FileOutputStream(target); PdfStamper stamper = new PdfStamper(reader, fileOutputStream); // 提取pdf中的表單 AcroFields form = stamper.getAcroFields(); form.addSubstitutionFont(BaseFont.createFont("STSong-Light","UniGB-UCS2-H", BaseFont.NOT_EMBEDDED)); // 讀圖片 Image image = Image.getInstance(imagePath); // 獲取操作的頁面 PdfContentByte under = stamper.getOverContent((int)positions[0]); //設置圖片的定位 image.setAbsolutePosition(positions[1]+xOffset, positions[2]+yOffset); //image.scalePercent(75); //縮放圖片為指定百分比 // 設置透明度為0.8 PdfGState gs = new PdfGState(); gs.setFillOpacity(0.8f); under.setGState(gs); // 添加圖片 under.addImage(image); stamper.close(); fileOutputStream.close(); reader.close(); input.close(); return true; } }