Obteniendo coordenadas de cadena utilizando ITextExtractionStrategy y LocationTextExtractionStrategy en Itextsharp

Tengo un archivo PDF que estoy leyendo en una cadena usando ITextExtractionStrategy. Ahora de la cadena estoy tomando una subcadena como My name is XYZ y necesito obtener las coordenadas rectangulares de la subcadena del archivo PDF pero no puedo hacerlo. En Google Tengo que saber que LocationTextExtractionStrategy pero no obtengo cómo usar esto para obtener las coordenadas.

Aquí está el código …

 ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy(); string currentText = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy); currentText = Encoding.UTF8.GetString(ASCIIEncoding.Convert(Encoding.Default, Encoding.UTF8, Encoding.Default.GetBytes(currentText))); text.Append(currentText); string getcoordinate="My name is XYZ"; 

¿Cómo puedo obtener la coordenada rectangular de esta subcadena usando ITEXTSHARP ..

Por favor ayuda.

Aquí hay una versión muy, muy simple de una implementación.

Antes de implementarlo es muy importante saber que los archivos PDF tienen un concepto cero de “palabras”, “párrafos”, “oraciones”, etc. Además, el texto dentro de un PDF no está necesariamente dispuesto de izquierda a derecha y de arriba hacia abajo, y esto no tiene nada. para hacer con lenguajes no LTR. La frase “Hola mundo” podría escribirse en el PDF como:

 Draw H at (10, 10) Draw ell at (20, 10) Draw rld at (90, 10) Draw o Wo at (50, 20) 

También podría escribirse como

 Draw Hello World at (10,10) 

La interfaz ITextExtractionStrategy que necesita implementar tiene un método llamado RenderText que se le llama una vez por cada fragmento de texto dentro de un PDF. Observe que dije “trozo” y no “palabra”. En el primer ejemplo anterior, el método sería llamado cuatro veces para esas dos palabras. En el segundo ejemplo, se llamaría una vez para esas dos palabras. Esta es la parte más importante de entender. Los PDF no tienen palabras y, debido a esto, iTextSharp tampoco tiene palabras. La parte de “palabra” está 100% a su scope para resolver.

También en este sentido, como dije antes, los PDF no tienen párrafos. La razón para tener esto en cuenta es que los PDF no pueden ajustar el texto a una nueva línea. Cada vez que ve algo que parece un retorno de párrafo, en realidad está viendo un nuevo comando de dibujo de texto que tiene una coordenada y diferente a la línea anterior. Vea esto para mayor discusión .

El siguiente código es una implementación muy simple. Para ello, estoy subclasificando LocationTextExtractionStrategy que ya implementa ITextExtractionStrategy . En cada llamada a RenderText() encuentro el rectángulo del fragmento actual (usando el código de Mark aquí ) y lo almacena para más adelante. Estoy usando esta clase de ayuda simple para almacenar estos trozos y rectangularjs:

 //Helper class that stores our rectangle and text public class RectAndText { public iTextSharp.text.Rectangle Rect; public String Text; public RectAndText(iTextSharp.text.Rectangle rect, String text) { this.Rect = rect; this.Text = text; } } 

