Obtenga hipervínculos PDF en iOS con Quartz

Pasé todo el día intentando obtener metadatos de hipervínculos de archivos PDF en mi aplicación para iPad. Las API CGPDF * son una verdadera pesadilla, y la única información que he encontrado en la red sobre todo esto es que tengo que buscar un diccionario “Annots”, pero no puedo encontrarlo en mis archivos PDF.

Incluso utilicé la muestra anterior de Voyeur Xcode para inspeccionar mi archivo PDF de prueba, pero no hay rastros de este diccionario “Annots” …

Ya sabes, esta es una característica que veo en cada lector de PDF: esta misma pregunta se ha hecho varias veces aquí sin respuestas prácticas reales. Por lo general, nunca solicito el código de muestra directamente, pero aparentemente esta vez realmente lo necesito … ¿Alguien consiguió esto funcionando, posiblemente con código de muestra?

Actualización : Me acabo de dar cuenta de que el tipo que ha hecho mi prueba PDF acaba de insertar una URL como texto, y no una anotación real. Trató de poner una anotación y mi código funciona ahora … Pero eso no es lo que necesito, así que parece que tendré que analizar el texto y buscar las URL. Pero esa es otra historia …

Actualización 2 : Así que finalmente se me ocurrió un código de trabajo. Lo estoy publicando aquí, así que espero que ayude a alguien. Supone que el documento PDF en realidad contiene anotaciones.

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. // eg you could create a button and put it on top of your page // and use it to open the URL with UIApplication's openURL } } 

Aquí está la idea básica para llegar a las anotaciones CGPDFDiccionario para cada página al menos. después de eso deberías ser capaz de resolverlo con ayuda de las especificaciones PDF de Adobe.

1.) obtener el CGPDFDocumentRef.

2.) obtener cada página.

3.) en cada página, use CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray) donde pageDictionary es el CGPDFDictionary que representa el CGPDFPage, y outputArray es la variable (CGPDFArrayRef) en la que se almacena el conjunto de Annots de esa página.

Gran código, pero estoy teniendo un pequeño problema para trabajar en mi proyecto. Obtiene todas las URL correctamente, pero cuando hago clic en él no sucede nada. Aquí está mi código. Tuve que modificar ligeramente el suyo para trabajar con mi proyecto). ¿Hay algo que falta?

 - (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 

Gracias y buen trabajo BrainFeeder!

ACTUALIZAR:

Para cualquiera que use el proyecto de hojas en su aplicación, así es como conseguí que los enlaces PDF funcionaran (no es perfecto, ya que el rect parece llenar toda la pantalla, pero es un comienzo):

 - (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. // eg 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); } //} 

Actualización final A continuación se muestra el código final que utilicé en mis aplicaciones.

 - (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]; } } 

Debo confundirme, porque todo esto funciona si uso:

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

¿Estoy mal uso de algo más tarde, tal vez? PDF suministra las esquinas, y CGRect quiere una esquina y un tamaño.