Редактирование гиперссылок и якорей в PDF с помощью ITextSharp - PullRequest
4 голосов
/ 05 июля 2011

Я использую библиотеку iTextSharp и C # .Net для разделения моего PDF-файла.

Рассмотрим PDF-файл sample.pdf, содержащий 72 страницы.Этот файл sample.pdf содержит страницы с гиперссылками, которые ведут на другую страницу.Например: на странице 4 есть три гиперссылки, которые при нажатии переходят к соответствующей 24-й, 27-й, 28-й странице.Как и на 4-й странице, есть почти 12 страниц, на которых есть эти гиперссылки.

Теперь, используя библиотеку iTextSharp, я разбил эти страницы PDF на 72 отдельных файла и сохранил их с именем 1.pdf, 2.pdf .... 72.pdf.Поэтому в 4.pdf при нажатии на эту гиперссылку мне нужно, чтобы PDF перешел на 24.pdf, 27.pdf, 28.pdf.

Пожалуйста, помогите мне здесь.Как я могу редактировать и устанавливать гиперссылки в 4.pdf, чтобы он перешел к соответствующим файлам PDF.

Спасибо, Ашок

Ответы [ 3 ]

6 голосов
/ 05 июля 2011

То, что вы хотите, вполне возможно.То, что вы хотите, потребует от вас работы с низкоуровневыми объектами PDF (PdfDictionary, PdfArray и т. Д.).

И всякий раз, когда кому-то нужно работать с этими объектами, я всегда отсылаю их к PDF Reference.В вашем случае вы захотите изучить главу 7 (особенно раздел 3) и главу 12, разделы 3 (навигация на уровне документа) и 5 ​​(аннотации).

Предполагая, что вы прочитали это, вот чтовам нужно сделать:

  1. Пройти по массиву аннотаций каждой страницы (в оригинальном документе, прежде чем разбивать его).
    1. Найти все аннотации ссылок и их места назначения.
    2. Создать новое место назначения для этой ссылки, соответствующее новому файлу.
    3. записать это новое место назначения в аннотацию ссылки.
  2. Запишите эту страницу в новый PDF-файл, используя PdfCopy (он будет копировать аннотации, а также содержимое страницы).

Шаг 1.1 не прост,Существует несколько различных форматов аннотаций "local goto".Вам необходимо определить, на какую страницу указывает данная ссылка.Некоторые ссылки могут содержать PDF-эквивалент «следующей страницы» или «предыдущей страницы», тогда как другие будут содержать ссылку на определенную страницу.Это будет «косвенная ссылка на объект», а не номер страницы.

Чтобы определить номер страницы по ссылке на страницу, вам нужно ... ой.Хорошо.Наиболее эффективным способом было бы вызвать PdfReader.GetPageRef (int pageNum) для каждой страницы в исходном документе и кэшировать ее на карте (reference-> pageNum).

Затем можно создать ссылки «удаленного перехода».путем создания удаленного действия goto PdfAction и записи его в запись аннотации «A» (действие), удалив все, что было там раньше (возможно, «Dest»).

Я не очень хорошо говорю на C #так что я оставлю вам фактическую реализацию.

3 голосов
/ 06 июля 2011

Хорошо, основываясь на том, что @Mark Storer вот какой-то стартовый код.Первый метод создает образец PDF с 10 страницами и несколькими ссылками на первой странице, которые перемещаются по разным частям PDF, поэтому нам есть над чем поработать.Второй метод открывает PDF-файл, созданный в первом методе, и просматривает каждую аннотацию, пытаясь выяснить, на какую страницу ссылается аннотация, и выводит ее в окно TRACE.Код написан на VB, но при необходимости его легко конвертировать в C #.Он нацелен на iTextSharp 5.1.1.0.

Если бы у меня была возможность, я мог бы попытаться пойти дальше и фактически разбить и заново связать вещи, но у меня сейчас нет времени.

Option Explicit On
Option Strict On

Imports iTextSharp.text
Imports iTextSharp.text.pdf
Imports System.IO

