使用 Quartz 在 iOS 上获取 PDF 超链接

Posted

技术标签:

【中文标题】使用 Quartz 在 iOS 上获取 PDF 超链接【英文标题】:Get PDF hyperlinks on iOS with Quartz 【发布时间】:2011-05-04 01:58:30 【问题描述】:

我整天都在尝试从我的 iPad 应用程序中的 PDF 中获取超链接元数据。 CGPDF* API 是一场真正的噩梦,我在网上找到的关于这一切的唯一信息是我必须查找“Annots”字典,但我只是在我的 PDF 中找不到它。

我什至使用旧的Voyeur Xcode sample 来检查我的测试 PDF 文件,但没有找到这个“注释”字典的踪迹...

你知道,这是我在每个 PDF 阅读器上看到的一个功能——同样的问题有 been asked multiple times 这里没有真正实用的答案。我通常从不直接要求示例代码,但显然这一次我真的需要它......有人得到这个工作,可能有示例代码吗?

更新:我刚刚意识到完成我的 PDF 测试的那个人刚刚插入了一个 URL 作为文本,而不是真正的注释。他尝试添加注释,我的代码现在可以工作了……但这不是我需要的,所以看来我必须分析文本并搜索 URL。但那是另一回事了……

更新 2:所以我终于想出了一些工作代码。我把它贴在这里所以希望它会帮助别人。它假定 PDF 文档实际上包含注释。

for(int i=0; i<pageCount; i++) 
    CGPDFPageRef page = CGPDFDocumentGetPage(doc, i+1);

    CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(page);

    CGPDFArrayRef outputArray;
    if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)) 
        return;
    

    int arrayCount = CGPDFArrayGetCount( outputArray );
    if(!arrayCount) 
        continue;
    

    for( int j = 0; j < arrayCount; ++j ) 
        CGPDFObjectRef aDictObj;
        if(!CGPDFArrayGetObject(outputArray, j, &aDictObj)) 
            return;
        

        CGPDFDictionaryRef annotDict;
        if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) 
            return;
        

        CGPDFDictionaryRef aDict;
        if(!CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) 
            return;
        

        CGPDFStringRef uriStringRef;
        if(!CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) 
            return;
        

        CGPDFArrayRef rectArray;
        if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) 
            return;
        

        int arrayCount = CGPDFArrayGetCount( rectArray );
        CGPDFReal coords[4];
        for( int k = 0; k < arrayCount; ++k ) 
            CGPDFObjectRef rectObj;
            if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) 
                return;
            

            CGPDFReal coord;
            if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) 
                return;
            

            coords[k] = coord;
                       

        char *uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);

        NSString *uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
        CGRect rect = CGRectMake(coords[0],coords[1],coords[2],coords[3]);

        CGPDFInteger pageRotate = 0;
        CGPDFDictionaryGetInteger( pageDictionary, "Rotate", &pageRotate ); 
        CGRect pageRect = CGRectIntegral( CGPDFPageGetBoxRect( page, kCGPDFMediaBox ));
        if( pageRotate == 90 || pageRotate == 270 ) 
            CGFloat temp = pageRect.size.width;
            pageRect.size.width = pageRect.size.height;
            pageRect.size.height = temp;
        

        rect.size.width -= rect.origin.x;
        rect.size.height -= rect.origin.y;

        CGAffineTransform trans = CGAffineTransformIdentity;
        trans = CGAffineTransformTranslate(trans, 0, pageRect.size.height);
        trans = CGAffineTransformScale(trans, 1.0, -1.0);

        rect = CGRectApplyAffineTransform(rect, trans);

        // do whatever you need with the coordinates.
        // e.g. you could create a button and put it on top of your page
        // and use it to open the URL with UIApplication's openURL
    

【问题讨论】:

第 6 行,不应该是 continue 而不是 return 吗? - 为什么检查对象、值、字典、字符串、数组等后返回。 这只是示例代码,没有任何错误检查。 PDF rects don't translate to native rects 有关详细信息,请参阅我的线程:向下滚动至:“其他 PDF 功能”、“在 PDF 中获取链接”、“了解 PDF 矩形以进行链接定位”@ 987654326@ 我正在做 rect.size.width -= rect.origin.x; rect.size.height -= rect.origin.y; 来解决这个问题,它对我有用.. 是的,它适用于 w&h,但 pdf 规范指出:数组采用 [llx lly urx ury] 的形式,指定左下 x、左下 y、右上 x 和上-矩形的右 y 坐标,按此顺序。这意味着您的 rect.origin.y 实际上是 rect.origin.y+rect.size.height,因为 adobe rect 是左下角,而不是 CGRect 默认的左上角。它可能没有那么明显,因为它可能只有 20-30 像素并且仍然注册您的媒体 【参考方案1】:

