这比听起来更难——你可能需要重新考虑你的问题。文档内超链接通常通过链接注释来完成,其中目标设置为“转到视图”操作。这种观点不一定包括界限甚至一点。有时它只是一个页面(当前缩放)或一个页面(适合宽度)或一个页面(在顶部,特定缩放)。它甚至比这更复杂,因为链接目标可能是按顺序执行的操作树,每个操作都是 18 种不同可能的操作类型之一,包括可用于驱使查看者前往特定目的地的 javascript。
我认为你也会在“链接到你的地方”遇到麻烦。
您可以使用 Atalasoft dotAnnotate 和 PDF 文本提取插件在 C# 中完成很多此任务(免责声明,我为 Atalasoft 工作,编写了 PDF->annotations 导入器,并曾在 Acrobat v 1、2 和3)。不,对不起,它不是免费软件。
这就是我的做法(免责声明 - 这就是我的想法):
class PageAnnots : KeyValuePair<int, List<PdfLinkData>> { }
public PageAnnots GetPageLinkDestinations(Stream stm)
{
PdfAnnotationDataImporter importer = new PdfAnnotationDataImporter(stm);
List<PageAnnots> pageAnnots = new List<PageAnnots>();
try {
importer.Load();
// this gets all annotations on all pages. On long docs, this will be time consuming
AnnotationDataCollection allAnnots = importer.Import();
int pageNo = 0;
// allAnnots is a collection of LayerData, each LayerData object being a collection
// of annots for a page. The collection is empty if there are no annots
foreach (AnnotationData pageOfAnnots in allAnnots) {
List<PdfLinkData> linkAnnots = new List<PdfLinkData>();
LayerData pageLayer = pageOfAnnots as LayerData;
if (pageLayer != null) {
// filter out each annot that is a link
foreach (AnnotationData annot in pageLayer.Items) {
PdfLinkData link = annot as PdfLinkData;
if (link != null)
linkAnnots.Add(link);
}
}
if (linkAnnots.Count > 0) {
pageAnnots.Add(new PageAnnots(pageNo, linkAnnots));
}
pageNo++;
}
}
catch (Exception err) {
// keep it? drop it?
}
return pageAnnots;
}
此时,我们已将其简化为键值对的集合,每个键是页码,每个值是表示该页面上链接的 PdfLinkData 对象的非空列表。
从那里,您可以遍历此集合并尝试像这样计算目标:
private int PageFromDestination(PdfDestination dest)
{
PdfIndexedPageReference pageRef = dest.Page as PdfIndexedPageReference;
return pageRef == null ? -1 : pageRef.PageIndex;
}
public void FigureDestination(PdfLinkData link)
{
PdfActionList actions = link.ClickAction;
foreach (PdfAction action in actions) {
PdfGoToViewAction gotoView = action as PdfGoToViewAction;
if (action == null)
continue;
// this only pulls the page from the destination. The dest
// may also contain information about the view. I'm assuming you
// only want the page number
int page = PageFromDestination(gotoView.Destination);
if (page >= 0) {
// here's where you step in - the click action could be
// a long chain of things including several GoToView actions.
// it's up to you to decide what you want to do. Handle only
// action lists of length 1? Stop at first GoToView?
// aggregate them all?
}
}
}
当您查看此代码时,您会想知道为什么在索引页面引用、操作类型和操作列表方面存在这种抽象级别?答案是 GoToView 操作也可以引用另一个文档——跨文档链接在 PDF 中有效。虽然 dotAnnotate 现在不支持它们,但它有望在未来支持它们。类似地,该操作可以指示转到嵌入 PDF 文档中的视图(是的,您可以在 PDF 中嵌入 PDF)。
您需要注意 dotAnnotate 为您提供了一组有限的相当高级的对象,并且不需要您了解和理解 PDF 规范(太多)。过去,我们曾尝试将非常精细的 API 发布到 TIFF 之类的东西中,但发现我们的客户并不觉得它们可口。因此,我们试图猜测我们的客户可能想要和需要什么,并创建更易于消化的 API。
iText 和 iTextSharp 为您提供了对 API 的非常精细的控制,但您需要了解 PDF 规范才能获得所需的内容。
例如,要进行注释提取,您将必须打开文档,获取页面目录,遍历页面树,找到所有具有 Annots 键的页面字典,遍历 Annots 数组,搜索其中的每个字典以查找键/Type 的值为 /Annot 和键 /SubType 的值为 /Link,然后提取键 /Dest 的值(如果存在),如果它不为空,则使用该键,否则查看键 /A 并开始走动作树找到一个键 /Type 设置为 /GoTo (IIRC) 的动作,然后从那里开始。
目的地可以是直接目的地,也可以是命名目的地。如果它是一个命名的目的地,您将不得不返回文档目录并拉出名称树并在命名目的地中搜索名称,当您找到它时,将那里的信息拉出。
所以,是的,您可以使用 iText 或其他类似的 PDF 解析器,但您需要执行所有这些步骤,除非其中一位库创建者愿意为您执行此操作。