Public Class Form1
    ''//Folder that we are working in
    Private Shared ReadOnly WorkingFolder As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Hyperlinked PDFs")
    ''//Sample PDF
    Private Shared ReadOnly BaseFile As String = Path.Combine(WorkingFolder, "Sample.pdf")

    Private Shared Sub CreateSamplePdf()
        ''//Create our output directory if it does not exist
        Directory.CreateDirectory(WorkingFolder)

        ''//Create our sample PDF
        Using Doc As New iTextSharp.text.Document(PageSize.LETTER)
            Using FS As New FileStream(BaseFile, FileMode.Create, FileAccess.Write, FileShare.Read)
                Using writer = PdfWriter.GetInstance(Doc, FS)
                    Doc.Open()

                    ''//Turn our hyperlinks blue
                    Dim BlueFont As Font = FontFactory.GetFont("Arial", 12, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLUE)

                    ''//Create 10 pages with simple labels on them
                    For I = 1 To 10
                        Doc.NewPage()
                        Doc.Add(New Paragraph(String.Format("Page {0}", I)))
                        ''//On the first page add some links
                        If I = 1 Then

                            ''//Go to pages relative to this page
                            Doc.Add(New Paragraph(New Chunk("First Page", BlueFont).SetAction(New PdfAction(PdfAction.FIRSTPAGE))))

                            Doc.Add(New Paragraph(New Chunk("Next Page", BlueFont).SetAction(New PdfAction(PdfAction.NEXTPAGE))))

                            Doc.Add(New Paragraph(New Chunk("Prev Page", BlueFont).SetAction(New PdfAction(PdfAction.PREVPAGE)))) ''//This one does not make sense but is here for completeness

                            Doc.Add(New Paragraph(New Chunk("Last Page", BlueFont).SetAction(New PdfAction(PdfAction.LASTPAGE))))

                            ''//Go to a specific hard-coded page number
                            Doc.Add(New Paragraph(New Chunk("Go to page 5", BlueFont).SetAction(PdfAction.GotoLocalPage(5, New PdfDestination(0), writer))))
                        End If
                    Next
                    Doc.Close()
                End Using
            End Using
        End Using
    End Sub
    Private Shared Sub ListPdfLinks()

        ''//Setup some variables to be used later
        Dim R As PdfReader
        Dim PageCount As Integer
        Dim PageDictionary As PdfDictionary
        Dim Annots As PdfArray

        ''//Open our reader
        R = New PdfReader(BaseFile)
        ''//Get the page cont
        PageCount = R.NumberOfPages

        ''//Loop through each page
        For I = 1 To PageCount
            ''//Get the current page
            PageDictionary = R.GetPageN(I)

            ''//Get all of the annotations for the current page
            Annots = PageDictionary.GetAsArray(PdfName.ANNOTS)

            ''//Make sure we have something
            If (Annots Is Nothing) OrElse (Annots.Length = 0) Then Continue For

            ''//Loop through each annotation
            For Each A In Annots.ArrayList

                ''//I do not completely understand this but I think this turns an Indirect Reference into an actual object, but I could be wrong
                ''//Anyway, convert the itext-specific object as a generic PDF object
                Dim AnnotationDictionary = DirectCast(PdfReader.GetPdfObject(A), PdfDictionary)

                ''//Make sure this annotation has a link
                If Not AnnotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK) Then Continue For

                ''//Make sure this annotation has an ACTION
                If AnnotationDictionary.Get(PdfName.A) Is Nothing Then Continue For

                ''//Get the ACTION for the current annotation
                Dim AnnotationAction = DirectCast(AnnotationDictionary.Get(PdfName.A), PdfDictionary)

                ''//Test if it is a named actions such as /FIRST, /LAST, etc
                If AnnotationAction.Get(PdfName.S).Equals(PdfName.NAMED) Then
                    Trace.Write("GOTO:")
                    If AnnotationAction.Get(PdfName.N).Equals(PdfName.FIRSTPAGE) Then
                        Trace.WriteLine(1)
                    ElseIf AnnotationAction.Get(PdfName.N).Equals(PdfName.NEXTPAGE) Then
                        Trace.WriteLine(Math.Min(I + 1, PageCount)) ''//Any links that go past the end of the document should just go to the last page
                    ElseIf AnnotationAction.Get(PdfName.N).Equals(PdfName.LASTPAGE) Then
                        Trace.WriteLine(PageCount)
                    ElseIf AnnotationAction.Get(PdfName.N).Equals(PdfName.PREVPAGE) Then
                        Trace.WriteLine(Math.Max(I - 1, 1)) ''//Any links the go before the first page should just go to the first page
                    End If


                    ''//Otherwise see if its a GOTO page action
                ElseIf AnnotationAction.Get(PdfName.S).Equals(PdfName.GOTO) Then

                    ''//Make sure that it has a destination
                    If AnnotationAction.GetAsArray(PdfName.D) Is Nothing Then Continue For

                    ''//Once again, not completely sure if this is the best route but the ACTION has a sub DESTINATION object that is an Indirect Reference.
                    ''//The code below gets that IR, asks the PdfReader to convert it to an actual page and then loop through all of the pages
                    ''//to see which page the IR points to. Very inneficient but I could not find a way to get the page number based on the IR.

                    ''//AnnotationAction.GetAsArray(PdfName.D) gets the destination
                    ''//AnnotationAction.GetAsArray(PdfName.D).ArrayList(0) get the indirect reference part of the destination (.ArrayList(1) has fitting options)
                    ''//DirectCast(AnnotationAction.GetAsArray(PdfName.D).ArrayList(0), PRIndirectReference) turns it into a PRIndirectReference
                    ''//The full line gets us an actual page object (actually I think it could be any type of pdf object but I have not tested that).
                    ''//BIG NOTE: This line really should have a bunch more sanity checks in place
                    Dim AnnotationReferencedPage = PdfReader.GetPdfObject(DirectCast(AnnotationAction.GetAsArray(PdfName.D).ArrayList(0), PRIndirectReference))
                    Trace.Write("GOTO:")
                    ''//Re-loop through all of the pages in the main document comparing them to this page
                    For J = 1 To PageCount
                        If AnnotationReferencedPage.Equals(R.GetPageN(J)) Then
                            Trace.WriteLine(J)
                            Exit For
                        End If
                    Next
                End If
            Next
        Next
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        CreateSamplePdf()
        ListPdfLinks()
        Me.Close()
    End Sub