这是获取至少每个页面的注释 CGPDFDictionary 的基本思想。之后,您应该能够在 Adob​​e 的 PDF 规范的帮助下弄清楚它。

1.) 获取 CGPDFDocumentRef。

2.) 获取每一页。

3.) 在每一页上,使用CGPDFDictionaryGetArray(pageDictionary, "Annots", &amp;outputArray),其中 pageDictionary 是表示 CGPDFPage 的 CGPDFDictionary,而 outputArray 是用于存储该页面的 Annots 数组的变量 (CGPDFArrayRef)。

【讨论】:

@Jesse Naugher:非常感谢您的回答,但是:“在那之后,您应该能够在 Adob​​e 的 PDF 规范的帮助下解决这个问题”我找不到任何有用的信息那个臃肿的烂摊子就是 Adob​​e 的 PDF 规范。其中“注释”一词出现的唯一部分是第 8 节,但同样,我在这里看不到任何可以帮助我的信息... frustration 有一个完整的部分介绍了 pdf 文档中可能出现的各种注释,包括链接注释。基本上,当您获得 Annotations Array 时,您会遍历它,每个条目都是一个字典,is 是一个注解。这些字典有一个名为“子类型”的键,用于确定注释的类型,“链接”就是其中之一,并在 pdf 规范中定义。 @Jesse Naugher:太棒了,我刚刚意识到我盯着错误的文档——现在我有了真正的 PDF 规范文档。我现在就去看看,谢谢(是的,当你累/沮丧时会发生这种情况)。 @Jesse Naugher:CGPDFDictionaryGetArray(pageDictionary, "Annots", &amp;outputArray) 为我返回 false...这是我获取 pageDictionary 的方法:CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(page); 确保您正确获取 pdf 本身,并且您拥有的页面是有效的,并且上面有注释。您必须分别检查每个页面的注释【参考方案2】:

很棒的代码,但在我的项目中使用它时遇到了一些麻烦。它正确地获取了所有 URL,但是当我单击它时,什么也没有发生。这是我的代码,我必须稍微修改你的代码才能使用我的项目)。是不是少了什么:

- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx 
//CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index+1);

CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index+1);
CGAffineTransform transform1 = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFMediaBox),
                                         CGContextGetClipBoundingBox(ctx));
CGContextConcatCTM(ctx, transform1);
CGContextDrawPDFPage(ctx, page);

int pageCount = CGPDFDocumentGetNumberOfPages(pdf);
int i = 0;
while (i<pageCount) 
    i++;
    CGPDFPageRef page = CGPDFDocumentGetPage(pdf, i+1);

    CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(page);

    CGPDFArrayRef outputArray;
    if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)) 
        return;
    

    int arrayCount = CGPDFArrayGetCount( outputArray );
    if(!arrayCount) 
        continue;
    

    for( int j = 0; j < arrayCount; ++j ) 
        CGPDFObjectRef aDictObj;
        if(!CGPDFArrayGetObject(outputArray, j, &aDictObj)) 
            return;
        

        CGPDFDictionaryRef annotDict;
        if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) 
            return;
        

        CGPDFDictionaryRef aDict;
        if(!CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) 
            return;
        

        CGPDFStringRef uriStringRef;
        if(!CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) 
            return;
        

        CGPDFArrayRef rectArray;
        if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) 
            return;
        

        int arrayCount = CGPDFArrayGetCount( rectArray );
        CGPDFReal coords[4];
        for( int k = 0; k < arrayCount; ++k ) 
            CGPDFObjectRef rectObj;
            if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) 
                return;
            

            CGPDFReal coord;
            if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) 
                return;
            

            coords[k] = coord;
                       

        char *uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);

        NSString *uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
        CGRect rect = CGRectMake(coords[0],coords[1],coords[2],coords[3]);

        CGPDFInteger pageRotate = 0;
        CGPDFDictionaryGetInteger( pageDictionary, "Rotate", &pageRotate ); 
        CGRect pageRect = CGRectIntegral( CGPDFPageGetBoxRect( page, kCGPDFMediaBox ));
        if( pageRotate == 90 || pageRotate == 270 ) 
            CGFloat temp = pageRect.size.width;
            pageRect.size.width = pageRect.size.height;
            pageRect.size.height = temp;
        

        rect.size.width -= rect.origin.x;
        rect.size.height -= rect.origin.y;

        CGAffineTransform trans = CGAffineTransformIdentity;
        trans = CGAffineTransformTranslate(trans, 0, pageRect.size.height);
        trans = CGAffineTransformScale(trans, 1.0, -1.0);

        rect = CGRectApplyAffineTransform(rect, trans);

        // do whatever you need with the coordinates.
        // e.g. you could create a button and put it on top of your page
        // and use it to open the URL with UIApplication's openURL
        NSURL *url = [NSURL URLWithString:uri];
        NSLog(@"URL: %@", url);
        CGPDFContextSetURLForRect(ctx, (CFURLRef)url, rect);
       // CFRelease(url);
        
       



