Permalink
Switch branches/tags
Nothing to show
Find file
51739e2 Mar 24, 2015
594 lines (454 sloc) 17.1 KB
//
// ReaderContentPage.m
// Reader v2.8.6
//
// Created by Julius Oklamcak on 2011-07-01.
// Copyright © 2011-2015 Julius Oklamcak. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to
// do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "ReaderConstants.h"
#import "ReaderContentPage.h"
#import "ReaderContentTile.h"
#import "CGPDFDocument.h"
@implementation ReaderContentPage
{
NSMutableArray *_links;
CGPDFDocumentRef _PDFDocRef;
CGPDFPageRef _PDFPageRef;
NSInteger _pageAngle;
CGFloat _pageWidth;
CGFloat _pageHeight;
CGFloat _pageOffsetX;
CGFloat _pageOffsetY;
}
#pragma mark - ReaderContentPage class methods
+ (Class)layerClass
{
return [ReaderContentTile class];
}
#pragma mark - ReaderContentPage PDF link methods
- (void)highlightPageLinks
{
if (_links.count > 0) // Add highlight views over all links
{
UIColor *hilite = [UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:0.15f];
for (ReaderDocumentLink *link in _links) // Enumerate the links array
{
UIView *highlight = [[UIView alloc] initWithFrame:link.rect];
highlight.autoresizesSubviews = NO;
highlight.userInteractionEnabled = NO;
highlight.contentMode = UIViewContentModeRedraw;
highlight.autoresizingMask = UIViewAutoresizingNone;
highlight.backgroundColor = hilite; // Color
[self addSubview:highlight];
}
}
}
- (ReaderDocumentLink *)linkFromAnnotation:(CGPDFDictionaryRef)annotationDictionary
{
ReaderDocumentLink *documentLink = nil; // Document link object
CGPDFArrayRef annotationRectArray = NULL; // Annotation co-ordinates array
if (CGPDFDictionaryGetArray(annotationDictionary, "Rect", &annotationRectArray))
{
CGPDFReal ll_x = 0.0f; CGPDFReal ll_y = 0.0f; // PDFRect lower-left X and Y
CGPDFReal ur_x = 0.0f; CGPDFReal ur_y = 0.0f; // PDFRect upper-right X and Y
CGPDFArrayGetNumber(annotationRectArray, 0, &ll_x); // Lower-left X co-ordinate
CGPDFArrayGetNumber(annotationRectArray, 1, &ll_y); // Lower-left Y co-ordinate
CGPDFArrayGetNumber(annotationRectArray, 2, &ur_x); // Upper-right X co-ordinate
CGPDFArrayGetNumber(annotationRectArray, 3, &ur_y); // Upper-right Y co-ordinate
if (ll_x > ur_x) { CGPDFReal t = ll_x; ll_x = ur_x; ur_x = t; } // Normalize Xs
if (ll_y > ur_y) { CGPDFReal t = ll_y; ll_y = ur_y; ur_y = t; } // Normalize Ys
ll_x -= _pageOffsetX; ll_y -= _pageOffsetY; // Offset lower-left co-ordinate
ur_x -= _pageOffsetX; ur_y -= _pageOffsetY; // Offset upper-right co-ordinate
switch (_pageAngle) // Page rotation angle (in degrees)
{
case 90: // 90 degree page rotation
{
CGPDFReal swap;
swap = ll_y; ll_y = ll_x; ll_x = swap;
swap = ur_y; ur_y = ur_x; ur_x = swap;
break;
}
case 270: // 270 degree page rotation
{
CGPDFReal swap;
swap = ll_y; ll_y = ll_x; ll_x = swap;
swap = ur_y; ur_y = ur_x; ur_x = swap;
ll_x = ((0.0f - ll_x) + _pageWidth);
ur_x = ((0.0f - ur_x) + _pageWidth);
break;
}
case 0: // 0 degree page rotation
{
ll_y = ((0.0f - ll_y) + _pageHeight);
ur_y = ((0.0f - ur_y) + _pageHeight);
break;
}
}
NSInteger vr_x = ll_x; NSInteger vr_w = (ur_x - ll_x); // Integer X and width
NSInteger vr_y = ll_y; NSInteger vr_h = (ur_y - ll_y); // Integer Y and height
CGRect viewRect = CGRectMake(vr_x, vr_y, vr_w, vr_h); // View CGRect from PDFRect
documentLink = [ReaderDocumentLink newWithRect:viewRect dictionary:annotationDictionary];
}
return documentLink;
}
- (void)buildAnnotationLinksList
{
_links = [NSMutableArray new]; // Links list array
CGPDFArrayRef pageAnnotations = NULL; // Page annotations array
CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(_PDFPageRef);
if (CGPDFDictionaryGetArray(pageDictionary, "Annots", &pageAnnotations) == true)
{
NSInteger count = CGPDFArrayGetCount(pageAnnotations); // Number of annotations
for (NSInteger index = 0; index < count; index++) // Iterate through all annotations
{
CGPDFDictionaryRef annotationDictionary = NULL; // PDF annotation dictionary
if (CGPDFArrayGetDictionary(pageAnnotations, index, &annotationDictionary) == true)
{
const char *annotationSubtype = NULL; // PDF annotation subtype string
if (CGPDFDictionaryGetName(annotationDictionary, "Subtype", &annotationSubtype) == true)
{
if (strcmp(annotationSubtype, "Link") == 0) // Found annotation subtype of 'Link'
{
ReaderDocumentLink *documentLink = [self linkFromAnnotation:annotationDictionary];
if (documentLink != nil) [_links insertObject:documentLink atIndex:0]; // Add link
}
}
}
}
//[self highlightPageLinks]; // Link support debugging
}
}
- (CGPDFArrayRef)destinationWithName:(const char *)destinationName inDestsTree:(CGPDFDictionaryRef)node
{
CGPDFArrayRef destinationArray = NULL;
CGPDFArrayRef limitsArray = NULL; // Limits array
if (CGPDFDictionaryGetArray(node, "Limits", &limitsArray) == true)
{
CGPDFStringRef lowerLimit = NULL; CGPDFStringRef upperLimit = NULL;
if (CGPDFArrayGetString(limitsArray, 0, &lowerLimit) == true) // Lower limit
{
if (CGPDFArrayGetString(limitsArray, 1, &upperLimit) == true) // Upper limit
{
const char *ll = (const char *)CGPDFStringGetBytePtr(lowerLimit); // Lower string
const char *ul = (const char *)CGPDFStringGetBytePtr(upperLimit); // Upper string
if ((strcmp(destinationName, ll) < 0) || (strcmp(destinationName, ul) > 0))
{
return NULL; // Destination name is outside this node's limits
}
}
}
}
CGPDFArrayRef namesArray = NULL; // Names array
if (CGPDFDictionaryGetArray(node, "Names", &namesArray) == true)
{
NSInteger namesCount = CGPDFArrayGetCount(namesArray);
for (NSInteger index = 0; index < namesCount; index += 2)
{
CGPDFStringRef destName; // Destination name string
if (CGPDFArrayGetString(namesArray, index, &destName) == true)
{
const char *dn = (const char *)CGPDFStringGetBytePtr(destName);
if (strcmp(dn, destinationName) == 0) // Found the destination name
{
if (CGPDFArrayGetArray(namesArray, (index + 1), &destinationArray) == false)
{
CGPDFDictionaryRef destinationDictionary = NULL; // Destination dictionary
if (CGPDFArrayGetDictionary(namesArray, (index + 1), &destinationDictionary) == true)
{
CGPDFDictionaryGetArray(destinationDictionary, "D", &destinationArray);
}
}
return destinationArray; // Return the destination array
}
}
}
}
CGPDFArrayRef kidsArray = NULL; // Kids array
if (CGPDFDictionaryGetArray(node, "Kids", &kidsArray) == true)
{
NSInteger kidsCount = CGPDFArrayGetCount(kidsArray);
for (NSInteger index = 0; index < kidsCount; index++)
{
CGPDFDictionaryRef kidNode = NULL; // Kid node dictionary
if (CGPDFArrayGetDictionary(kidsArray, index, &kidNode) == true) // Recurse into node
{
destinationArray = [self destinationWithName:destinationName inDestsTree:kidNode];
if (destinationArray != NULL) return destinationArray; // Return destination array
}
}
}
return NULL;
}
- (id)annotationLinkTarget:(CGPDFDictionaryRef)annotationDictionary
{
id linkTarget = nil; // Link target object
CGPDFStringRef destName = NULL; const char *destString = NULL;
CGPDFDictionaryRef actionDictionary = NULL; CGPDFArrayRef destArray = NULL;
if (CGPDFDictionaryGetDictionary(annotationDictionary, "A", &actionDictionary) == true)
{
const char *actionType = NULL; // Annotation action type string
if (CGPDFDictionaryGetName(actionDictionary, "S", &actionType) == true)
{
if (strcmp(actionType, "GoTo") == 0) // GoTo action type
{
if (CGPDFDictionaryGetArray(actionDictionary, "D", &destArray) == false)
{
CGPDFDictionaryGetString(actionDictionary, "D", &destName);
}
}
else // Handle other link action type possibility
{
if (strcmp(actionType, "URI") == 0) // URI action type
{
CGPDFStringRef uriString = NULL; // Action's URI string
if (CGPDFDictionaryGetString(actionDictionary, "URI", &uriString) == true)
{
const char *uri = (const char *)CGPDFStringGetBytePtr(uriString); // Destination URI string
NSString *target = [NSString stringWithCString:uri encoding:NSUTF8StringEncoding]; // NSString - UTF8
linkTarget = [NSURL URLWithString:[target stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if (linkTarget == nil) NSLog(@"%s Bad URI '%@'", __FUNCTION__, target);
}
}
}
}
}
else // Handle other link target possibilities
{
if (CGPDFDictionaryGetArray(annotationDictionary, "Dest", &destArray) == false)
{
if (CGPDFDictionaryGetString(annotationDictionary, "Dest", &destName) == false)
{
CGPDFDictionaryGetName(annotationDictionary, "Dest", &destString);
}
}
}
if (destName != NULL) // Handle a destination name
{
CGPDFDictionaryRef catalogDictionary = CGPDFDocumentGetCatalog(_PDFDocRef);
CGPDFDictionaryRef namesDictionary = NULL; // Destination names in the document
if (CGPDFDictionaryGetDictionary(catalogDictionary, "Names", &namesDictionary) == true)
{
CGPDFDictionaryRef destsDictionary = NULL; // Document destinations dictionary
if (CGPDFDictionaryGetDictionary(namesDictionary, "Dests", &destsDictionary) == true)
{
const char *destinationName = (const char *)CGPDFStringGetBytePtr(destName); // Name
destArray = [self destinationWithName:destinationName inDestsTree:destsDictionary];
}
}
}
if (destString != NULL) // Handle a destination string
{
CGPDFDictionaryRef catalogDictionary = CGPDFDocumentGetCatalog(_PDFDocRef);
CGPDFDictionaryRef destsDictionary = NULL; // Document destinations dictionary
if (CGPDFDictionaryGetDictionary(catalogDictionary, "Dests", &destsDictionary) == true)
{
CGPDFDictionaryRef targetDictionary = NULL; // Destination target dictionary
if (CGPDFDictionaryGetDictionary(destsDictionary, destString, &targetDictionary) == true)
{
CGPDFDictionaryGetArray(targetDictionary, "D", &destArray);
}
}
}
if (destArray != NULL) // Handle a destination array
{
NSInteger targetPageNumber = 0; // The target page number
CGPDFDictionaryRef pageDictionaryFromDestArray = NULL; // Target reference
if (CGPDFArrayGetDictionary(destArray, 0, &pageDictionaryFromDestArray) == true)
{
NSInteger pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocRef); // Pages
for (NSInteger pageNumber = 1; pageNumber <= pageCount; pageNumber++)
{
CGPDFPageRef pageRef = CGPDFDocumentGetPage(_PDFDocRef, pageNumber);
CGPDFDictionaryRef pageDictionaryFromPage = CGPDFPageGetDictionary(pageRef);
if (pageDictionaryFromPage == pageDictionaryFromDestArray) // Found it
{
targetPageNumber = pageNumber; break;
}
}
}
else // Try page number from array possibility
{
CGPDFInteger pageNumber = 0; // Page number in array
if (CGPDFArrayGetInteger(destArray, 0, &pageNumber) == true)
{
targetPageNumber = (pageNumber + 1); // 1-based
}
}
if (targetPageNumber > 0) // We have a target page number
{
linkTarget = [NSNumber numberWithInteger:targetPageNumber];
}
}
return linkTarget;
}
- (id)processSingleTap:(UITapGestureRecognizer *)recognizer
{
id result = nil; // Tap result object
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
if (_links.count > 0) // Process the single tap
{
CGPoint point = [recognizer locationInView:self];
for (ReaderDocumentLink *link in _links) // Enumerate links
{
if (CGRectContainsPoint(link.rect, point) == true) // Found it
{
result = [self annotationLinkTarget:link.dictionary]; break;
}
}
}
}
return result;
}
#pragma mark - ReaderContentPage instance methods
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
self.autoresizesSubviews = NO;
self.userInteractionEnabled = NO;
self.contentMode = UIViewContentModeRedraw;
self.autoresizingMask = UIViewAutoresizingNone;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (instancetype)initWithURL:(NSURL *)fileURL page:(NSInteger)page password:(NSString *)phrase
{
CGRect viewRect = CGRectZero; // View rect
if (fileURL != nil) // Check for non-nil file URL
{
_PDFDocRef = CGPDFDocumentCreateUsingUrl((__bridge CFURLRef)fileURL, phrase);
if (_PDFDocRef != NULL) // Check for non-NULL CGPDFDocumentRef
{
if (page < 1) page = 1; // Check the lower page bounds
NSInteger pages = CGPDFDocumentGetNumberOfPages(_PDFDocRef);
if (page > pages) page = pages; // Check the upper page bounds
_PDFPageRef = CGPDFDocumentGetPage(_PDFDocRef, page); // Get page
if (_PDFPageRef != NULL) // Check for non-NULL CGPDFPageRef
{
CGPDFPageRetain(_PDFPageRef); // Retain the PDF page
CGRect cropBoxRect = CGPDFPageGetBoxRect(_PDFPageRef, kCGPDFCropBox);
CGRect mediaBoxRect = CGPDFPageGetBoxRect(_PDFPageRef, kCGPDFMediaBox);
CGRect effectiveRect = CGRectIntersection(cropBoxRect, mediaBoxRect);
_pageAngle = CGPDFPageGetRotationAngle(_PDFPageRef); // Angle
switch (_pageAngle) // Page rotation angle (in degrees)
{
default: // Default case
case 0: case 180: // 0 and 180 degrees
{
_pageWidth = effectiveRect.size.width;
_pageHeight = effectiveRect.size.height;
_pageOffsetX = effectiveRect.origin.x;
_pageOffsetY = effectiveRect.origin.y;
break;
}
case 90: case 270: // 90 and 270 degrees
{
_pageWidth = effectiveRect.size.height;
_pageHeight = effectiveRect.size.width;
_pageOffsetX = effectiveRect.origin.y;
_pageOffsetY = effectiveRect.origin.x;
break;
}
}
NSInteger page_w = _pageWidth; // Integer width
NSInteger page_h = _pageHeight; // Integer height
if (page_w % 2) page_w--; if (page_h % 2) page_h--; // Even
viewRect.size = CGSizeMake(page_w, page_h); // View size
}
else // Error out with a diagnostic
{
CGPDFDocumentRelease(_PDFDocRef), _PDFDocRef = NULL;
NSAssert(NO, @"CGPDFPageRef == NULL");
}
}
else // Error out with a diagnostic
{
NSAssert(NO, @"CGPDFDocumentRef == NULL");
}
}
else // Error out with a diagnostic
{
NSAssert(NO, @"fileURL == nil");
}
ReaderContentPage *view = [self initWithFrame:viewRect];
if (view != nil) [self buildAnnotationLinksList];
return view;
}
- (void)removeFromSuperview
{
self.layer.delegate = nil;
//self.layer.contents = nil;
[super removeFromSuperview];
}
- (void)dealloc
{
CGPDFPageRelease(_PDFPageRef), _PDFPageRef = NULL;
CGPDFDocumentRelease(_PDFDocRef), _PDFDocRef = NULL;
}
#if (READER_DISABLE_RETINA == TRUE) // Option
- (void)didMoveToWindow
{
self.contentScaleFactor = 1.0f; // Override scale factor
}
#endif // end of READER_DISABLE_RETINA Option
#pragma mark - CATiledLayer delegate methods
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)context
{
ReaderContentPage *readerContentPage = self; // Retain self
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // White
CGContextFillRect(context, CGContextGetClipBoundingBox(context)); // Fill
//NSLog(@"%s %@", __FUNCTION__, NSStringFromCGRect(CGContextGetClipBoundingBox(context)));
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height); CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(_PDFPageRef, kCGPDFCropBox, self.bounds, 0, true));
//CGContextSetRenderingIntent(context, kCGRenderingIntentDefault); CGContextSetInterpolationQuality(context, kCGInterpolationDefault);
CGContextDrawPDFPage(context, _PDFPageRef); // Render the PDF page into the context
if (readerContentPage != nil) readerContentPage = nil; // Release self
}
@end
#pragma mark -
//
// ReaderDocumentLink class implementation
//
@implementation ReaderDocumentLink
{
CGPDFDictionaryRef _dictionary;
CGRect _rect;
}
#pragma mark - Properties
@synthesize rect = _rect;
@synthesize dictionary = _dictionary;
#pragma mark - ReaderDocumentLink class methods
+ (instancetype)newWithRect:(CGRect)linkRect dictionary:(CGPDFDictionaryRef)linkDictionary
{
return [[ReaderDocumentLink alloc] initWithRect:linkRect dictionary:linkDictionary];
}
#pragma mark - ReaderDocumentLink instance methods
- (instancetype)initWithRect:(CGRect)linkRect dictionary:(CGPDFDictionaryRef)linkDictionary
{
if ((self = [super init]))
{
_dictionary = linkDictionary;
_rect = linkRect;
}
return self;
}
@end