Sto usando PDFBox per convalidare un documento pdf. Ci sono alcuni requisiti per controllare i seguenti tipi di testo presenti in un PDF
Ho cercato nella lista API di PDFBOX ma non sono riuscito a trovare questo tipo di API.
Qualcuno può per favore aiutarmi e dire come determinare diversi tipi di stili di font / testo artificiali per essere presenti in un PDF usando PDFBOX.
In teoria si dovrebbe iniziare questo derivando una class da PDFTextStripper
e sovrascrivendo il suo metodo:
/** * Write a Java string to the output stream. The default implementation will ignore the
textPositions
* 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, ListtextPositions) throws IOException { writeString(text); }
L’override allora dovrebbe usare List
invece del String text
; ciascuna TextPosition
rappresenta essenzialmente una singola lettera singola e le informazioni sullo stato grafico attive quando tale lettera è stata disegnata.
Sfortunatamente, l’elenco textPositions
non contiene i contenuti corretti nella versione corrente 1.8.3. Ad esempio per la linea “Questo è normale testo”. dal tuo PDF il metodo writeString
viene chiamato quattro volte, una volta ciascuna per le stringhe “Questo”, “è”, “normale” e “testo”. Sfortunatamente l’elenco textPositions
ogni volta contiene le istanze TextPosition
per le lettere dell’ultima stringa “text”.
Questo in realtà si è dimostrato essere già stato riconosciuto come PDFBoxX PDFBOX-1804 che nel frattempo è stato risolto come risolto per le versioni 1.8.4 e 2.0.0.
Ciò è stato detto, non appena si dispone di una versione PDFBox che è fissa, è ansible verificare alcuni stili artificiali come segue:
Questo stile di testo è creato in questo modo nel contenuto della pagina:
BT /F0 1 Tf 24 0 5.10137 24 66 695.5877 Tm 0 Tr [<03>]TJ ...
La parte rilevante si verifica nell’impostazione della matrice di testo Tm . Il 5.10137 è un fattore per il quale il testo è tranciato.
Quando si controlla una posizione di TextPosition textPosition
come sopra indicato, è ansible interrogare questo valore usando
textPosition.getTextPos().getValue(1, 0)
Se questo valore è significativamente maggiore di 0.0, si dispone di corsivo artificiale. Se è significativamente inferiore a 0.0, hai un corsivo arretrato.
Questi stili artificiali usano doppie lettere di stampa utilizzando diverse modalità di rendering; ad esempio, la “T” maiuscola, in caso di grassetto:
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
(cioè, prima disegnare la lettera in modalità normale, riempiendo l’area della lettera e quindi disegnandola in modalità contorno, disegnando una linea lungo il bordo della lettera, entrambi in nero, CMYK 0, 0, 0, 1; questo lascia l’impressione di un lettera più spessa.)
e in caso di schema:
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
(vale a dire prima disegnando la lettera in modo normale bianco, CMYK 0, 0, 0, 0, riempiendo l’area della lettera e quindi disegnandola in modalità contorno, disegnando una linea lungo il bordo della lettera, in nero, CMYK 0, 0, 0 1, questo lascia l’impressione di una lettera nera su una lettera bianca).
Sfortunatamente il PDFBox PDFTextStripper
non tiene traccia della modalità di rendering del testo. Inoltre, elimina esplicitamente occorrenze di caratteri duplicati approssimativamente nella stessa posizione. Quindi, non è il compito di riconoscere questi stili artificiali.
Se davvero hai bisogno di farlo, dovresti cambiare TextPosition
per contenere anche la modalità di rendering, PDFStreamEngine
per aggiungerlo alle istanze di TextPosition
generate e PDFTextStripper
per non rilasciare glifi duplicati in processTextPosition
.
scrissi
Sfortunatamente il PDFBox
PDFTextStripper
non tiene traccia della modalità di rendering del testo.
Questo non è completamente vero, puoi trovare la modalità di rendering corrente usando getGraphicsState().getTextState().getRenderingMode()
. Ciò significa che durante processTextPosition
hai la modalità di rendering disponibile e puoi provare e memorizzare le informazioni sulla modalità di rendering (e sul colore!) Per la determinata posizione di TextPosition
da qualche parte, ad esempio in alcune Map
, per un uso successivo.
Inoltre, elimina esplicitamente occorrenze di caratteri duplicati approssimativamente nella stessa posizione.
È ansible disabilitare questo chiamando setSuppressDuplicateOverlappingText(false)
.
Con queste due modifiche dovresti essere in grado di eseguire anche i test richiesti per il controllo di grassetto artificiale e contorno.
Quest’ultima modifica potrebbe anche non essere necessaria se processTextPosition
e controlli gli stili all’inizio di processTextPosition
.
Come accennato in Correzioni , è ansible recuperare la modalità di rendering e le informazioni sul colore raccogliendo tali informazioni in una sovrascrittura processTextPosition
.
A questo l’OP ha commentato questo
Sempre il colore accarezzato e non accarezzato sta arrivando come nero
PDFTextStripper.properties
questo è stato un po ‘sorprendente, ma dopo aver esaminato il file PDFTextStripper.properties
(da cui sono stati inizializzati gli operatori supportati durante l’estrazione del testo), il motivo è diventato chiaro:
# The following operators are not relevant to text extraction, # so we can silently ignore them. ... K k
Pertanto gli operatori di impostazione del colore (specialmente quelli per i colors CMYK come nel presente documento) vengono ignorati in questo contesto! Fortunatamente le implementazioni di questi operatori per PageDrawer
possono essere utilizzate anche in questo contesto.
Quindi il seguente proof-of-concept mostra come tutte le informazioni richieste possono essere recuperate.
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 renderingMode = new HashMap(); Map strokingColor = new HashMap(); Map nonStrokingColor = new HashMap(); protected void writeString(String text, List 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(); } }
Usando questo ottieni il periodo ‘.’ nel testo normale come:
. - 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
Nel testo in grassetto artificiale ottieni;
. - 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
Nel corsivo artificiale:
. - 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
E in contorno artificiale:
. - 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
Quindi, ci sei, tutte le informazioni necessarie per il riconoscimento di quegli stili artificiali. Ora devi semplicemente analizzare i dati.
A proposito, dare un’occhiata al caso in grassetto artificiale: le coordinate potrebbero non essere sempre identiche ma invece semplicemente molto simili. Pertanto, per il test è necessaria una certa clemenza se due oggetti di posizione del testo descrivono la stessa posizione.
La mia soluzione per questo problema era di creare una nuova class che estenda la class PDFTextStripper e sovrascriva la funzione:
getCharactersByArticle()
nota: PDFBox versione 1.8.5
CustomPDFTextStripper class
public class CustomPDFTextStripper extends PDFTextStripper { public CustomPDFTextStripper() throws IOException { super(); } public Vector> getCharactersByArticle(){ return charactersByArticle; } }
In questo modo posso analizzare il documento pdf e quindi ottenere il TextPosition da una funzione di estrazione personalizzata:
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> vectorlistoftps = stripper.getCharactersByArticle(); for (int i = 0; i < vectorlistoftps.size(); i++) { List 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 contiene numerose informazioni sui caratteri del documento pdf.
PRODUZIONE:
Stringa [x: 168.24, y: 64.15997, altezza: 6.061287, spazio: 8.9664, larghezza: 3.4879303, yScala: 8.9664] J
String [x: 171.69745, y: 64.15997, altezza: 6.061287, spazio: 8.9664, larghezza: 2.2416077, yScale: 8.9664] N
String [x: 176.25777, y: 64.15997, altezza: 6.0343876, spazio: 8.9664, larghezza: 6.4737396, yScale: 8.9664] N
Stringa [x: 182.73778, y: 64.15997, altezza: 4.214208, spazio: 8.9664, larghezza: 3.981079, yScale: 8.9664] e .....