Редактирование гиперссылок и якорей в PDF с помощью ITextSharp
Я использую библиотеку 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.
- Может ли Google Chrome открывать локальные ссылки?
- Пример использования гиперссылки в WPF
- Что такое синтаксис RTF для гиперссылки?
- Как я могу сделать ссылку на клики в NSAttributedString?
- Как создать ссылки в текстовом клиенте?
Пожалуйста, помогите мне здесь. Как редактировать и устанавливать гиперссылки в файле 4.pdf, чтобы он переместился в соответствующие файлы PDF.
Спасибо, Ашок
- Facebook share link - можете ли вы настроить текст сообщения?
- C # regex pattern для извлечения URL-адресов из заданной строки - не полный URL-адрес html, но также и ссылки
- Использование jQuery для программного перехода по ссылке
- Как ссылаться на метод в javadoc?
- jQuery отключить ссылку
- Связывание значения столбца в jqGrid с новой страницей с помощью GET
- Как сохранить: активный стиль css после нажатия элемента
- Scrapy: Следуйте ссылке, чтобы получить дополнительные данные элемента?
То, что вы хотите, вполне возможно. Для чего вам понадобится работать с низкоуровневыми PDF-объектами (PdfDictionary, PdfArray и т. Д.).
И всякий раз, когда кто-то должен работать с этими объектами, я всегда ссылаюсь на Справочник по PDF . В вашем случае вы захотите изучить главу 7 (особенно раздел 3) и главы 12, разделы 3 (навигационная система уровня документа) и 5 (annotations).
Предполагая, что вы прочитали это, вот что вам нужно сделать:
- Пройдите через массив аннотаций каждой страницы (в исходном документе, прежде чем разбить его).
- Найдите все annotations ссылок и их адресатов.
- Создайте новый пункт назначения для этой ссылки, соответствующий новому файлу.
- напишите это новое назначение в аннотацию ссылки.
- Запишите эту страницу в новый PDF-файл, используя PdfCopy (он скопирует annotations, а также содержимое страницы).
Шаг 1.1 не прост. Существует несколько различных форматов аннотаций «local goto». Вам нужно определить, на какую страницу ссылается данная ссылка. В некоторых ссылках можно указать эквивалент в формате PDF «следующая страница» или «предыдущая страница», в то время как другие будут содержать ссылку на определенную страницу. Это будет «косвенная ссылка на объект», а не номер страницы.
Чтобы определить номер страницы из ссылки на страницу, вам нужно … ouch. Хорошо. Наиболее эффективным способом было бы вызвать PdfReader.GetPageRef (int pageNum) для каждой страницы исходного документа и кэшировать его на карте (reference-> pageNum).
Затем вы можете создавать «удаленные удаленные» ссылки, создавая удаленный PODAction и записывая это в запись «A» (action) annotations ссылки, удаляя все, что было там раньше (возможно, «Dest»).
Я не очень хорошо говорю на C #, поэтому я оставлю реальную реализацию вам.
Хорошо, основываясь на том, что у @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
,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
Эта функция ниже использует iTextSharp для:
- Открыть PDF-файл
- Страница через PDF
- Осмотрите annotations на каждой странице для тех, которые являются ANCHORS
Шаг №4 заключается в том, чтобы вставить любую логику, которую вы хотите здесь, … обновите ссылки, запишите их и т. Д.
/// Inspects PDF files for internal links. /// 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 }
Удачи.