2010-11-02 20 views
54

Pasé todo el día intentando obtener metadatos de hipervínculos de archivos PDF en mi aplicación de 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 .Obtenga hipervínculos PDF en iOS con Quartz

Incluso he utilizado la vieja Voyeur Xcode sample para inspeccionar mi archivo PDF de prueba, pero no hay rastro de este diccionario "Anot" ...

Usted sabe, esta es una característica que veo en cada lector PDF - esta misma pregunta tiene beenaskedmultipletimes aquí sin respuestas 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. 
     // 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 
    } 
} 
+0

línea 6, ¿no debería ser 'continue' en lugar de' return'? - ¿Por qué regresas después de verificar objeto, valor, dict, cadena, matriz, etc. –

+0

Es solo un código de ejemplo sin ninguna comprobación de errores. – pt2ph8

+0

PDF rects no traducir a rects nativos ver mi hilo para más detalles: desplazarse hacia abajo hasta: 'Otras características PDF', 'Obtener enlaces dentro de un PDF', 'Entender el PDF Rect para posicionamiento de enlace' http://stackoverflow.com/questions/3889634/fast-and-lean-pdf-viewer-for-iphone-ipad-ios-tips-and-hints –

Respuesta

14

heres la idea básica para llegar a las anotaciones CGPDFDictionary 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, utilice CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray) donde pageDictionary es la representación de la CGPDFDictionary CGPDFPage y outputArray es la variable (CGPDFArrayRef) para almacenar la matriz Anot de esa página en.

+0

@Jesse Naugher: Muchas gracias por su respuesta, pero: "después de eso, debería ser capaz de resolverlo con la ayuda de la especificación PDF de Adobe" No pude encontrar ninguna información útil de ese desastre hinchado que es el de Adobe PDF espec. La única parte de ella donde aparece la palabra "anotación" es la sección 8, pero de nuevo, no puedo ver ninguna información que pueda ayudarme aquí ... * frustración * – pt2ph8

+1

hay una sección completa sobre cada tipo de anotación que puede ser en un documento pdf, incluida la anotación del enlace. Básicamente, cuando obtiene la matriz de anotaciones, la recorre y cada entrada es un diccionario que * es * una anotación. Estos diccionarios tienen una clave llamada 'Subtipo' que determina el tipo de anotación que es, y "Enlace" es uno de ellos, y se define en la especificación pdf. –

+0

@Jesse Naugher: Increíble, me acabo de dar cuenta de que estaba mirando el documento equivocado, ahora tengo el documento ** real ** de especificación PDF. Voy a echarle un vistazo ahora, gracias (sí, eso es lo que sucede cuando estás cansado/frustrado). – pt2ph8

9

Gran código, pero estoy teniendo un pequeño problema al trabajarlo 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<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); 
     } 
    } 


} 

Gracias & gran Brainfeeder trabajo!

ACTUALIZACIÓN:

Para cualquiera usando el proyecto hojas en su aplicación así es como llegué los enlaces PDF a trabajar (no es perfecto como 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. 
      // 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); 
     } 
    //} 

Informe final de actualización 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]; 
} 

} 
+0

@ user470763: Sí, agregar un botón es la solución más obvia :) – pt2ph8

+0

@Brainfeeder El único problema que estoy teniendo ahora es que el tamaño rect solo se adapta para iPhone y no para iPad. Además, en los enlaces de página completa, no puedo deslizar para cambiar de página. –

+0

@kmcg: Gracias por su código, también puedo escalar los tamaños rect en el ipad, lo único que necesita es cambiar los valores de xey, es posible que lo ayude. También quería preguntar si puede encontrar alguna palabra del archivo pdf que no sean URLs.Gracias. – Sarah

0

debo estar confundido, porque esto funciona todo si uso:

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

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