谢谢,BrainFeeder 做得很好!

更新:

对于在您的应用程序中使用叶子项目的任何人,这就是我使 PDF 链接工作的方式(它并不完美,因为 rect 似乎填满了整个屏幕,但这是一个开始):

- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx 

CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index+1);
CGAffineTransform transform1 = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFMediaBox),
                                         CGContextGetClipBoundingBox(ctx));
CGContextConcatCTM(ctx, transform1);
CGContextDrawPDFPage(ctx, page);


    CGPDFPageRef pageAd = CGPDFDocumentGetPage(pdf, index);

    CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(pageAd);

    CGPDFArrayRef outputArray;
    if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)) 
        return;
    

    int arrayCount = CGPDFArrayGetCount( outputArray );
    if(!arrayCount) 
        //continue;
    

    for( int j = 0; j < arrayCount; ++j ) 
        CGPDFObjectRef aDictObj;
        if(!CGPDFArrayGetObject(outputArray, j, &aDictObj)) 
            return;
        

        CGPDFDictionaryRef annotDict;
        if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) 
            return;
        

        CGPDFDictionaryRef aDict;
        if(!CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) 
            return;
        

        CGPDFStringRef uriStringRef;
        if(!CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) 
            return;
        

        CGPDFArrayRef rectArray;
        if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) 
            return;
        

        int arrayCount = CGPDFArrayGetCount( rectArray );
        CGPDFReal coords[4];
        for( int k = 0; k < arrayCount; ++k ) 
            CGPDFObjectRef rectObj;
            if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) 
                return;
            

            CGPDFReal coord;
            if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) 
                return;
            

            coords[k] = coord;
                       

        char *uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);

        NSString *uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
        CGRect rect = CGRectMake(coords[0],coords[1],coords[2],coords[3]);

        CGPDFInteger pageRotate = 0;
        CGPDFDictionaryGetInteger( pageDictionary, "Rotate", &pageRotate ); 
        CGRect pageRect = CGRectIntegral( CGPDFPageGetBoxRect( page, kCGPDFMediaBox ));
        if( pageRotate == 90 || pageRotate == 270 ) 
            CGFloat temp = pageRect.size.width;
            pageRect.size.width = pageRect.size.height;
            pageRect.size.height = temp;
        

        rect.size.width -= rect.origin.x;
        rect.size.height -= rect.origin.y;

        CGAffineTransform trans = CGAffineTransformIdentity;
        trans = CGAffineTransformTranslate(trans, 0, pageRect.size.height);
        trans = CGAffineTransformScale(trans, 1.0, -1.0);

        rect = CGRectApplyAffineTransform(rect, trans);

            // do whatever you need with the coordinates.
            // e.g. you could create a button and put it on top of your page
            // and use it to open the URL with UIApplication's openURL
            NSURL *url = [NSURL URLWithString:uri];
            NSLog(@"URL: %@", url);
//          CGPDFContextSetURLForRect(ctx, (CFURLRef)url, rect);
            UIButton *button = [[UIButton alloc] initWithFrame:rect];
            [button setTitle:@"LINK" forState:UIControlStateNormal];
            [button addTarget:self action:@selector(openLink:) forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];
           // CFRelease(url);
        
    // 

最终更新 下面是我在应用程序中使用的最终代码。

- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx 
//If the view already contains a button control remove it
if ([[self.view subviews] containsObject:button]) 
    [button removeFromSuperview];


CGPDFPageRef page = CGPDFDocumentGetPage(pdf, index+1);
CGAffineTransform transform1 = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFMediaBox),
                                         CGContextGetClipBoundingBox(ctx));
