如何在有密度扫描仪等可用空间的pdf上插入图像或图章

我有一个pdf文件在哪里我在它的所有页面上添加一个标记。

但问题是,邮票被添加到每个页面的左上角。 如果页面在该部分中有文本,则标记将显示在文本上。

我的问题是,是否有任何方法可以读取每个页面,如果该部分中没有文本添加标记,则搜索最近的可用空间,就像密度扫描仪一样?

我正在使用IText和Java 1.7。

自由空间fider类和距离计算function与接受的答案中的相同。

以下是我正在使用的编辑代码:

// The resulting PDF file String RESULT = "K:\\DCIN_TER\\DCIN_EPU2\\CIRCUIT FROM BRANCH\\RAINBOW ORDERS\\" + jtfSONo.getText().trim() + "\\PADR Release\\Final PADR Release 1.pdf"; // Create a reader PdfReader reader = new PdfReader("K:\\DCIN_TER\\DCIN_EPU2\\CIRCUIT FROM BRANCH\\RAINBOW ORDERS\\" + jtfSONo.getText().trim() + "\\PADR Release\\Final PADR Release.pdf"); // Create a stamper PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(RESULT)); // Loop over the pages and add a footer to each page int n = reader.getNumberOfPages(); for(int i = 1; i <= n; i++) { Collection rectangles = find(reader, 300, 100, n, stamper); // minimum width & height of a rectangle Iterator itr = rectangles.iterator(); while(itr.hasNext()) { System.out.println(itr.next()); } if(!(rectangles.isEmpty()) && (rectangles.size() != 0)) { Rectangle2D best = null; double bestDist = Double.MAX_VALUE; Point2D.Double point = new Point2D.Double(200, 400); float x = 0, y = 0; for(Rectangle2D rectangle: rectangles) { double distance = distance(rectangle, point); if(distance < bestDist) { best = rectangle; bestDist = distance; x = (float) best.getX(); y = (float) best.getY(); int left = (int) best.getMinX(); int right = (int) best.getMaxX(); int top = (int) best.getMaxY(); int bottom = (int) best.getMinY(); System.out.println("x : " + x); System.out.println("y : " + y); System.out.println("left : " + left); System.out.println("right : " + right); System.out.println("top : " + top); System.out.println("bottom : " + bottom); } } getFooterTable(i, n).writeSelectedRows(0, -1, x, y, stamper.getOverContent(i)); // 0, -1 indicates 1st row, 1st column upto last row and last column } else getFooterTable(i, n).writeSelectedRows(0, -1, 94, 140, stamper.getOverContent(i)); // bottom left corner } // Close the stamper stamper.close(); // Close the reader reader.close(); public Collection find(PdfReader reader, float minWidth, float minHeight, int page, PdfStamper stamper) throws IOException { Rectangle cropBox = reader.getCropBox(page); Rectangle2D crop = new Rectangle2D.Float(cropBox.getLeft(), cropBox.getBottom(), cropBox.getWidth(), cropBox.getHeight()); FreeSpaceFinder finder = new FreeSpaceFinder(crop, minWidth, minHeight); PdfReaderContentParser parser = new PdfReaderContentParser(reader); parser.processContent(page, finder); System.out.println("finder.freeSpaces : " + finder.freeSpaces); return finder.freeSpaces; } // Create a table with page X of Y, @param x the page number, @param y the total number of pages, @return a table that can be used as footer public static PdfPTable getFooterTable(int x, int y) { java.util.Date date = new java.util.Date(); SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy"); String month = sdf.format(date); System.out.println("Month : " + month); PdfPTable table = new PdfPTable(1); table.setTotalWidth(120); table.setLockedWidth(true); table.getDefaultCell().setFixedHeight(20); table.getDefaultCell().setBorder(Rectangle.TOP); table.getDefaultCell().setBorder(Rectangle.LEFT); table.getDefaultCell().setBorder(Rectangle.RIGHT); table.getDefaultCell().setBorderColorTop(BaseColor.BLUE); table.getDefaultCell().setBorderColorLeft(BaseColor.BLUE); table.getDefaultCell().setBorderColorRight(BaseColor.BLUE); table.getDefaultCell().setBorderWidthTop(1f); table.getDefaultCell().setBorderWidthLeft(1f); table.getDefaultCell().setBorderWidthRight(1f); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); Font font1 = new Font(FontFamily.HELVETICA, 10, Font.BOLD, BaseColor.BLUE); table.addCell(new Phrase("CONTROLLED COPY", font1)); table.getDefaultCell().setFixedHeight(20); table.getDefaultCell().setBorder(Rectangle.LEFT); table.getDefaultCell().setBorder(Rectangle.RIGHT); table.getDefaultCell().setBorderColorLeft(BaseColor.BLUE); table.getDefaultCell().setBorderColorRight(BaseColor.BLUE); table.getDefaultCell().setBorderWidthLeft(1f); table.getDefaultCell().setBorderWidthRight(1f); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); Font font = new Font(FontFamily.HELVETICA, 10, Font.BOLD, BaseColor.RED); table.addCell(new Phrase(month, font)); table.getDefaultCell().setFixedHeight(20); table.getDefaultCell().setBorder(Rectangle.LEFT); table.getDefaultCell().setBorder(Rectangle.RIGHT); table.getDefaultCell().setBorder(Rectangle.BOTTOM); table.getDefaultCell().setBorderColorLeft(BaseColor.BLUE); table.getDefaultCell().setBorderColorRight(BaseColor.BLUE); table.getDefaultCell().setBorderColorBottom(BaseColor.BLUE); table.getDefaultCell().setBorderWidthLeft(1f); table.getDefaultCell().setBorderWidthRight(1f); table.getDefaultCell().setBorderWidthBottom(1f); table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(new Phrase("BLR DESIGN DEPT.", font1)); return table; } 

