Получение разумных координат
Вы используете text.getXDirAdj()
и text.getYDirAdj()
в качестве x и y координат в потоке контента. Это не сработает, потому что координаты PDFBox, используемые при извлечении текста, преобразуются в систему координат, которую они предпочитают для извлечения текста, ср. JavaDocs:
/**
* This will get the text direction adjusted x position of the character.
* This is adjusted based on text direction so that the first character
* in that direction is in the upper left at 0,0.
*
* @return The x coordinate of the text.
*/
public float getXDirAdj()
/**
* This will get the y position of the text, adjusted so that 0,0 is upper left and it is
* adjusted based on the text direction.
*
* @return The adjusted y coordinate of the character.
*/
public float getYDirAdj()
Для TextPosition text
вместо этого следует использовать
text.getTextMatrix().getTranslatex()
и
text.getTextMatrix().getTranslateY()
Но даже эти цифры, возможно, придется исправить, ср. этот ответ , поскольку PDFBox умножил матрицу на перевод, сделав начало координат нижнего левого угла рамки обрезки.
Таким образом, если PDRectangle cropBox
- это рамка обрезки текущей страницы, используйте
text.getTextMatrix().getTranslatex() + cropBox.getLowerLeftX()
и
text.getTextMatrix().getTranslateY() + cropBox.getLowerLeftY()
(Эта координата нормализация PDFBox является PITA для всех, кто действительно хочет работать с текстовыми координатами ...)
Другие вопросы
У вашего кода есть некоторые другие проблемы, одна из которых становится понятной в документе, которым вы поделились: вы добавляете в поток содержимого страницы без сброса графического контекста:
PDPageContentStream contentStream = new PDPageContentStream(pdocument,
stripper.getCurrentPage(), true, true);
Конструктор с этой подписью предполагает, что вы не хотите сбрасывать контекст. Используйте параметр с дополнительным параметром boolean
и установите его на true
для запроса сброса контекста:
PDPageContentStream contentStream = new PDPageContentStream(pdocument,
stripper.getCurrentPage(), true, true, true);
Теперь контекст сброшен, и позиция снова в порядке.
Однако оба этих конструктора устарели и не должны использоваться по этой причине. В ветке разработки они уже удалены. Вместо этого используйте
PDPageContentStream contentStream = new PDPageContentStream(pdocument,
stripper.getCurrentPage(), AppendMode.APPEND, true, true);
Однако возникает еще одна проблема: вы создаете новый PDPageContentStream
для каждого writeString
вызова. Если это делается с помощью сброса контекста каждый раз, вложение пар saveGraphicsState / restoreGraphicsState может стать довольно глубоким. Таким образом, вы должны создать только один такой поток контента на страницу и использовать его во всех writeString
вызовах для этой страницы.
Таким образом, ваш подкласс стриппера может выглядеть так:
class CoverCharByImage extends PDFTextStripper {
public CoverCharByImage(PDImageXObject pdImage) throws IOException {
super();
this.pdImage = pdImage;
}
final PDImageXObject pdImage;
PDPageContentStream contentStream = null;
@Override
public void processPage(PDPage page) throws IOException {
super.processPage(page);
if (contentStream != null) {
contentStream.close();
contentStream = null;
}
}
@Override
protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
if (contentStream == null)
contentStream = new PDPageContentStream(document, getCurrentPage(), AppendMode.APPEND, true, true);
PDRectangle cropBox = getCurrentPage().getCropBox();
for (TextPosition text : textPositions) {
if (text.getUnicode().equals("a")) {
contentStream.drawImage(pdImage, text.getTextMatrix().getTranslateX() + cropBox.getLowerLeftX(),
text.getTextMatrix().getTranslateY() + cropBox.getLowerLeftY(),
text.getWidthDirAdj(), text.getHeightDir());
}
}
}
}
( CoverCharacterByImage внутренний класс)
и его можно использовать так:
PDDocument pdocument = PDDocument.load(...);
String imagePath = ...;
PDImageXObject pdImage = PDImageXObject.createFromFile(imagePath, pdocument);
CoverCharByImage stripper = new CoverCharByImage(pdImage);
stripper.setSortByPosition(true);
Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream());
stripper.writeText(pdocument, dummy);
pdocument.save(...);
( CoverCharacterByImage test testCoverLikeLez
)
в результате
* * Тысяча семьдесят-девять и т.д.