Мы использовали основанный на iText PdfVeryDenseMergeTool , который мы нашли в этом вопросе SO Как удалить пробел при объединении , чтобы объединить несколько файлов PDF в один файл PDF. Инструмент объединяет PDF-файлы, не оставляя пробелов между ними, и отдельные PDF-файлы также разбиваются по страницам, когда это возможно.
Мы хотим перенести PdfVeryDenseMergeTool в PDFBox. Мы нашли PDFBox 2 на основе PdfDenseMergeTool , который объединяет PDF-файлы следующим образом:
Отдельные PDF-файлы:
Плотный Объединенный PDF:
Мы ищем что-то подобное (это уже одна из основанных на iText PdfVeryDenseMergeTool , но мы хотим сделать это с помощью PDFBox 2):
В нашей попытке сделать портирование мы обнаружили, что PdfVeryDenseMergeTool использует PageVerticalAnalyzer , расширяющий слушатель iText PDF Render Listener и выполняющий каждый раз, когда текст, изображение или ar c рисуется в PDF. И вся информация рендеринга затем используется для разделения отдельных PDF на несколько страниц. Мы попытались найти похожего слушателя PDF Render Listener в PDFBox 2, но обнаружили, что в доступном классе PDFRenderer есть только методы рендеринга изображений. Поэтому мы не уверены, как перенести PageVerticalAnalyzer в PDFBox.
Если кто-то может предложить подход для продвижения вперед, мы будем очень благодарны за его помощь.
Спасибо lot!
РЕДАКТИРОВАТЬ 7 февраля 2020
В настоящее время мы расширяем PDFGraphicsStreamEngine от PDFBox для создания пользовательского рендеринга движок, который отслеживает координаты изображений, текстовых линий и дуг при их отрисовке. Этот пользовательский движок будет портом PageVerticalAnalyzer . После этого мы надеемся, что сможем перенести PdfVeryDenseMergeTool в PDFBox.
РЕДАКТИРОВАТЬ 8 февраля 2020
Вот очень простой порт PageVerticalAnalyzer , который обрабатывает изображения и текст. Я новичок в PDFBox ie, поэтому мой логик c для работы с изображениями, вероятно, шаткий. Вот базовый c подход:
Текст : для каждого напечатанного глифа получите bottomY и сделайте topY = bottomY + charHeight, отметьте эти верхние / нижние точки.
Изображение : для каждого вызова drawImage (), похоже, есть два способа выяснить, где он был нарисован. Первый использует координаты из последнего вызова appendRectangle (), а второй использует последние вызовы moveTo (), множественные lineTo () и closePath (). Я отдаю последнему приоритет. Если я не могу найти какой-либо путь (я нашел его в одном PDF, в другом, перед drawImage (), я нашел только appendRectangle ()), я использую первый. Если ни один из них не существует, я понятия не имею, что делать. Вот как я предполагаю, что PDFBox помечает координаты изображения с помощью moveTo () / lineTo () / closePath ():
Вот моя текущая реализация:
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.Vector;
public class PageVerticalAnalyzer extends PDFGraphicsStreamEngine
{
/**
* This is a port of iText based PageVerticalAnalyzer found here
* https://github.com/mkl-public/testarea-itext5/blob/master/src/main/java/mkl/testarea/itext5/merge/PageVerticalAnalyzer.java
*
* @param page PDF Page
*/
protected PageVerticalAnalyzer(PDPage page)
{
super(page);
}
public static void main(String[] args) throws IOException
{
File file = new File("q2.pdf");
try (PDDocument doc = PDDocument.load(file))
{
PDPage page = doc.getPage(0);
PageVerticalAnalyzer engine = new PageVerticalAnalyzer(page);
engine.run();
System.out.println(engine.verticalFlips);
}
}
/**
* Runs the engine on the current page.
*
* @throws IOException If there is an IO error while drawing the page.
*/
public void run() throws IOException
{
processPage(getPage());
for (PDAnnotation annotation : getPage().getAnnotations())
{
showAnnotation(annotation);
}
}
// All path related stuff
@Override
public void clip(int windingRule) throws IOException
{
System.out.println("clip");
}
@Override
public void moveTo(float x, float y) throws IOException
{
System.out.printf("moveTo %.2f %.2f%n", x, y);
lastPathBottomTop = new float[] {(Float) null, y};
}
@Override
public void lineTo(float x, float y) throws IOException
{
System.out.printf("lineTo %.2f %.2f%n", x, y);
lastLineTo = new float[] {x, y};
}
@Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
{
System.out.printf("curveTo %.2f %.2f, %.2f %.2f, %.2f %.2f%n", x1, y1, x2, y2, x3, y3);
}
@Override
public Point2D getCurrentPoint() throws IOException
{
// if you want to build paths, you'll need to keep track of this like PageDrawer does
return new Point2D.Float(0, 0);
}
@Override
public void closePath() throws IOException
{
System.out.println("closePath");
lastPathBottomTop[0] = lastLineTo[1];
lastLineTo = null;
}
@Override
public void endPath() throws IOException
{
System.out.println("endPath");
}
@Override
public void strokePath() throws IOException
{
System.out.println("strokePath");
}
@Override
public void fillPath(int windingRule) throws IOException
{
System.out.println("fillPath");
}
@Override
public void fillAndStrokePath(int windingRule) throws IOException
{
System.out.println("fillAndStrokePath");
}
@Override
public void shadingFill(COSName shadingName) throws IOException
{
System.out.println("shadingFill " + shadingName.toString());
}
// Rectangle related stuff
@Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException
{
System.out.printf("appendRectangle %.2f %.2f, %.2f %.2f, %.2f %.2f, %.2f %.2f%n",
p0.getX(), p0.getY(), p1.getX(), p1.getY(),
p2.getX(), p2.getY(), p3.getX(), p3.getY());
lastRectBottomTop = new float[] {(float) p0.getY(), (float) p3.getY()};
}
// Image drawing
@Override
public void drawImage(PDImage pdImage) throws IOException
{
System.out.println("drawImage");
if (lastPathBottomTop != null) {
addVerticalUseSection(lastPathBottomTop[0], lastPathBottomTop[1]);
} else if (lastRectBottomTop != null ){
addVerticalUseSection(lastRectBottomTop[0], lastRectBottomTop[1]);
} else {
throw new Error("Drawing image without last reference!");
}
lastPathBottomTop = null;
lastRectBottomTop = null;
}
// All text related stuff
@Override
public void showTextString(byte[] string) throws IOException
{
System.out.print("showTextString \"");
super.showTextString(string);
System.out.println("\"");
}
@Override
public void showTextStrings(COSArray array) throws IOException
{
System.out.print("showTextStrings \"");
super.showTextStrings(array);
System.out.println("\"");
}
@Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode,
Vector displacement) throws IOException
{
// print the actual character that is being rendered
System.out.print(unicode);
super.showGlyph(textRenderingMatrix, font, code, unicode, displacement);
// rendering matrix seems to contain bounding box of dimensions the char
// and an x/y point where bounding box starts
//System.out.println(textRenderingMatrix.toString());
// y of the bottom of the char
// not sure why the y value is in the 8th column
// when I print the matrix, it shows up in the 6th column
float yBottom = textRenderingMatrix.getValue(0, 7);
// height of the char
// using the value in the first column as the char height
float yTop = yBottom + textRenderingMatrix.getValue(0, 0);
addVerticalUseSection(yBottom, yTop);
}
// Keeping track of bottom/top point pairs
void addVerticalUseSection(float from, float to)
{
if (to < from)
{
float temp = to;
to = from;
from = temp;
}
int i=0, j=0;
for (; i<verticalFlips.size(); i++)
{
float flip = verticalFlips.get(i);
if (flip < from)
continue;
for (j=i; j<verticalFlips.size(); j++)
{
flip = verticalFlips.get(j);
if (flip < to)
continue;
break;
}
break;
}
boolean fromOutsideInterval = i%2==0;
boolean toOutsideInterval = j%2==0;
while (j-- > i)
verticalFlips.remove(j);
if (toOutsideInterval)
verticalFlips.add(i, to);
if (fromOutsideInterval)
verticalFlips.add(i, from);
}
final List<Float> verticalFlips = new ArrayList<Float>();
private float[] lastRectBottomTop;
private float[] lastPathBottomTop;
private float[] lastLineTo;
}
Я ищу ответы на следующие вопросы:
- Как улучшить эту реализацию?
- Как обрабатывать другие вещи, такие как кривые, которые я не обрабатывал?