Skip to content

Commit

Permalink
Added support for high quality zooming using CATiledLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodrigo Hammerly authored and Rodrigo Hammerly committed Aug 24, 2011
1 parent 67b10ed commit 813e147
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 9 deletions.
61 changes: 60 additions & 1 deletion Classes/PDFExampleViewController.m
Expand Up @@ -12,7 +12,7 @@
@implementation PDFExampleViewController

- (id)init {
if (self = [super init]) {
if ((self = [super init])) {
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("paper.pdf"), NULL, NULL);
pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
CFRelease(pdfURL);
Expand Down Expand Up @@ -52,6 +52,65 @@ - (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx {
CGContextDrawPDFPage(ctx, page);
}

- (void)renderTiledPageAtIndex:(NSUInteger)index forLayer:(LeavesTiledLayer*)layer inContext:(CGContextRef)ctx
{
CGPDFPageRef drawPDFPageRef = NULL;

@synchronized(self) // Block any other threads
{
drawPDFPageRef = CGPDFPageRetain( CGPDFDocumentGetPage(pdf, index + 1) );
}

if (drawPDFPageRef != NULL) // Render the page into the context
{
CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, 1.0f); // White

CGContextFillRect(ctx, CGContextGetClipBoundingBox(ctx));

CGFloat boundsHeight = layer.frame.size.height;

if (CGPDFPageGetRotationAngle(drawPDFPageRef) == 0)
{
CGFloat boundsWidth = layer.frame.size.width; // View width

CGRect cropBoxRect = CGPDFPageGetBoxRect(drawPDFPageRef, kCGPDFCropBox);
CGRect mediaBoxRect = CGPDFPageGetBoxRect(drawPDFPageRef, kCGPDFMediaBox);
CGRect effectiveRect = CGRectIntersection(cropBoxRect, mediaBoxRect);

CGFloat effectiveWidth = effectiveRect.size.width;
CGFloat effectiveHeight = effectiveRect.size.height;

CGFloat widthScale = (boundsWidth / effectiveWidth);
CGFloat heightScale = (boundsHeight / effectiveHeight);

CGFloat scale = (widthScale < heightScale) ? widthScale : heightScale;

CGFloat x_offset = ((boundsWidth - (effectiveWidth * scale)) / 2.0f);
CGFloat y_offset = ((boundsHeight - (effectiveHeight * scale)) / 2.0f);

y_offset = (boundsHeight - y_offset); // Co-ordinate system adjust

CGFloat x_translate = (x_offset - (effectiveRect.origin.x * scale));
CGFloat y_translate = (y_offset + (effectiveRect.origin.y * scale));

CGContextTranslateCTM(ctx, x_translate, y_translate);

CGContextScaleCTM(ctx, scale, -scale); // Mirror Y
}
else // Use CGPDFPageGetDrawingTransform for pages with rotation (AKA kludge)
{
CGContextTranslateCTM(ctx, 0.0f, boundsHeight); CGContextScaleCTM(ctx, 1.0f, -1.0f);

CGContextConcatCTM(ctx, CGPDFPageGetDrawingTransform(drawPDFPageRef, kCGPDFCropBox, layer.bounds, 0, true));
}

CGContextDrawPDFPage(ctx, drawPDFPageRef);
}

CGPDFPageRelease(drawPDFPageRef); // Cleanup

}

#pragma mark UIViewController

- (void) viewDidLoad {
Expand Down
16 changes: 16 additions & 0 deletions Leaves.xcodeproj/project.pbxproj
Expand Up @@ -11,6 +11,8 @@
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; };
5B2D1CA114053BE70076B90C /* LeavesTiledLayerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B2D1CA014053BE70076B90C /* LeavesTiledLayerDelegate.m */; };
5B5B759114052939008EDBE8 /* LeavesTiledLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B5B759014052939008EDBE8 /* LeavesTiledLayer.m */; };
D337E308119B61A5003E5728 /* LeavesCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D337E307119B61A5003E5728 /* LeavesCache.m */; };
D33D45A2117BDE0100BA7203 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D33D45A1117BDE0100BA7203 /* QuartzCore.framework */; };
D33D4A0D117C34F000BA7203 /* ImageExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D33D4A0C117C34F000BA7203 /* ImageExampleViewController.m */; };
Expand All @@ -36,6 +38,10 @@
1D6058910D05DD3D006BFB54 /* Leaves.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Leaves.app; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
5B2D1C9F14053BE70076B90C /* LeavesTiledLayerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeavesTiledLayerDelegate.h; sourceTree = "<group>"; };
5B2D1CA014053BE70076B90C /* LeavesTiledLayerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeavesTiledLayerDelegate.m; sourceTree = "<group>"; };
5B5B758F14052939008EDBE8 /* LeavesTiledLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeavesTiledLayer.h; sourceTree = "<group>"; };
5B5B759014052939008EDBE8 /* LeavesTiledLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeavesTiledLayer.m; sourceTree = "<group>"; };
D337E306119B61A5003E5728 /* LeavesCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeavesCache.h; sourceTree = "<group>"; };
D337E307119B61A5003E5728 /* LeavesCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeavesCache.m; sourceTree = "<group>"; };
D33D45A1117BDE0100BA7203 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -146,6 +152,10 @@
D33D4E58117D818100BA7203 /* LeavesView.m */,
D33D4E59117D818100BA7203 /* LeavesViewController.h */,
D33D4E5A117D818100BA7203 /* LeavesViewController.m */,
5B5B758F14052939008EDBE8 /* LeavesTiledLayer.h */,
5B5B759014052939008EDBE8 /* LeavesTiledLayer.m */,
5B2D1C9F14053BE70076B90C /* LeavesTiledLayerDelegate.h */,
5B2D1CA014053BE70076B90C /* LeavesTiledLayerDelegate.m */,
);
path = Leaves;
sourceTree = "<group>";
Expand Down Expand Up @@ -204,7 +214,11 @@
};
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Leaves" */;
compatibilityVersion = "Xcode 3.1";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
en,
);
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -245,6 +259,8 @@
D33D4F41117E244C00BA7203 /* ExamplesViewController.m in Sources */,
D33D5176117E780300BA7203 /* ProceduralExampleViewController.m in Sources */,
D337E308119B61A5003E5728 /* LeavesCache.m in Sources */,
5B5B759114052939008EDBE8 /* LeavesTiledLayer.m in Sources */,
5B2D1CA114053BE70076B90C /* LeavesTiledLayerDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
1 change: 0 additions & 1 deletion Leaves/LeavesCache.m
Expand Up @@ -101,5 +101,4 @@ - (void) setPageSize:(CGSize)value {
pageSize = value;
[self flush];
}

@end
16 changes: 16 additions & 0 deletions Leaves/LeavesTiledLayer.h
@@ -0,0 +1,16 @@
//
// LeavesTiledLayer.h
// Leaves
//
// Created by Rodrigo Hammerly on 8/24/11.
// Copyright 2011 Tom Brow. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>

@interface LeavesTiledLayer : CATiledLayer {

}

@end
59 changes: 59 additions & 0 deletions Leaves/LeavesTiledLayer.m
@@ -0,0 +1,59 @@
//
// LeavesTiledLayer.m
// Leaves
//
// Created by Rodrigo Hammerly on 8/24/11.
// Copyright 2011 Tom Brow. All rights reserved.
//

#import "LeavesTiledLayer.h"

#define ZOOM_LEVELS 5

@implementation LeavesTiledLayer

+ (CFTimeInterval)fadeDuration
{
return 0.0; // No fading wanted
}

+ (id<CAAction>)defaultActionForKey:(NSString *)event
{
return nil;
}
#pragma mark LeavesTiledLayer instance methods

- (id)init
{
if ((self = [super init]))
{
self.levelsOfDetail = ZOOM_LEVELS;

self.levelsOfDetailBias = (ZOOM_LEVELS - 1);

CGFloat screenScale; // Points to pixels

UIScreen *mainScreen = [UIScreen mainScreen];

if ([mainScreen respondsToSelector:@selector(scale)])
screenScale = [mainScreen scale];
else
screenScale = 1.0f;

CGRect screenBounds = [mainScreen bounds]; // Is in points

CGFloat w_pixels = (screenBounds.size.width * screenScale);
CGFloat h_pixels = (screenBounds.size.height * screenScale);

CGFloat max = (w_pixels < h_pixels) ? h_pixels : w_pixels;

CGFloat sizeOfTiles = (max < 512.0f) ? 512.0f : 1024.0f;

self.tileSize = CGSizeMake(sizeOfTiles, sizeOfTiles);
self.backgroundColor = [UIColor clearColor].CGColor;
}

return self;
}

@end
21 changes: 21 additions & 0 deletions Leaves/LeavesTiledLayerDelegate.h
@@ -0,0 +1,21 @@
//
// LeavesTiledLayerDelegate.h
// Leaves
//
// Created by Rodrigo Hammerly on 8/24/11.
// Copyright 2011 Tom Brow. All rights reserved.
//

#import <Foundation/Foundation.h>
@protocol LeavesViewDataSource;

@interface LeavesTiledLayerDelegate : NSObject {
id<LeavesViewDataSource> dataSource;
NSInteger pageIndex;
}

@property (assign) id<LeavesViewDataSource> dataSource;
@property (assign) NSInteger pageIndex;

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context;
@end
33 changes: 33 additions & 0 deletions Leaves/LeavesTiledLayerDelegate.m
@@ -0,0 +1,33 @@
//
// LeavesTiledLayerDelegate.m
// Leaves
//
// Created by Rodrigo Hammerly on 8/24/11.
// Copyright 2011 Tom Brow. All rights reserved.
//

#import "LeavesTiledLayerDelegate.h"
#import "LeavesView.h"

@implementation LeavesTiledLayerDelegate

@synthesize dataSource;
@synthesize pageIndex;

- (id)init {

if ((self = [super init])) {
self.pageIndex = 0;
}

return self;
}

#pragma mark LeavesViewTiledLayer delegated methods
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {

[self.dataSource renderTiledPageAtIndex:self.pageIndex forLayer:layer inContext:context];

}

@end
13 changes: 12 additions & 1 deletion Leaves/LeavesView.h
Expand Up @@ -10,10 +10,16 @@
#import <QuartzCore/QuartzCore.h>
#import "LeavesCache.h"

@class LeavesTiledLayer;
@class LeavesTiledLayerDelegate;
@protocol LeavesViewDataSource;
@protocol LeavesViewDelegate;

@interface LeavesView : UIView {

LeavesTiledLayer *topPageZoomLayer;
LeavesTiledLayerDelegate *topPageZoomDelegate;

CALayer *topPage;
CALayer *topPageOverlay;
CAGradientLayer *topPageShadow;
Expand All @@ -40,6 +46,8 @@
BOOL touchIsActive;
CGRect nextPageRect, prevPageRect;
BOOL interactionLocked;

BOOL zoomActive;
}

@property (assign) id<LeavesViewDataSource> dataSource;
Expand All @@ -58,6 +66,9 @@
// The default value is NO. Only set this to YES if your implementation of the data source methods is thread-safe.
@property (assign) BOOL backgroundRendering;

// set this value to YES to activate zooming
@property (assign) BOOL zoomActive;

// refreshes the contents of all pages via the data source methods, much like -[UITableView reloadData]
- (void) reloadData;

Expand All @@ -68,7 +79,7 @@

- (NSUInteger) numberOfPagesInLeavesView:(LeavesView*)leavesView;
- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx;

- (void)renderTiledPageAtIndex:(NSUInteger)index forLayer:(LeavesTiledLayer*)layer inContext:(CGContextRef)ctx;
@end


Expand Down

0 comments on commit 813e147

Please sign in to comment.