我的另一个答案集中在原始问题上,即如何在页面上找到给定最小尺寸的自由空间。

由于答案已经写完,OP提供的代码试图利用原始答案。

这个答案涉及该代码。

该代码有许多缺点。

  1. 页面上可用空间的选择取决于文档中的页面数。

    其原因是在页面循环开始时找到:

     for(int i = 1; i <= n; i++) { Collection rectangles = find(reader, 300, 100, n, stamper); ... 

    OP肯定意味着i ,而不是那里。 代码始终在最后一个文档页面上查找可用空间。

  2. 矩形低于应有的值。

    其原因在于检索和使用矩形坐标:

      x = (float) best.getX(); y = (float) best.getY(); ... getFooterTable(i, n).writeSelectedRows(0, -1, x, y, stamper.getOverContent(i)); 

    Rectangle2D方法getXgetY返回左下方矩形角的坐标; 另一方面, PdfPTable方法writeSelectedRows需要左上角的矩形角。 因此,应该使用getMaxY而不是getY

是否有任何方法可以读取每个页面,如果该部分中没有文本,则添加标记,否则搜索最近的可用空间,就像密度扫描仪一样?

iText不提供开箱即用的function。 但是,根据您想要回避的内容类型,您可以考虑将页面渲染为图像并在图像中查找白点或使用尝试查找没有文本的位置的策略进行文本提取。

分析页面的渲染版本的第一个替代方案将是单独问题的焦点,因为必须首先选择图像处理库。

然而,在许多情况下,第一种选择不是最好的方法。 例如,如果您只想逃避文本但不一定要图形(如水印),或者您还想要躲避不可见的文本(通常可以在PDF查看器中标记,因此会干扰您的添加)。

在这种情况下,第二种选择(使用iText的文本和图像提取function)可能是更合适的方法。

这里有一个用于此类任务的示例RenderListener

 public class FreeSpaceFinder implements RenderListener { // // constructors // public FreeSpaceFinder(Rectangle2D initialBox, float minWidth, float minHeight) { this(Collections.singleton(initialBox), minWidth, minHeight); } public FreeSpaceFinder(Collection initialBoxes, float minWidth, float minHeight) { this.minWidth = minWidth; this.minHeight = minHeight; freeSpaces = initialBoxes; } // // RenderListener implementation // @Override public void renderText(TextRenderInfo renderInfo) { Rectangle2D usedSpace = renderInfo.getAscentLine().getBoundingRectange(); usedSpace.add(renderInfo.getDescentLine().getBoundingRectange()); remove(usedSpace); } @Override public void renderImage(ImageRenderInfo renderInfo) { Matrix imageMatrix = renderInfo.getImageCTM(); Vector image00 = rect00.cross(imageMatrix); Vector image01 = rect01.cross(imageMatrix); Vector image10 = rect10.cross(imageMatrix); Vector image11 = rect11.cross(imageMatrix); Rectangle2D usedSpace = new Rectangle2D.Float(image00.get(Vector.I1), image00.get(Vector.I2), 0, 0); usedSpace.add(image01.get(Vector.I1), image01.get(Vector.I2)); usedSpace.add(image10.get(Vector.I1), image10.get(Vector.I2)); usedSpace.add(image11.get(Vector.I1), image11.get(Vector.I2)); remove(usedSpace); } @Override public void beginTextBlock() { } @Override public void endTextBlock() { } // // helpers // void remove(Rectangle2D usedSpace) { final double minX = usedSpace.getMinX(); final double maxX = usedSpace.getMaxX(); final double minY = usedSpace.getMinY(); final double maxY = usedSpace.getMaxY(); final Collection newFreeSpaces = new ArrayList(); for (Rectangle2D freeSpace: freeSpaces) { final Collection newFragments = new ArrayList(); if (freeSpace.intersectsLine(minX, minY, maxX, minY)) newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), freeSpace.getWidth(), minY-freeSpace.getMinY())); if (freeSpace.intersectsLine(minX, maxY, maxX, maxY)) newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), maxY, freeSpace.getWidth(), freeSpace.getMaxY() - maxY)); if (freeSpace.intersectsLine(minX, minY, minX, maxY)) newFragments.add(new Rectangle2D.Double(freeSpace.getMinX(), freeSpace.getMinY(), minX - freeSpace.getMinX(), freeSpace.getHeight())); if (freeSpace.intersectsLine(maxX, minY, maxX, maxY)) newFragments.add(new Rectangle2D.Double(maxX, freeSpace.getMinY(), freeSpace.getMaxX() - maxX, freeSpace.getHeight())); if (newFragments.isEmpty()) { add(newFreeSpaces, freeSpace); } else { for (Rectangle2D fragment: newFragments) { if (fragment.getHeight() >= minHeight && fragment.getWidth() >= minWidth) { add(newFreeSpaces, fragment); } } } } freeSpaces = newFreeSpaces; } void add(Collection rectangles, Rectangle2D addition) { final Collection toRemove = new ArrayList(); boolean isContained = false; for (Rectangle2D rectangle: rectangles) { if (rectangle.contains(addition)) { isContained = true; break; } if (addition.contains(rectangle)) toRemove.add(rectangle); } rectangles.removeAll(toRemove); if (!isContained) rectangles.add(addition); } // // members // public Collection freeSpaces = null; final float minWidth; final float minHeight; final static Vector rect00 = new Vector(0, 0, 1); final static Vector rect01 = new Vector(0, 1, 1); final static Vector rect10 = new Vector(1, 0, 1); final static Vector rect11 = new Vector(1, 1, 1); } 

使用此FreeSpaceFinder您可以在以下方法中找到具有给定最小尺寸的空白区域:

 public Collection find(PdfReader reader, float minWidth, float minHeight, int page) throws IOException { Rectangle cropBox = reader.getCropBox(page); Rectangle2D crop = new Rectangle2D.Float(cropBox.getLeft(), cropBox.getBottom(), cropBox.getWidth(), cropBox.getHeight()); FreeSpaceFinder finder = new FreeSpaceFinder(crop, minWidth, minHeight); PdfReaderContentParser parser = new PdfReaderContentParser(reader); parser.processContent(page, finder); return finder.freeSpaces; } 

对于您的任务,您现在必须从返回的矩形中选择最适合您的矩形。

请注意,此代码仍可能需要根据您的要求进行调整:

  • 它忽略剪辑路径,渲染模式,颜色和覆盖对象。 因此,它考虑所有文本和所有位图图像,无论它们实际上是否可见。
  • 它不考虑矢量图形(因为iText解析器包不考虑它们)。
  • 它不是很优化。

适用于此PDF页面:

样品发票

最小宽度为200,高度为50,你可以得到这些矩形:

 xywh 000,000 000,000 595,000 056,423 000,000 074,423 595,000 168,681 000,000 267,304 314,508 088,751 000,000 503,933 351,932 068,665 164,296 583,598 430,704 082,800 220,803 583,598 374,197 096,474 220,803 583,598 234,197 107,825 000,000 700,423 455,000 102,396 000,000 700,423 267,632 141,577 361,348 782,372 233,652 059,628 

或者,更直观地说,这里是页面上的矩形:

样品发票带有标记为至少200x50的自由矩形

纸平面是矢量图形,因此被忽略。

当然你也可以改变PDF渲染代码,不要绘制你想要忽略的东西,并且可视地绘制你想要忽略的原始隐形东西,然后使用位图图像分析……

编辑

在他的评论中,OP询问如何在find返回的矩形集合中find最接近给定点的矩形。

首先不一定是最近的矩形,可能有多个。

话虽如此,可以选择最近的矩形,如下所示:

首先需要计算点和矩形之间的距离,例如:

 double distance(Rectangle2D rectangle, Point2D point) { double x = point.getX(); double y = point.getY(); double left = rectangle.getMinX(); double right = rectangle.getMaxX(); double top = rectangle.getMaxY(); double bottom = rectangle.getMinY(); if (x < left) // point left of rect { if (y < bottom) // and below return Point2D.distance(x, y, left, bottom); if (y > top) // and top return Point2D.distance(x, y, left, top); return left - x; } if (x > right) // point right of rect { if (y < bottom) // and below return Point2D.distance(x, y, right, bottom); if (y > top) // and top return Point2D.distance(x, y, right, top); return x - right; } if (y < bottom) // and below return bottom - y; if (y > top) // and top return y - top; return 0; } 

使用此距离测量,可以使用类似这样的代码为Collection rectanglesPoint2D point选择最近的矩形:

 Rectangle2D best = null; double bestDist = Double.MAX_VALUE; for (Rectangle2D rectangle: rectangles) { double distance = distance(rectangle, point); if (distance < bestDist) { best = rectangle; bestDist = distance; } } 

在这之后best包含一个最好的矩形。

对于上面使用的示例文档,此方法返回页面角和左右中心的彩色矩形:

样品发票带有自由矩形,标注尺寸至少为200x50,填充最佳矩形

编辑二

从iText 5.5.6开始, RenderListener接口已经扩展为ExtRenderListener ,也可以通过路径构造和路径绘制操作发出信号。 因此,上面的FreeSpaceFinder也可以扩展为处理路径:

 // // Additional ExtRenderListener methods // @Override public void modifyPath(PathConstructionRenderInfo renderInfo) { List points = new ArrayList(); if (renderInfo.getOperation() == PathConstructionRenderInfo.RECT) { float x = renderInfo.getSegmentData().get(0); float y = renderInfo.getSegmentData().get(1); float w = renderInfo.getSegmentData().get(2); float h = renderInfo.getSegmentData().get(3); points.add(new Vector(x, y, 1)); points.add(new Vector(x+w, y, 1)); points.add(new Vector(x, y+h, 1)); points.add(new Vector(x+w, y+h, 1)); } else if (renderInfo.getSegmentData() != null) { for (int i = 0; i < renderInfo.getSegmentData().size()-1; i+=2) { points.add(new Vector(renderInfo.getSegmentData().get(i), renderInfo.getSegmentData().get(i+1), 1)); } } for (Vector point: points) { point = point.cross(renderInfo.getCtm()); Rectangle2D.Float pointRectangle = new Rectangle2D.Float(point.get(Vector.I1), point.get(Vector.I2), 0, 0); if (currentPathRectangle == null) currentPathRectangle = pointRectangle; else currentPathRectangle.add(pointRectangle); } } @Override public Path renderPath(PathPaintingRenderInfo renderInfo) { if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP) remove(currentPathRectangle); currentPathRectangle = null; return null; } @Override public void clipPath(int rule) { // TODO Auto-generated method stub } Rectangle2D.Float currentPathRectangle = null; 

( FreeSpaceFinderExt.java )

使用此类,上面的结果改进为

示例发票,其中包含至少200x50大小的自由矩形标记和最佳矩形填充,扩展渲染侦听器

如您所见,纸质平面和表格背景颜色现在也被考虑在内。