Как выделить текст или слово в PDF-файл, используя iTextsharp? - PullRequest
3 голосов
/ 29 июня 2011

Мне нужно найти слово в существующем файле PDF, и я хочу выделить текст или слово

и сохраните файл pdf

У меня есть идея, используя PdfAnnotation.CreateMarkup, мы могли бы найти положение текста и добавить к нему bgcolor ... но я не знаю, как это реализовать: (

Пожалуйста, помогите мне

Ответы [ 5 ]

4 голосов
/ 18 июня 2012

Я нашел, как это сделать, на всякий случай, если кому-то нужно получить слова или предложения с местоположениями (координатами) из документа PDF, вы найдете этот пример. Project ЗДЕСЬ Я использовал VB.NET 2010 для этого. Не забудьте добавить ссылку на вашу библиотеку iTextSharp в этом проекте.

Я добавил свой собственный класс стратегии TextExtraction, основанный на классе LocationTextExtractionStrategy. Я сосредоточился на TextChunks, потому что они уже имеют эти координаты.

Есть некоторые известные ограничения, такие как:

  • Не допускается многострочный поиск (фразы), допускаются только символы, слова или однострочные предложения.
  • Не работает с повернутым текстом.
  • Я не тестировал PDF-файлы с альбомной ориентацией страницы, но полагаю, что для этого могут потребоваться некоторые изменения.
  • В случае, если вам нужно нарисовать этот HighLight / прямоугольники над водяным знаком, вам нужно будет добавить / изменить некоторый код, но только код в форме, это не связано с процессом извлечения текста / местоположений.
4 голосов
/ 30 июня 2011

Это одна из тех вещей, "звучит просто, но на самом деле очень сложно".Смотрите сообщения Марка здесь и здесь .В конечном итоге вы, вероятно, будете указывать на LocationTextExtractionStrategy.Удачи!Если вы действительно узнаете, как это сделать, опубликуйте это здесь, есть несколько человек, которые задаются вопросом, что именно вам интересно!

1 голос
/ 14 марта 2017

Спасибо, Jcis!

После нескольких часов исследований и размышлений я нашел ваше решение, которое помогло мне решить мои проблемы.

было 2 маленьких ошибки.

сначала: штамп должен быть закрыт перед считывателем, в противном случае он вызывает исключение.

Public Sub PDFTextGetter(ByVal pSearch As String, ByVal SC As StringComparison, ByVal SourceFile As String, ByVal DestinationFile As String)
    Dim stamper As iTextSharp.text.pdf.PdfStamper = Nothing
    Dim cb As iTextSharp.text.pdf.PdfContentByte = Nothing

    Me.Cursor = Cursors.WaitCursor
    If File.Exists(SourceFile) Then
        Dim pReader As New PdfReader(SourceFile)

        stamper = New iTextSharp.text.pdf.PdfStamper(pReader, New System.IO.FileStream(DestinationFile, FileMode.Create))
        PB.Value = 0 : PB.Maximum = pReader.NumberOfPages
        For page As Integer = 1 To pReader.NumberOfPages
            Dim strategy As myLocationTextExtractionStrategy = New myLocationTextExtractionStrategy

            'cb = stamper.GetUnderContent(page)
            cb = stamper.GetOverContent(page)
            Dim state As New PdfGState()
            state.FillOpacity = 0.3F
            cb.SetGState(state)

            'Send some data contained in PdfContentByte, looks like the first is always cero for me and the second 100, but i'm not sure if this could change in some cases
            strategy.UndercontentCharacterSpacing = cb.CharacterSpacing
            strategy.UndercontentHorizontalScaling = cb.HorizontalScaling

            'It's not really needed to get the text back, but we have to call this line ALWAYS, 
            'because it triggers the process that will get all chunks from PDF into our strategy Object
            Dim currentText As String = PdfTextExtractor.GetTextFromPage(pReader, page, strategy)

            'The real getter process starts in the following line
            Dim MatchesFound As List(Of iTextSharp.text.Rectangle) = strategy.GetTextLocations(pSearch, SC)

            'Set the fill color of the shapes, I don't use a border because it would make the rect bigger
            'but maybe using a thin border could be a solution if you see the currect rect is not big enough to cover all the text it should cover
            cb.SetColorFill(BaseColor.PINK)

            'MatchesFound contains all text with locations, so do whatever you want with it, this highlights them using PINK color:

            For Each rect As iTextSharp.text.Rectangle In MatchesFound
                ' cb.Rectangle(rect.Left, rect.Bottom, rect.Width, rect.Height)
                cb.SaveState()
                cb.SetColorFill(BaseColor.YELLOW)
                cb.Rectangle(rect.Left, rect.Bottom, rect.Width, rect.Height)
                cb.Fill()
                cb.RestoreState()
            Next
            'cb.Fill()

            PB.Value = PB.Value + 1
        Next
        stamper.Close()
        pReader.Close()
    End If
    Me.Cursor = Cursors.Default

End Sub

секунда: ваше решение не работает, когда искомый текст находится в последней строке извлеченного текста.

    Public Function GetTextLocations(ByVal pSearchString As String, ByVal pStrComp As System.StringComparison) As List(Of iTextSharp.text.Rectangle)
        Dim FoundMatches As New List(Of iTextSharp.text.Rectangle)
        Dim sb As New StringBuilder()
        Dim ThisLineChunks As List(Of TextChunk) = New List(Of TextChunk)
        Dim bStart As Boolean, bEnd As Boolean
        Dim FirstChunk As TextChunk = Nothing, LastChunk As TextChunk = Nothing
        Dim sTextInUsedChunks As String = vbNullString

        ' For Each chunk As TextChunk In locationalResult
        For j As Integer = 0 To locationalResult.Count - 1
            Dim chunk As TextChunk = locationalResult(j)

            If chunk.text.Contains(pSearchString) Then
                Thread.Sleep(1)
            End If

            If ThisLineChunks.Count > 0 AndAlso (Not chunk.SameLine(ThisLineChunks.Last) Or j = locationalResult.Count - 1) Then
                If sb.ToString.IndexOf(pSearchString, pStrComp) > -1 Then
                    Dim sLine As String = sb.ToString

                    'Check how many times the Search String is present in this line:
                    Dim iCount As Integer = 0
                    Dim lPos As Integer
                    lPos = sLine.IndexOf(pSearchString, 0, pStrComp)
                    Do While lPos > -1
                        iCount += 1
                        If lPos + pSearchString.Length > sLine.Length Then Exit Do Else lPos = lPos + pSearchString.Length
                        lPos = sLine.IndexOf(pSearchString, lPos, pStrComp)
                    Loop

                    'Process each match found in this Text line:
                    Dim curPos As Integer = 0
                    For i As Integer = 1 To iCount
                        Dim sCurrentText As String, iFromChar As Integer, iToChar As Integer

                        iFromChar = sLine.IndexOf(pSearchString, curPos, pStrComp)
                        curPos = iFromChar
                        iToChar = iFromChar + pSearchString.Length - 1
                        sCurrentText = vbNullString
                        sTextInUsedChunks = vbNullString
                        FirstChunk = Nothing
                        LastChunk = Nothing

                        'Get first and last Chunks corresponding to this match found, from all Chunks in this line
                        For Each chk As TextChunk In ThisLineChunks
                            sCurrentText = sCurrentText & chk.text

                            'Check if we entered the part where we had found a matching String then get this Chunk (First Chunk)
                            If Not bStart AndAlso sCurrentText.Length - 1 >= iFromChar Then
                                FirstChunk = chk
                                bStart = True
                            End If

                            'Keep getting Text from Chunks while we are in the part where the matching String had been found
                            If bStart And Not bEnd Then
                                sTextInUsedChunks = sTextInUsedChunks & chk.text
                            End If

                            'If we get out the matching String part then get this Chunk (last Chunk)
                            If Not bEnd AndAlso sCurrentText.Length - 1 >= iToChar Then
                                LastChunk = chk
                                bEnd = True
                            End If

                            'If we already have first and last Chunks enclosing the Text where our String pSearchString has been found 
                            'then it's time to get the rectangle, GetRectangleFromText Function below this Function, there we extract the pSearchString locations
                            If bStart And bEnd Then
                                FoundMatches.Add(GetRectangleFromText(FirstChunk, LastChunk, pSearchString, sTextInUsedChunks, iFromChar, iToChar, pStrComp))
                                curPos = curPos + pSearchString.Length
                                bStart = False : bEnd = False
                                Exit For
                            End If
                        Next
                    Next
                End If
                sb.Clear()
                ThisLineChunks.Clear()
            End If
            ThisLineChunks.Add(chunk)
            sb.Append(chunk.text)
        Next

        Return FoundMatches
    End Function
1 голос
/ 20 июля 2012

@ Jcis, я фактически справился с обходом нескольких запросов, используя ваш пример в качестве отправной точки.Я использую ваш проект в качестве ссылки в ac # project и изменил то, что он делаетВместо того, чтобы просто выделять, я фактически рисую белый прямоугольник вокруг поискового запроса, а затем, используя координаты прямоугольника, помещаю поле формы.Мне также пришлось поменять режим записи contentbyte на getovercontent, чтобы полностью заблокировать искомый текст.На самом деле я создал строковый массив поисковых терминов, и затем, используя цикл for, я создаю столько разных текстовых полей, сколько мне нужно.

        Test.Form1 formBuilder = new Test.Form1();

        string[] fields = new string[] { "%AccountNumber%", "%MeterNumber%", "%EmailFieldHolder%", "%AddressFieldHolder%", "%EmptyFieldHolder%", "%CityStateZipFieldHolder%", "%emptyFieldHolder1%", "%emptyFieldHolder2%", "%emptyFieldHolder3%", "%emptyFieldHolder4%", "%emptyFieldHolder5%", "%emptyFieldHolder6%", "%emptyFieldHolder7%", "%emptyFieldHolder8%", "%SiteNameFieldHolder%", "%SiteNameFieldHolderWithExtraSpace%" };
        //int a = 0;
        for (int a = 0; a < fields.Length; )
        {
            string[] fieldNames = fields[a].Split('%');
            string[] fieldName = Regex.Split(fieldNames[1], "Field");
            formBuilder.PDFTextGetter(fields[a], StringComparison.CurrentCultureIgnoreCase, htmlToPdf, finalhtmlToPdf, fieldName[0]);
            File.Delete(htmlToPdf);
            System.Array.Clear(fieldNames, 0, 2);
            System.Array.Clear(fieldName, 0, 1);
            a++;
            if (a == fields.Length)
            {
                break;
            }
            string[] fieldNames1 = fields[a].Split('%');
            string[] fieldName1 = Regex.Split(fieldNames1[1], "Field");
            formBuilder.PDFTextGetter(fields[a], StringComparison.CurrentCultureIgnoreCase, finalhtmlToPdf, htmlToPdf, fieldName1[0]);
            File.Delete(finalhtmlToPdf);
            System.Array.Clear(fieldNames1, 0, 2);
            System.Array.Clear(fieldName1, 0, 1);
            a++;
        }

В вашем примере она возвращает функцию PDFTextGetterи вперед между двумя файлами, пока я не достигну готового продукта.Это работает очень хорошо, и это было бы невозможно без вашего первоначального проекта, так что спасибо вам за это.Я также изменил ваш VB таким образом, чтобы отображать текстовые поля так:

           For Each rect As iTextSharp.text.Rectangle In MatchesFound
                cb.Rectangle(rect.Left, rect.Bottom + 1, rect.Width, rect.Height + 4)
                Dim field As New TextField(stamper.Writer, rect, FieldName & Fields)
                Dim form = stamper.AcroFields
                Dim fieldKeys = form.Fields.Keys
                stamper.AddAnnotation(field.GetTextField(), page)
                Fields += 1
            Next

Просто подумал, что поделюсь тем, что мне удалось сделать с вашим проектом в качестве основы.Он даже увеличивает имена полей по мере необходимости.Мне также пришлось добавить новый параметр в вашу функцию, но это не стоит перечислять здесь.Еще раз спасибо за этот отличный старт.

0 голосов
/ 19 апреля 2017

Я конвертирую VB-проект Jcis в WpfApplication C # (файл на google-диске) и даже применяю исправления Boris , но проект не исправляет беги. Это очень ценно, если кто-то, кто понимает алгоритм программы, исправит это.

...