使用 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 的基本思想。之后,您应该能够在 Adobe 的 PDF 规范的帮助下弄清楚它。
1.) 获取 CGPDFDocumentRef。
2.) 获取每一页。
3.) 在每一页上,使用CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)
,其中 pageDictionary 是表示 CGPDFPage 的 CGPDFDictionary,而 outputArray 是用于存储该页面的 Annots 数组的变量 (CGPDFArrayRef)。
【讨论】:
@Jesse Naugher:非常感谢您的回答,但是:“在那之后,您应该能够在 Adobe 的 PDF 规范的帮助下解决这个问题”我找不到任何有用的信息那个臃肿的烂摊子就是 Adobe 的 PDF 规范。其中“注释”一词出现的唯一部分是第 8 节,但同样,我在这里看不到任何可以帮助我的信息... frustration 有一个完整的部分介绍了 pdf 文档中可能出现的各种注释,包括链接注释。基本上,当您获得 Annotations Array 时,您会遍历它,每个条目都是一个字典,is 是一个注解。这些字典有一个名为“子类型”的键,用于确定注释的类型,“链接”就是其中之一,并在 pdf 规范中定义。 @Jesse Naugher:太棒了,我刚刚意识到我盯着错误的文档——现在我有了真正的 PDF 规范文档。我现在就去看看,谢谢(是的,当你累/沮丧时会发生这种情况)。 @Jesse Naugher:CGPDFDictionaryGetArray(pageDictionary, "Annots", &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 超链接的主要内容,如果未能解决你的问题,请参考以下文章