Ottieni collegamenti ipertestuali PDF su iOS con Quartz

Ho passato tutto il giorno a cercare di ottenere i metadati dei collegamenti ipertestuali dai PDF nella mia applicazione iPad. Le API di CGPDF * sono un vero incubo e l’unica informazione che ho trovato in rete è che devo cercare un dizionario “Annot”, ma non riesco a trovarlo nei miei PDF.

Ho anche usato il vecchio campione Xcode Voyeur per ispezionare il mio file PDF di prova, ma nessuna traccia di questo dizionario “Annot” …

Sai, questa è una caratteristica che vedo su ogni lettore di PDF – questa stessa domanda è stata posta più volte qui senza reali risposte pratiche. Di solito non chiedo direttamente codice di esempio, ma a quanto pare questa volta ne ho davvero bisogno … qualcuno ha funzionato, possibilmente con un codice di esempio?

Aggiornamento : ho appena realizzato che il ragazzo che ha eseguito il mio test di PDF aveva appena inserito un URL come testo e non una vera annotazione. Ha provato a mettere un’annotazione e il mio codice funziona ora … Ma non è quello che mi serve, quindi sembra che dovrò analizzare il testo e cercare gli URL. Ma questa è un’altra storia …

Aggiornamento 2 : Quindi alla fine ho trovato un codice funzionante. Sto postando qui, quindi spero che possa aiutare qualcuno. Presume che il documento PDF contenga effettivamente annotazioni.

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

ecco l’idea di base per arrivare agli annoti CGPDFDictionary per ogni pagina almeno. dopo di ciò dovresti essere in grado di capirlo con l’aiuto delle specifiche PDF di Adobe.

1.) ottieni CGPDFDocumentRef.

2.) ottieni ogni pagina.

3.) in ogni pagina, utilizzare CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray) dove pageDictionary è il CGPDFDictionary che rappresenta la CGPDFPage e outputArray è la variabile (CGPDFArrayRef) per memorizzare l’array Annots di tale pagina.

Grande codice ma sto avendo qualche problema a lavorarlo nel mio progetto. Ottiene correttamente tutti gli URL ma quando faccio clic su di esso non succede nulla. Ecco il mio codice che ho dovuto modificare leggermente il tuo per lavorare con il mio progetto). C’è qualcosa che manca:

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

Grazie e ottimo lavoro BrainFeeder!

AGGIORNARE:

Per chiunque utilizzi il progetto "foglie" nella tua app questo è il modo in cui ho ottenuto il funzionamento dei collegamenti PDF (non è perfetto dato che il rect sembra riempire l'intero schermo ma è un inizio):

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

Aggiornamento finale Di seguito è riportato il codice finale che ho utilizzato nelle mie app.

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

Devo essere confuso, perché tutto funziona se uso:

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

Sto abusando di qualcosa più tardi, forse? PDF fornisce gli angoli e CGRect vuole un angolo e una dimensione.