Как определить искусственный жирный стиль, искусственный курсивный стиль и стиль искусственного контура текста с помощью PDFBOX
Я использую PDFBox для проверки PDF документа. Существует определенное требование для проверки следующих типов текста в PDF
- Искусственный текст в стиле Bold
- Искусственный курсив стиль текста.
- Искусственный контурный стиль текста
Я выполнил поиск в списке API PDFBOX, но не смог найти такой API.
Может кто-нибудь, пожалуйста, помогите мне и расскажите, как определить различные типы искусственных шрифтов / стилей текста, которые будут присутствовать в PDF, используя PDFBOX.
2 ответа
Общая процедура и выпуск PDFBox
В теории это следует начинать с вывода класса из PDFTextStripper
и переопределяя его метод:
/**
* Write a Java string to the output stream. The default implementation will ignore the <code>textPositions</code>
* and just calls {@link #writeString(String)}.
*
* @param text The text to write to the stream.
* @param textPositions The TextPositions belonging to the text.
* @throws IOException If there is an error when writing the text.
*/
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
writeString(text);
}
Ваше переопределение тогда должно использовать List<TextPosition> textPositions
вместо String text
; каждый TextPosition
по сути, представляет собой одну букву и информацию о графическом состоянии, активную на момент написания этой буквы.
К сожалению textPositions
список не содержит правильное содержание в текущей версии 1.8.3. Например, для строки "Это обычный текст". из вашего PDF метода writeString
вызывается четыре раза, по одному для строк "This", " is", " normal" и " text". К сожалению textPositions
список каждый раз содержит TextPosition
экземпляры для букв последней строки "текст".
Фактически, это уже было признано проблемой PDFBox PDFBOX-1804, которая, тем не менее, была исправлена как исправленная для версий 1.8.4 и 2.0.0.
Это было сказано, как только у вас есть исправленная версия PDFBox, вы можете проверить некоторые искусственные стили следующим образом:
Искусственный курсив
Этот стиль текста создается следующим образом в содержимом страницы:
BT
/F0 1 Tf
24 0 5.10137 24 66 695.5877 Tm
0 Tr
[<03>]TJ
...
Соответствующая часть происходит при настройке текстовой матрицы Tm. 5.10137 является фактором, по которому текст сдвигается.
Когда вы проверяете TextPosition textPosition
как указано выше, вы можете запросить это значение, используя
textPosition.getTextPos().getValue(1, 0)
Если это значение больше 0,0, у вас есть искусственный курсив. Если оно меньше 0,0, у вас есть искусственный курсив в обратном направлении.
Искусственный жирный или набросок текста
Эти искусственные стили используют двойную печать букв с использованием разных режимов рендеринга; например, заглавная буква "Т", в случае жирного шрифта:
0 0 0 1 k
...
BT
/F0 1 Tf
24 0 0 24 66.36 729.86 Tm
<03>Tj
4 M 0.72 w
0 0 Td
1 Tr
0 0 0 1 K
<03>Tj
ET
(т. е. сначала рисуя букву в обычном режиме, заполняя область письма, а затем рисуя ее в режиме контура, рисуя линию вдоль границы буквы, оба черного цвета, CMYK 0, 0, 0, 1; это оставляет впечатление толстая буква.)
и в случае набросков:
BT
/F0 1 Tf
24 0 0 24 66 661.75 Tm
0 0 0 0 k
<03>Tj
/GS1 gs
4 M 0.288 w
0 0 Td
1 Tr
0 0 0 1 K
<03>Tj
ET
(т.е. сначала рисуем букву в обычном режиме белым цветом, CMYK 0, 0, 0, 0, заполняя область письма, а затем рисуем ее в режиме контура, рисуя линию вдоль границы буквы, черным цветом, CMYK 0, 0, 0, 1; это оставляет впечатление обведенного черным по белому буквы.)
К сожалению, PDFBox PDFTextStripper
не отслеживает режим рендеринга текста. Кроме того, он явно удаляет повторяющиеся вхождения символов примерно в одной и той же позиции. Таким образом, это не является задачей распознавания этих искусственных стилей.
Если вам действительно нужно это сделать, вам придется изменить TextPosition
также содержать режим рендеринга, PDFStreamEngine
добавить его в сгенерированный TextPosition
экземпляры и PDFTextStripper
не бросать дубликаты глифов в processTextPosition
,
исправления
я написал
К сожалению, PDFBox
PDFTextStripper
не отслеживает режим рендеринга текста.
Это не совсем верно, вы можете найти текущий режим рендеринга, используя getGraphicsState().getTextState().getRenderingMode()
, Это означает, что во время processTextPosition
у вас есть доступный режим рендеринга и вы можете попытаться сохранить информацию о режиме рендеринга (и цвет!) для заданного TextPosition
где-то, например, в некоторых Map<TextPosition, ...>
, для последующего использования.
Кроме того, он явно удаляет повторяющиеся вхождения символов примерно в одной и той же позиции.
Вы можете отключить это, позвонив setSuppressDuplicateOverlappingText(false)
,
С этими двумя изменениями вы также сможете выполнить необходимые тесты для проверки на искусственный жирный шрифт и контур.
Последнее изменение может даже не потребоваться, если вы храните и проверяете стили в начале processTextPosition
,
Как восстановить режим рендеринга и цвет
Как упомянуто в " Исправлениях", действительно возможно получить режим рендеринга и информацию о цвете, собирая эту информацию в processTextPosition
переопределения.
На это ФП отметил, что
Поглаживающий и не поглаживающий цвет всегда приходит как черный
Сначала это было немного удивительно, но, посмотрев на PDFTextStripper.properties
(из которого инициализируются операторы, поддерживаемые при извлечении текста), причина стала ясна:
# The following operators are not relevant to text extraction,
# so we can silently ignore them.
...
K
k
Таким образом, операторы настройки цвета (особенно те, что для цветов CMYK, как в настоящем документе) в этом контексте игнорируются! К счастью, реализации этих операторов для PageDrawer
можно использовать и в этом контексте.
Таким образом, следующее доказательство концепции показывает, как можно получить всю необходимую информацию.
public class TextWithStateStripperSimple extends PDFTextStripper
{
public TextWithStateStripperSimple() throws IOException {
super();
setSuppressDuplicateOverlappingText(false);
registerOperatorProcessor("K", new org.apache.pdfbox.util.operator.SetStrokingCMYKColor());
registerOperatorProcessor("k", new org.apache.pdfbox.util.operator.SetNonStrokingCMYKColor());
}
@Override
protected void processTextPosition(TextPosition text)
{
renderingMode.put(text, getGraphicsState().getTextState().getRenderingMode());
strokingColor.put(text, getGraphicsState().getStrokingColor());
nonStrokingColor.put(text, getGraphicsState().getNonStrokingColor());
super.processTextPosition(text);
}
Map<TextPosition, Integer> renderingMode = new HashMap<TextPosition, Integer>();
Map<TextPosition, PDColorState> strokingColor = new HashMap<TextPosition, PDColorState>();
Map<TextPosition, PDColorState> nonStrokingColor = new HashMap<TextPosition, PDColorState>();
protected void writeString(String text, List<TextPosition> textPositions) throws IOException
{
writeString(text + '\n');
for (TextPosition textPosition: textPositions)
{
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(textPosition.getCharacter())
.append(" - shear by ")
.append(textPosition.getTextPos().getValue(1, 0))
.append(" - ")
.append(textPosition.getX())
.append(" ")
.append(textPosition.getY())
.append(" - ")
.append(renderingMode.get(textPosition))
.append(" - ")
.append(toString(strokingColor.get(textPosition)))
.append(" - ")
.append(toString(nonStrokingColor.get(textPosition)))
.append('\n');
writeString(textBuilder.toString());
}
}
String toString(PDColorState colorState)
{
if (colorState == null)
return "null";
StringBuilder builder = new StringBuilder();
for (float f: colorState.getColorSpaceValue())
{
builder.append(' ')
.append(f);
}
return builder.toString();
}
}
Используя это, вы получаете период "." в обычном тексте как:
. - shear by 0.0 - 256.5701 88.6875 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
Вы получаете искусственный жирный текст;
. - shear by 0.0 - 378.86 122.140015 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
. - shear by 0.0 - 378.86002 122.140015 - 1 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
На искусственном курсиве:
. - shear by 5.10137 - 327.121 156.4123 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 1.0
И в искусственном изложении:
. - shear by 0.0 - 357.25 190.25 - 0 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 0.0
. - shear by 0.0 - 357.25 190.25 - 1 - 0.0 0.0 0.0 1.0 - 0.0 0.0 0.0 0.0
Итак, вот вам вся информация, необходимая для распознавания этих искусственных стилей. Теперь вам просто нужно проанализировать данные.
Кстати, взгляните на искусственный жирный случай: координаты не всегда могут быть идентичными, а просто очень похожими. Таким образом, требуется некоторое снисхождение для проверки того, описывают ли два объекта положения текста одну и ту же позицию.
Моим решением этой проблемы было создание нового класса, который расширяет класс PDFTextStripper и переопределяет функцию:
getCharactersByArticle()
примечание: PDFBox версия 1.8.5
CustomPDFTextStripper class
public class CustomPDFTextStripper extends PDFTextStripper
{
public CustomPDFTextStripper() throws IOException {
super();
}
public Vector<List<TextPosition>> getCharactersByArticle(){
return charactersByArticle;
}
}
Таким образом, я могу проанализировать документ PDF и затем получить TextPosition из пользовательской функции извлечения:
private void extractTextPosition() throws FileNotFoundException, IOException {
PDFParser parser = new PDFParser(new FileInputStream(pdf));
parser.parse();
StringWriter outString = new StringWriter();
CustomPDFTextStripper stripper = new CustomPDFTextStripper();
stripper.writeText(parser.getPDDocument(), outString);
Vector<List<TextPosition>> vectorlistoftps = stripper.getCharactersByArticle();
for (int i = 0; i < vectorlistoftps.size(); i++) {
List<TextPosition> tplist = vectorlistoftps.get(i);
for (int j = 0; j < tplist.size(); j++) {
TextPosition text = tplist.get(j);
System.out.println(" String "
+ "[x: " + text.getXDirAdj() + ", y: "
+ text.getY() + ", height:" + text.getHeightDir()
+ ", space: " + text.getWidthOfSpace() + ", width: "
+ text.getWidthDirAdj() + ", yScale: " + text.getYScale() + "]"
+ text.getCharacter());
}
}
}
TextPositions содержат многочисленные сведения о символах документа PDF.
ВЫХОД:
Строка [x: 168,24, y: 64,15997, высота: 6,061287, пространство: 8,9664, ширина: 3,4879303, масштаб Y: 8,9664]J
Строка [x: 171.69745, y: 64.15997, высота:6.061287, пространство: 8.9664, ширина: 2.2416077, yScale:8.9664]N
Строка [x: 176.25777, y: 64.15997, высота:6.0343876, пространство: 8.9664, ширина: 6.4737396, y Масштаб: 8.9664] N
Строка [x: 182.73778, y:64.15997, высота:4.214208, пространство: 8.9664, ширина: 3.981079, yScale: 8.9664]e .....