End Class
0 голосов
/ 09 декабря 2011

Эта функция ниже использует iTextSharp для:

  1. Открыть PDF
  2. Страница через PDF
  3. Проверьте аннотации на каждой странице для тех, которые являются ЯКОРАМИ

Шаг # 4 - вставить здесь любую логику, которую вы хотите ... обновить ссылки, зарегистрировать их и т. Д.

    /// <summary>Inspects PDF files for internal links.
    /// </summary>
    public static void FindPdfDocsWithInternalLinks()
    {
        foreach (var fi in PdfFiles) {
            try {
                var reader = new PdfReader(fi.FullName);
                // Pagination
                for(var i = 1; i <= reader.NumberOfPages; i++) {
                    var pageDict = reader.GetPageN(i);
                    var annotArray = (PdfArray)PdfReader.GetPdfObject(pageDict.Get(PdfName.ANNOTS));
                    if (annotArray == null) continue;
                    if (annotArray.Length <= 0) continue;
                    // check every annotation on the page
                    foreach (var annot in annotArray.ArrayList) {
                        var annotDict = (PdfDictionary)PdfReader.GetPdfObject(annot);
                        if (annotDict == null) continue;
                        var subtype = annotDict.Get(PdfName.SUBTYPE).ToString();
                        if (subtype != "/Link") continue;
                        var linkDict = (PdfDictionary)annotDict.GetDirectObject(PdfName.A);
                        if (linkDict == null) continue;
                        // if it makes it this far, its an Anchor annotation
                        // so we can grab it's URI
                        var sUri = linkDict.Get(PdfName.URI).ToString();
                        if (String.IsNullOrEmpty(sUri)) continue;
                    }
                }
                reader.Close();
            }
            catch (InvalidPdfException e)
            {
                if (!fi.FullName.Contains("_vti_cnf"))
                    Console.WriteLine("\r\nInvalid PDF Exception\r\nFilename: " + fi.FullName + "\r\nException:\r\n" + e);
                continue;
            }
            catch (NullReferenceException e) 
            {
                if (!fi.FullName.Contains("_vti_cnf"))
                    Console.WriteLine("\r\nNull Reference Exception\r\nFilename: " + fi.Name + "\r\nException:\r\n" + e);
                continue;
            }
        }

        // DO WHATEVER YOU WANT HERE
    }

Удачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...