Получить PDF гиперссылки на iOS с помощью Quartz
Я провел весь день, пытаясь получить метаданные гиперссылок из PDF-файлов в своем приложении для iPad. API CGPDF* - настоящий кошмар, и единственная информация, которую я нашел в сети обо всем этом, - это то, что мне нужно искать словарь "Аннотации", но я просто не могу найти его в своих PDF-файлах.
Я даже использовал старый пример Voyeur Xcode для проверки моего тестового PDF-файла, но не нашел следов этого словаря "Annots"...
Вы знаете, это особенность, которую я вижу в каждом приложении для чтения PDF - этот же вопрос задавался здесь несколько раз без реальных практических ответов. Обычно я никогда не запрашиваю пример кода напрямую, но, по-видимому, на этот раз он мне действительно нужен... кто-нибудь получил эту работу, возможно, с примером кода?
Обновление: я только что понял, что парень, который сделал мое тестирование 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
}
}
3 ответа
Вот основная идея - получить аннотации CGPDFDictionary для каждой страницы по крайней мере. после этого вы сможете понять это с помощью спецификации PDF от Adobe.
1.) получить CGPDFDocumentRef.
2.) получить каждую страницу.
3.) на каждой странице используйте CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray)
где pageDictionary - это CGPDFDictionary, представляющий CGPDFPage, а outputArray - переменная (CGPDFArrayRef) для хранения массива Annots этой страницы.
Отличный код, но у меня возникли небольшие проблемы при его внедрении в мой проект. Он получает все 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-ссылки (это не идеально, так как прямоугольник, кажется, заполняет весь экран, но это начало):
- (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];
}
}
Я должен быть смущен, потому что это все работает, если я использую:
CGRect rect = CGRectMake(coords[0],coords[1],coords[2]-coords[0]+1,coords[3]-coords[1]+1);
Возможно, я что-то неправильно использую позже? PDF предоставляет углы, а CGRect хочет угол и размер.