Y aquí está la subclase:

 public class MyLocationTextExtractionStrategy : LocationTextExtractionStrategy { //Hold each coordinate public List myPoints = new List(); //Automatically called for each chunk of text in the PDF public override void RenderText(TextRenderInfo renderInfo) { base.RenderText(renderInfo); //Get the bounding box for the chunk of text var bottomLeft = renderInfo.GetDescentLine().GetStartPoint(); var topRight = renderInfo.GetAscentLine().GetEndPoint(); //Create a rectangle from it var rect = new iTextSharp.text.Rectangle( bottomLeft[Vector.I1], bottomLeft[Vector.I2], topRight[Vector.I1], topRight[Vector.I2] ); //Add this to our main collection this.myPoints.Add(new RectAndText(rect, renderInfo.GetText())); } } 

Y finalmente una implementación de lo anterior:

 //Our test file var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf"); //Create our test file, nothing special using (var fs = new FileStream(testFile, FileMode.Create, FileAccess.Write, FileShare.None)) { using (var doc = new Document()) { using (var writer = PdfWriter.GetInstance(doc, fs)) { doc.Open(); doc.Add(new Paragraph("This is my sample file")); doc.Close(); } } } //Create an instance of our strategy var t = new MyLocationTextExtractionStrategy(); //Parse page 1 of the document above using (var r = new PdfReader(testFile)) { var ex = PdfTextExtractor.GetTextFromPage(r, 1, t); } //Loop through each chunk found foreach (var p in t.myPoints) { Console.WriteLine(string.Format("Found text {0} at {1}x{2}", p.Text, p.Rect.Left, p.Rect.Bottom)); } 

No puedo enfatizar lo suficiente que lo anterior no tiene en cuenta las “palabras”, eso dependerá de usted. El objeto TextRenderInfo que pasa a RenderText tiene un método llamado GetCharacterRenderInfos() que puede usar para obtener más información. También es posible que desee utilizar GetBaseline() instead of GetDescentLine () `si no le importan los descensores de la fuente.

EDITAR

(Tuve un gran almuerzo, así que me siento un poco más servicial.)

Aquí hay una versión actualizada de MyLocationTextExtractionStrategy que hace lo que dicen mis comentarios a continuación, es decir, toma una cadena para buscar y busca cada fragmento de esa cadena. Por todos los motivos enumerados, esto no funcionará en algunos / muchos / la mayoría / todos los casos. Si la subcadena existe varias veces en un solo fragmento, solo devolverá la primera instancia. Ligaduras y diacríticos también podrían meterse con esto.

 public class MyLocationTextExtractionStrategy : LocationTextExtractionStrategy { //Hold each coordinate public List myPoints = new List(); //The string that we're searching for public String TextToSearchFor { get; set; } //How to compare strings public System.Globalization.CompareOptions CompareOptions { get; set; } public MyLocationTextExtractionStrategy(String textToSearchFor, System.Globalization.CompareOptions compareOptions = System.Globalization.CompareOptions.None) { this.TextToSearchFor = textToSearchFor; this.CompareOptions = compareOptions; } //Automatically called for each chunk of text in the PDF public override void RenderText(TextRenderInfo renderInfo) { base.RenderText(renderInfo); //See if the current chunk contains the text var startPosition = System.Globalization.CultureInfo.CurrentCulture.CompareInfo.IndexOf(renderInfo.GetText(), this.TextToSearchFor, this.CompareOptions); //If not found bail if (startPosition < 0) { return; } //Grab the individual characters var chars = renderInfo.GetCharacterRenderInfos().Skip(startPosition).Take(this.TextToSearchFor.Length).ToList(); //Grab the first and last character var firstChar = chars.First(); var lastChar = chars.Last(); //Get the bounding box for the chunk of text var bottomLeft = firstChar.GetDescentLine().GetStartPoint(); var topRight = lastChar.GetAscentLine().GetEndPoint(); //Create a rectangle from it var rect = new iTextSharp.text.Rectangle( bottomLeft[Vector.I1], bottomLeft[Vector.I2], topRight[Vector.I1], topRight[Vector.I2] ); //Add this to our main collection this.myPoints.Add(new RectAndText(rect, this.TextToSearchFor)); } 

Deberías usar esto igual que antes, pero ahora el constructor tiene un solo parámetro requerido:

 var t = new MyLocationTextExtractionStrategy("sample"); 

Es una vieja pregunta, pero dejo aquí mi respuesta ya que no pude encontrar una respuesta correcta en la web.

Como Chris Haas ha expuesto, no es fácil tratar con palabras ya que iText trata con trozos. El código que Chris post falló en la mayor parte de mi prueba porque una palabra normalmente está dividida en diferentes partes (advierte sobre eso en la publicación).

Para resolver ese problema aquí, es la estrategia que he usado:

  1. División de trozos en caracteres (en realidad objetos textrenderinfo por cada char)
  2. Grupo de caracteres por línea. Esto no es sencillo ya que tienes que lidiar con la alineación de los fragmentos.
  3. Busque la palabra que necesita encontrar para cada línea

Dejo aquí el código. Lo pruebo con varios documentos y funciona bastante bien, pero podría fallar en algunos escenarios porque es un poco complicado este fragmento -> transformación de palabras.

Espero que ayude a alguien.

  class LocationTextExtractionStrategyEx : LocationTextExtractionStrategy { private List m_DocChunks = new List(); private List m_LinesTextInfo = new List(); public List m_SearchResultsList = new List(); private String m_SearchText; public const float PDF_PX_TO_MM = 0.3528f; public float m_PageSizeY; public LocationTextExtractionStrategyEx(String sSearchText, float fPageSizeY) : base() { this.m_SearchText = sSearchText; this.m_PageSizeY = fPageSizeY; } private void searchText() { foreach (LineInfo aLineInfo in m_LinesTextInfo) { int iIndex = aLineInfo.m_Text.IndexOf(m_SearchText); if (iIndex != -1) { TextRenderInfo aFirstLetter = aLineInfo.m_LineCharsList.ElementAt(iIndex); SearchResult aSearchResult = new SearchResult(aFirstLetter, m_PageSizeY); this.m_SearchResultsList.Add(aSearchResult); } } } private void groupChunksbyLine() { LocationTextExtractionStrategyEx.ExtendedTextChunk textChunk1 = null; LocationTextExtractionStrategyEx.LineInfo textInfo = null; foreach (LocationTextExtractionStrategyEx.ExtendedTextChunk textChunk2 in this.m_DocChunks) { if (textChunk1 == null) { textInfo = new LocationTextExtractionStrategyEx.LineInfo(textChunk2); this.m_LinesTextInfo.Add(textInfo); } else if (textChunk2.sameLine(textChunk1)) { textInfo.appendText(textChunk2); } else { textInfo = new LocationTextExtractionStrategyEx.LineInfo(textChunk2); this.m_LinesTextInfo.Add(textInfo); } textChunk1 = textChunk2; } } public override string GetResultantText() { groupChunksbyLine(); searchText(); //In this case the return value is not useful return ""; } public override void RenderText(TextRenderInfo renderInfo) { LineSegment baseline = renderInfo.GetBaseline(); //Create ExtendedChunk ExtendedTextChunk aExtendedChunk = new ExtendedTextChunk(renderInfo.GetText(), baseline.GetStartPoint(), baseline.GetEndPoint(), renderInfo.GetSingleSpaceWidth(), renderInfo.GetCharacterRenderInfos().ToList()); this.m_DocChunks.Add(aExtendedChunk); } public class ExtendedTextChunk { public string m_text; private Vector m_startLocation; private Vector m_endLocation; private Vector m_orientationVector; private int m_orientationMagnitude; private int m_distPerpendicular; private float m_charSpaceWidth; public List m_ChunkChars; public ExtendedTextChunk(string txt, Vector startLoc, Vector endLoc, float charSpaceWidth,List chunkChars) { this.m_text = txt; this.m_startLocation = startLoc; this.m_endLocation = endLoc; this.m_charSpaceWidth = charSpaceWidth; this.m_orientationVector = this.m_endLocation.Subtract(this.m_startLocation).Normalize(); this.m_orientationMagnitude = (int)(Math.Atan2((double)this.m_orientationVector[1], (double)this.m_orientationVector[0]) * 1000.0); this.m_distPerpendicular = (int)this.m_startLocation.Subtract(new Vector(0.0f, 0.0f, 1f)).Cross(this.m_orientationVector)[2]; this.m_ChunkChars = chunkChars; } public bool sameLine(LocationTextExtractionStrategyEx.ExtendedTextChunk textChunkToCompare) { return this.m_orientationMagnitude == textChunkToCompare.m_orientationMagnitude && this.m_distPerpendicular == textChunkToCompare.m_distPerpendicular; } } public class SearchResult { public int iPosX; public int iPosY; public SearchResult(TextRenderInfo aCharcter, float fPageSizeY) { //Get position of upperLeft coordinate Vector vTopLeft = aCharcter.GetAscentLine().GetStartPoint(); //PosX float fPosX = vTopLeft[Vector.I1]; //PosY float fPosY = vTopLeft[Vector.I2]; //Transform to mm and get y from top of page iPosX = Convert.ToInt32(fPosX * PDF_PX_TO_MM); iPosY = Convert.ToInt32((fPageSizeY - fPosY) * PDF_PX_TO_MM); } } public class LineInfo { public string m_Text; public List m_LineCharsList; public LineInfo(LocationTextExtractionStrategyEx.ExtendedTextChunk initialTextChunk) { this.m_Text = initialTextChunk.m_text; this.m_LineCharsList = initialTextChunk.m_ChunkChars; } public void appendText(LocationTextExtractionStrategyEx.ExtendedTextChunk additionalTextChunk) { m_LineCharsList.AddRange(additionalTextChunk.m_ChunkChars); this.m_Text += additionalTextChunk.m_text; } } } 

Sé que esta es una pregunta muy antigua, pero a continuación es lo que terminé haciendo. Solo publíquelo aquí esperando que sea útil para otra persona.

El siguiente código le dirá las coordenadas iniciales de la (s) línea (s) que contiene un texto de búsqueda. No debería ser difícil modificarlo para dar posiciones de palabras. Nota. Probé esto en itextsharp 5.5.11.0 y no funcionará en algunas versiones anteriores

Como se mencionó anteriormente, los pdfs no tienen ningún concepto de palabras / líneas o párrafos. Pero descubrí que LocationTextExtractionStrategy hace un muy buen trabajo dividiendo líneas y palabras. Entonces mi solución está basada en eso.

RENUNCIA:

Esta solución se basa en el https://github.com/itext/itextsharp/blob/develop/src/core/iTextSharp/text/pdf/parser/LocationTextExtractionStrategy.cs y ese archivo tiene un comentario que dice que es una vista previa del desarrollador. Entonces esto podría no funcionar en el futuro.

De todos modos aquí está el código.

 using System.Collections.Generic; using iTextSharp.text.pdf.parser; namespace Logic { public class LocationTextExtractionStrategyWithPosition : LocationTextExtractionStrategy { private readonly List locationalResult = new List(); private readonly ITextChunkLocationStrategy tclStrat; public LocationTextExtractionStrategyWithPosition() : this(new TextChunkLocationStrategyDefaultImp()) { } /** * Creates a new text extraction renderer, with a custom strategy for * creating new TextChunkLocation objects based on the input of the * TextRenderInfo. * @param strat the custom strategy */ public LocationTextExtractionStrategyWithPosition(ITextChunkLocationStrategy strat) { tclStrat = strat; } private bool StartsWithSpace(string str) { if (str.Length == 0) return false; return str[0] == ' '; } private bool EndsWithSpace(string str) { if (str.Length == 0) return false; return str[str.Length - 1] == ' '; } /** * Filters the provided list with the provided filter * @param textChunks a list of all TextChunks that this strategy found during processing * @param filter the filter to apply. If null, filtering will be skipped. * @return the filtered list * @since 5.3.3 */ private List filterTextChunks(List textChunks, ITextChunkFilter filter) { if (filter == null) { return textChunks; } var filtered = new List(); foreach (var textChunk in textChunks) { if (filter.Accept(textChunk)) { filtered.Add(textChunk); } } return filtered; } public override void RenderText(TextRenderInfo renderInfo) { LineSegment segment = renderInfo.GetBaseline(); if (renderInfo.GetRise() != 0) { // remove the rise from the baseline - we do this because the text from a super/subscript render operations should probably be considered as part of the baseline of the text the super/sub is relative to Matrix riseOffsetTransform = new Matrix(0, -renderInfo.GetRise()); segment = segment.TransformBy(riseOffsetTransform); } TextChunk tc = new TextChunk(renderInfo.GetText(), tclStrat.CreateLocation(renderInfo, segment)); locationalResult.Add(tc); } public IList GetLocations() { var filteredTextChunks = filterTextChunks(locationalResult, null); filteredTextChunks.Sort(); TextChunk lastChunk = null; var textLocations = new List(); foreach (var chunk in filteredTextChunks) { if (lastChunk == null) { //initial textLocations.Add(new TextLocation { Text = chunk.Text, X = iTextSharp.text.Utilities.PointsToMillimeters(chunk.Location.StartLocation[0]), Y = iTextSharp.text.Utilities.PointsToMillimeters(chunk.Location.StartLocation[1]) }); } else { if (chunk.SameLine(lastChunk)) { var text = ""; // we only insert a blank space if the trailing character of the previous string wasn't a space, and the leading character of the current string isn't a space if (IsChunkAtWordBoundary(chunk, lastChunk) && !StartsWithSpace(chunk.Text) && !EndsWithSpace(lastChunk.Text)) text += ' '; text += chunk.Text; textLocations[textLocations.Count - 1].Text += text; } else { textLocations.Add(new TextLocation { Text = chunk.Text, X = iTextSharp.text.Utilities.PointsToMillimeters(chunk.Location.StartLocation[0]), Y = iTextSharp.text.Utilities.PointsToMillimeters(chunk.Location.StartLocation[1]) }); } } lastChunk = chunk; } //now find the location(s) with the given texts return textLocations; } } public class TextLocation { public float X { get; set; } public float Y { get; set; } public string Text { get; set; } } } 

Cómo llamar al método:

  using (var reader = new PdfReader(inputPdf)) { var parser = new PdfReaderContentParser(reader); var strategy = parser.ProcessContent(pageNumber, new LocationTextExtractionStrategyWithPosition()); var res = strategy.GetLocations(); reader.Close(); } var searchResult = res.Where(p => p.Text.Contains(searchText)).OrderBy(p => pY).Reverse().ToList(); inputPdf is a byte[] that has the pdf data pageNumber is the page where you want to search in