Come determinare lo stile grassetto artificiale, lo stile corsivo artificiale e lo stile di contorno artificiale di un testo utilizzando PDFBOX

Sto usando PDFBox per convalidare un documento pdf. Ci sono alcuni requisiti per controllare i seguenti tipi di testo presenti in un PDF

  • Testo in grassetto artificiale
  • Testo in stile corsivo artificiale.
  • Testo in stile contorno artificiale

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.

La procedura generale e un problema 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, List textPositions) throws IOException { writeString(text); } 

L’override allora dovrebbe usare List textPositions 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:

Testo corsivo artificiale

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.

Testo in grassetto o in grassetto artificiale

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 .

Correzioni

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 recuperare la modalità di rendering e il colore

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 .....