Получите гиперссылки PDF на iOS с кварцем

Я весь день пытался получить метаданные гиперссылок из PDF файлов в своем приложении iPad. API CGPDF * - настоящий кошмар, и единственная часть информации, которую я нашел в сети, - это то, что мне нужно искать словарь "Annots", но я просто не могу найти его в своих 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 openURL
    }
}

Ответ 1

heres основная идея, чтобы добраться до аннотов CGPDFDictionary для каждой страницы по крайней мере. после этого вы сможете понять это с помощью спецификации PDF от Adobe.

1.) получить CGPDFDocumentRef.

2.) получить каждую страницу.

3.) на каждой странице используйте CGPDFDictionaryGetArray(pageDictionary, "Annots", &outputArray), где pageDictionary - это CGPDFDictionary, представляющий CGPDFPage, а outputArray - это переменная (CGPDFArrayRef) для хранения массива Annots этой страницы.

Ответ 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 openURL
        NSURL *url = [NSURL URLWithString:uri];
        NSLog(@"URL: %@", url);
        CGPDFContextSetURLForRect(ctx, (CFURLRef)url, rect);
       // CFRelease(url);
        }
    }   


}

Спасибо и отличная работа BrainFeeder!

UPDATE:

Для тех, кто использует проект "Листья" в вашем приложении, это то, как я получил ссылки 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 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];
}

}

Ответ 3

Я должен быть смущен, потому что все это работает, если я использую:

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

Я что-то неправильно употребляю позже? PDF поставляет углы, а CGRect хочет угол и размер.