CGContextConcatCTM(ctx, transform1);
CGContextDrawPDFPage(ctx, page);


CGPDFPageRef pageAd = CGPDFDocumentGetPage(pdf, index);

CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(pageAd);

CGPDFArrayRef outputArray;
if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)) 
    return;


int arrayCount = CGPDFArrayGetCount( outputArray );
if(!arrayCount) 
    //continue;


for( int j = 0; j < arrayCount; ++j ) 
    CGPDFObjectRef aDictObj;
    if(!CGPDFArrayGetObject(outputArray, j, &aDictObj)) 
        return;
    

    CGPDFDictionaryRef annotDict;
    if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) 
        return;
    

    CGPDFDictionaryRef aDict;
    if(!CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) 
        return;
    

    CGPDFStringRef uriStringRef;
    if(!CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) 
        return;
    

    CGPDFArrayRef rectArray;
    if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) 
        return;
    

    int arrayCount = CGPDFArrayGetCount( rectArray );
    CGPDFReal coords[4];
    for( int k = 0; k < arrayCount; ++k ) 
        CGPDFObjectRef rectObj;
        if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) 
            return;
        

        CGPDFReal coord;
        if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) 
            return;
        

        coords[k] = coord;
                   

    char *uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);

    NSString *uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
    CGRect rect = CGRectMake(coords[0],coords[1],coords[2],coords[3]);
    CGPDFInteger pageRotate = 0;
    CGPDFDictionaryGetInteger( pageDictionary, "Rotate", &pageRotate ); 
    CGRect pageRect = CGRectIntegral( CGPDFPageGetBoxRect( page, kCGPDFMediaBox ));
    if( pageRotate == 90 || pageRotate == 270 ) 
        CGFloat temp = pageRect.size.width;
        pageRect.size.width = pageRect.size.height;
        pageRect.size.height = temp;
    

    rect.size.width -= rect.origin.x;
    rect.size.height -= rect.origin.y;

    CGAffineTransform trans = CGAffineTransformIdentity;
    trans = CGAffineTransformTranslate(trans, 35, pageRect.size.height+150);
    trans = CGAffineTransformScale(trans, 1.15, -1.15);

    rect = CGRectApplyAffineTransform(rect, trans);

    urlLink = [NSURL URLWithString:uri];
    [urlLink retain];

    //Create a button to get link actions
    button = [[UIButton alloc] initWithFrame:rect];
    [button setBackgroundImage:[UIImage imageNamed:@"link_bg.png"] forState:UIControlStateHighlighted];
    [button addTarget:self action:@selector(openLink:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
   
[leavesView reloadData];



【讨论】:

@user470763:是的,添加一个按钮是最明显的解决方案:) @Brainfeeder 我现在真正遇到的唯一问题是矩形大小仅适用于 iPhone 而不是 iPad。此外,在整页链接上,我无法滑动以更改页面。 @kmcg :感谢您的代码,我也可以在 ipad 中缩放矩形大小,您唯一需要做的就是更改 x 和 y 的值,也许对您有帮助。还想问您是否能够从 pdf 文件中找到除 URL 之外的任何单词。谢谢。 请注意,由那段代码创建的按钮是用白色字体清晰的。因此,如果您的 pdf 不是彩色的,那么您将看不到它。我无法将矩形放在正确的位置 @lindon 我已经用我的最终代码更新了我的答案。我 90% 确定这适用于 iPhone 和 iPad,但我现在没有时间进行测试。我已经有大约 6 个月没有从事这个项目了,所以我不记得了。希望它可以帮助你。当我完成时,一切正常。【参考方案3】:

我一定很困惑,因为如果我使用这一切都有效:

CGRect rect = CGRectMake(coords[0],coords[1],coords[2]-coords[0]+1,coords[3]-coords[1]+1);

也许我以后会误用某些东西吗? PDF 提供角,而 CGRect 需要角和大小。

【讨论】:

以上是关于使用 Quartz 在 iOS 上获取 PDF 超链接的主要内容,如果未能解决你的问题,请参考以下文章

Quartz-2D

使用 Quartz 2D 解析 pdf 时获取文本位置

如何获取用户触摸的 PDF 对象(字符串、图像等)?

渲染 PDF iOS-iPad- Quartz 的问题

iOS开发UI篇—Quartz2D使用(绘制基本图形)

PDF Quartz 渲染质量