|
|
@@ -0,0 +1,218 @@
|
|
|
+package org.springblade.manager.utils;
|
|
|
+
|
|
|
+import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
|
|
|
+import org.apache.pdfbox.cos.COSName;
|
|
|
+import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
+import org.apache.pdfbox.pdmodel.PDPage;
|
|
|
+import org.apache.pdfbox.pdmodel.PDPageTree;
|
|
|
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
|
+import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
|
|
|
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
|
|
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
|
|
|
+import org.apache.pdfbox.rendering.PDFRenderer;
|
|
|
+import org.apache.pdfbox.util.Matrix;
|
|
|
+
|
|
|
+import javax.imageio.*;
|
|
|
+import javax.imageio.metadata.IIOMetadata;
|
|
|
+import javax.imageio.metadata.IIOMetadataNode;
|
|
|
+import javax.imageio.stream.ImageOutputStream;
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+
|
|
|
+/**
|
|
|
+ * PDF分析工具类
|
|
|
+ * 目前仅实现一下功能
|
|
|
+ * 1、pdf尺寸检测,比如检测pdf是否为A4尺寸或者其它标准尺寸
|
|
|
+ * 2、检测pdf是否包含图片,如果包含图片,则判断图片dpi是否大于等于300
|
|
|
+ */
|
|
|
+public class PDFAnalyzerUtils {
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检测pdf尺寸是否是A4或者A3,如果所有页面尺寸都是A4或者A3,则返回true
|
|
|
+ * 同时检测pdf中是否包含图片,如果包含图片,则判断图片DPI是否大于等于300, 如果所有图片DPI都大于等于300,则返回true
|
|
|
+ * @param doc pdf文件
|
|
|
+ * @return true表示pdf尺寸是A4或者A3,且所有图片DPI都大于等于300,否则返回false
|
|
|
+ * @throws IOException 读取pdf文件时发生错误
|
|
|
+ */
|
|
|
+ public static boolean checkPdfSizeAndImageDPI(PDDocument doc) throws IOException {
|
|
|
+ PDPageTree pages = doc.getPages();
|
|
|
+ for (PDPage page : pages) {
|
|
|
+ PDRectangle mediaBox = page.getMediaBox();
|
|
|
+ String pageSize = getPageSize(mediaBox);
|
|
|
+ if (!pageSize.equals("A4") && !pageSize.equals("A3")) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!checkImageDPI(page, 300, 2)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取页面尺寸名称
|
|
|
+ *
|
|
|
+ * @param mediaBox 页面尺寸
|
|
|
+ * @return 页面尺寸名称,如果不是标准尺寸,则返回"Unknown"
|
|
|
+ */
|
|
|
+ private static String getPageSize(PDRectangle mediaBox) {
|
|
|
+ float width = mediaBox.getWidth() / 72 * 25.4f;
|
|
|
+ float height = mediaBox.getHeight() / 72 * 25.4f;
|
|
|
+
|
|
|
+ if ((Math.abs(width - 210) < 1 && Math.abs(height - 297) < 1) || (Math.abs(width - 297) < 1 && Math.abs(height - 210) < 1)) {
|
|
|
+ return "A4";
|
|
|
+ } else if ((Math.abs(width - 420) < 1 && Math.abs(height - 297) < 1) || (Math.abs(width - 297) < 1 && Math.abs(height - 420) < 1)) {
|
|
|
+ return "A3";
|
|
|
+ } else if ((Math.abs(width - 420) < 1 && Math.abs(height - 594) < 1) || (Math.abs(width - 594) < 1 && Math.abs(height - 420) < 1)) {
|
|
|
+ return "A2";
|
|
|
+ } else if ((Math.abs(width - 594) < 1 && Math.abs(height - 841) < 1) || (Math.abs(width - 841) < 1 && Math.abs(height - 594) < 1)) {
|
|
|
+ return "A1";
|
|
|
+ }
|
|
|
+ return "Unknown";
|
|
|
+ }
|
|
|
+ public static boolean checkImageDPI(PDPage page, int dpi, int tolerance) throws IOException {
|
|
|
+ if (hasImages(page)) {
|
|
|
+ return checkPageDpi(page, dpi, tolerance);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断页面是否包含图片
|
|
|
+ *
|
|
|
+ * @param page 页面
|
|
|
+ * @return 如果包含图片,则返回true,否则返回false
|
|
|
+ */
|
|
|
+ private static boolean hasImages(PDPage page) {
|
|
|
+ return page.getResources().getXObjectNames().iterator().hasNext();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查单个PDF页面中的图片是否满足最小DPI要求。
|
|
|
+ * @param page 要检查的PDPage对象。
|
|
|
+ * @param requiredDpi 要求的最小DPI值。
|
|
|
+ * @param tolerance DPI的容差范围。
|
|
|
+ * @return 如果页面上所有图片都满足要求,则返回true;否则返回false。
|
|
|
+ * @throws IOException 如果处理页面内容流时发生错误。
|
|
|
+ */
|
|
|
+ private static boolean checkPageDpi(PDPage page, int requiredDpi, int tolerance) throws IOException {
|
|
|
+ final AtomicBoolean allOk = new AtomicBoolean(true);
|
|
|
+
|
|
|
+ // 创建一个自定义的图形流引擎
|
|
|
+ PDFGraphicsStreamEngine engine = new PDFGraphicsStreamEngine(page) {
|
|
|
+ @Override
|
|
|
+ public void drawImage(PDImage pdImage) throws IOException {
|
|
|
+ Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
|
|
|
+
|
|
|
+ // 图像的像素尺寸
|
|
|
+ int imageWidth = pdImage.getWidth();
|
|
|
+ int imageHeight = pdImage.getHeight();
|
|
|
+
|
|
|
+ // 从变换矩阵获取X和Y方向的缩放因子。
|
|
|
+ // 这个缩放因子表示图像在页面上被渲染后的大小(单位是磅, 1 inch = 72 points)。
|
|
|
+ float renderedWidthInPoints = ctm.getScalingFactorX();
|
|
|
+ float renderedHeightInPoints = ctm.getScalingFactorY();
|
|
|
+
|
|
|
+ // 将磅转换为英寸
|
|
|
+ float renderedWidthInInches = renderedWidthInPoints / 72f;
|
|
|
+ float renderedHeightInInches = renderedHeightInPoints / 72f;
|
|
|
+
|
|
|
+ // 避免除以零
|
|
|
+ if (renderedWidthInInches == 0 || renderedHeightInInches == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算实际的DPI
|
|
|
+ // 注意:对于旋转过的图片,这里的计算可能需要更复杂的处理,但对于常规的缩放,这是准确的。
|
|
|
+ //
|
|
|
+ long dpiX = Math.round(imageWidth / renderedWidthInInches);
|
|
|
+ long dpiY = Math.round(imageHeight / renderedHeightInInches);
|
|
|
+
|
|
|
+ System.out.printf("找到图片: 像素=[%d x %d], 渲染尺寸=[%.2f\" x %.2f\"], 计算DPI=[%d x %d]%n",
|
|
|
+ imageWidth, imageHeight, renderedWidthInInches, renderedHeightInInches, dpiX, dpiY);
|
|
|
+
|
|
|
+ // 检查DPI是否低于要求值(考虑容差)
|
|
|
+ if (dpiX < requiredDpi - tolerance || dpiY < requiredDpi - tolerance) {
|
|
|
+ System.err.printf("不合格! 图片DPI [%d x %d] 低于要求的 %d DPI%n", dpiX, dpiY, requiredDpi);
|
|
|
+ allOk.set(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void appendRectangle(java.awt.geom.Point2D p0, java.awt.geom.Point2D p1, java.awt.geom.Point2D p2, java.awt.geom.Point2D p3) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clip(int windingRule) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void moveTo(float x, float y) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void lineTo(float x, float y) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public java.awt.geom.Point2D getCurrentPoint() throws IOException {
|
|
|
+ return new java.awt.geom.Point2D.Float(0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void closePath() throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void endPath() throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void strokePath() throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void fillPath(int windingRule) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void fillAndStrokePath(int windingRule) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void shadingFill(org.apache.pdfbox.cos.COSName shadingName) throws IOException {
|
|
|
+ // 不需要处理,留空
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理当前页面的内容流
|
|
|
+ engine.processPage(page);
|
|
|
+
|
|
|
+ return allOk.get();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|