1/* 2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#if PLATFORM(IOS) 27 28#import "WebPDFViewIOS.h" 29#import "WebDataSourceInternal.h" 30 31#import "WebFrameInternal.h" 32#import "WebJSPDFDoc.h" 33#import "WebKitVersionChecks.h" 34#import "WebPDFDocumentExtras.h" 35#import "WebPDFViewPlaceholder.h" 36#import <JavaScriptCore/JSContextRef.h> 37#import <JavaScriptCore/JSStringRef.h> 38#import <JavaScriptCore/JSStringRefCF.h> 39#import <WebCore/Color.h> 40#import <WebCore/Frame.h> 41#import <WebCore/FrameLoader.h> 42#import <WebCore/FrameLoaderClient.h> 43#import <WebCore/GraphicsContext.h> 44#import <WebCore/StringWithDirection.h> 45#import <WebCore/WKGraphics.h> 46#import <WebKitLegacy/WebFrame.h> 47#import <WebKitLegacy/WebFrameLoadDelegate.h> 48#import <WebKitLegacy/WebFramePrivate.h> 49#import <WebKitLegacy/WebFrameView.h> 50#import <WebKitLegacy/WebNSViewExtras.h> 51#import <WebKitLegacy/WebViewPrivate.h> 52#import <wtf/Assertions.h> 53#import <wtf/RetainPtr.h> 54#import <wtf/StdLibExtras.h> 55 56using namespace WebCore; 57using namespace std; 58 59static int comparePageRects(const void *key, const void *array); 60 61static const float PAGE_WIDTH_INSET = 4.0 * 2.0; 62static const float PAGE_HEIGHT_INSET = 4.0 * 2.0; 63 64@implementation WebPDFView 65 66+ (NSArray *)supportedMIMETypes 67{ 68 return [NSArray arrayWithObjects: 69 @"text/pdf", 70 @"application/pdf", 71 nil]; 72} 73 74+ (CGColorRef)shadowColor 75{ 76 static CGColorRef shadowColor = createCGColorWithDeviceWhite(0, 2.0 / 3); 77 return shadowColor; 78} 79 80+ (CGColorRef)backgroundColor 81{ 82 static CGColorRef backgroundColor = createCGColorWithDeviceWhite(204.0 / 255, 1); 83 return backgroundColor; 84} 85 86// This is a secret protocol for WebDataSource and WebFrameView to offer us the opportunity to do something different 87// depending upon which frame is trying to instantiate a representation for PDF. 88+ (Class)_representationClassForWebFrame:(WebFrame *)webFrame 89{ 90 // If this is not the main frame, use the old WAK PDF view. 91 if ([[webFrame webView] mainFrame] != webFrame) 92 return [WebPDFView class]; 93 94 return [WebPDFViewPlaceholder class]; 95} 96 97- (void)dealloc 98{ 99 if (_PDFDocument != NULL) 100 CGPDFDocumentRelease(_PDFDocument); 101 free(_pageRects); 102 [_title release]; 103 [super dealloc]; 104} 105 106- (void)drawPage:(CGPDFPageRef)aPage 107{ 108 CGContextRef context = WKGetCurrentGraphicsContext(); 109 size_t pageNumber = CGPDFPageGetPageNumber(aPage); 110 CGRect pageRect = _pageRects[pageNumber-1]; 111 112 // Draw page. 113 CGContextSaveGState(context); 114 CGFloat height = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_FLIPPED_SHADOWS) ? 2.0f : -2.0f; 115 CGContextSetShadowWithColor(context, CGSizeMake(0.0f, height), 3.0f, [[self class] shadowColor]); 116 setStrokeAndFillColor(context, cachedCGColor(Color::white, ColorSpaceDeviceRGB)); 117 CGContextFillRect(context, pageRect); 118 CGContextRestoreGState(context); 119 120 CGContextSaveGState(context); 121 122 CGContextTranslateCTM(context, CGRectGetMinX(pageRect), CGRectGetMinY(pageRect)); 123 CGContextScaleCTM(context, 1.0, -1.0); 124 CGContextTranslateCTM(context, 0, -CGRectGetHeight(pageRect)); 125 126 CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(aPage, kCGPDFCropBox, CGRectMake(0.0, 0.0, CGRectGetWidth(pageRect), CGRectGetHeight(pageRect)), 0, true)); 127 128 CGRect cropBox = CGPDFPageGetBoxRect(aPage, kCGPDFCropBox); 129 CGContextClipToRect(context, cropBox); 130 131 CGContextDrawPDFPage(context, aPage); 132 CGContextRestoreGState(context); 133} 134 135- (NSArray *)_pagesInRect:(CGRect)rect 136{ 137 NSMutableArray *pages = [NSMutableArray array]; 138 size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument); 139 CGRect *firstFoundRect = (CGRect *)bsearch(&rect, _pageRects, pageCount, sizeof(CGRect), comparePageRects); 140 if (firstFoundRect != NULL) { 141 size_t firstFoundIndex = firstFoundRect - _pageRects; 142 id page = (id)CGPDFDocumentGetPage(_PDFDocument, firstFoundIndex+1); 143 ASSERT(page); 144 if (!page) 145 return pages; 146 [pages addObject:page]; 147 size_t i; 148 for (i = firstFoundIndex - 1; i < pageCount; i--) { 149 if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) { 150 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1); 151 ASSERT(page); 152 if (page) 153 [pages addObject:page]; 154 } else 155 break; 156 } 157 for (i = firstFoundIndex + 1; i < pageCount; i++) { 158 if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) { 159 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1); 160 ASSERT(page); 161 if (page) 162 [pages addObject:page]; 163 } else 164 break; 165 } 166 } 167 return pages; 168} 169 170- (void)drawRect:(CGRect)aRect 171{ 172 CGContextRef context = WKGetCurrentGraphicsContext(); 173 174 // Draw Background. 175 CGContextSaveGState(context); 176 setStrokeAndFillColor(context, [[self class] backgroundColor]); 177 CGContextFillRect(context, aRect); 178 CGContextRestoreGState(context); 179 180 if (!_PDFDocument) 181 return; 182 183 CGPDFPageRef page = NULL; 184 NSEnumerator * enumerator = [[self _pagesInRect:aRect] objectEnumerator]; 185 186 while ((page = (CGPDFPageRef)[enumerator nextObject])) 187 [self drawPage:page]; 188} 189 190- (void)setDataSource:(WebDataSource *)dataSource 191{ 192 // Since this class is a WebDocumentView and WebDocumentRepresentation, setDataSource: will be called twice. Do work only once. 193 if (dataSourceHasBeenSet) 194 return; 195 196 if (!_title) 197 _title = [[[[dataSource request] URL] lastPathComponent] copy]; 198 199 WAKView * superview = [self superview]; 200 201 // As noted above, -setDataSource: will be called twice -- once for the WebDocumentRepresentation, once for the WebDocumentView. 202 if (!superview) 203 return; 204 205 [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size]; 206 207 dataSourceHasBeenSet = YES; 208} 209 210- (void)dataSourceUpdated:(WebDataSource *)dataSource 211{ 212 213} 214 215- (void)setNeedsLayout:(BOOL)flag 216{ 217 218} 219 220- (void)layout 221{ 222 // <rdar://problem/7790957> Problem with UISplitViewController on iPad when displaying PDF 223 // Since we don't have anything to layout, just repaint the visible tiles. 224 [self setNeedsDisplay:YES]; 225} 226 227- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow 228{ 229 230} 231 232- (void)viewDidMoveToHostWindow 233{ 234 235} 236 237- (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource 238{ 239} 240 241- (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource 242{ 243} 244 245- (void)_computePageRects 246{ 247 size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument); 248 _pageRects = (CGRect *)malloc(sizeof(CGRect) * pageCount); 249 250 CGSize size = CGSizeMake(0.0, PAGE_HEIGHT_INSET); 251 size_t i; 252 for (i = 1; i <= pageCount; i++) { 253 254 CGPDFPageRef page = CGPDFDocumentGetPage(_PDFDocument, i); 255 CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox); 256 CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180); 257 if (rotation != 0) { 258 boxRect = CGRectApplyAffineTransform(boxRect, CGAffineTransformMakeRotation(rotation)); 259 boxRect.size.width = roundf(boxRect.size.width); 260 boxRect.size.height = roundf(boxRect.size.height); 261 } 262 263 _pageRects[i-1] = boxRect; 264 _pageRects[i-1].origin.y = size.height; 265 266 size.height += boxRect.size.height + PAGE_HEIGHT_INSET; 267 size.width = max(size.width, boxRect.size.width); 268 } 269 270 size.width += PAGE_WIDTH_INSET * 2.0; 271 [self setBoundsSize:size]; 272 273 for (i = 0; i < pageCount; i++) 274 _pageRects[i].origin.x = roundf((size.width - _pageRects[i].size.width) / 2); 275} 276 277- (void)_checkPDFTitle 278{ 279 if (!_PDFDocument) 280 return; 281 282 NSString *title = nil; 283 284 CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_PDFDocument); 285 CGPDFStringRef value; 286 if (CGPDFDictionaryGetString(info, "Title", &value)) 287 title = [(NSString *)CGPDFStringCopyTextString(value) autorelease]; 288 289 if ([title length]) { 290 [_title release]; 291 _title = [title copy]; 292 core([self _frame])->loader().client().dispatchDidReceiveTitle(StringWithDirection(title, LTR)); 293 } 294} 295 296- (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource 297{ 298 CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]); 299 300 if (!provider) 301 return; 302 303 _PDFDocument = CGPDFDocumentCreateWithProvider(provider); 304 CGDataProviderRelease(provider); 305 306 if (!_PDFDocument) 307 return; 308 309 [self _checkPDFTitle]; 310 [self _computePageRects]; 311 312 NSArray *scripts = allScriptsInPDFDocument(_PDFDocument); 313 314 NSUInteger scriptCount = [scripts count]; 315 if (!scriptCount) 316 return; 317 318 JSGlobalContextRef ctx = JSGlobalContextCreate(0); 319 JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, dataSource); 320 321 for (NSUInteger i = 0; i < scriptCount; ++i) { 322 JSStringRef script = JSStringCreateWithCFString((CFStringRef)[scripts objectAtIndex:i]); 323 JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0); 324 JSStringRelease(script); 325 } 326 327 JSGlobalContextRelease(ctx); 328 329 [self setNeedsDisplay:YES]; 330} 331 332- (BOOL)canProvideDocumentSource 333{ 334 return NO; 335} 336 337- (NSString *)documentSource 338{ 339 return nil; 340} 341 342- (NSString *)title 343{ 344 return _title; 345} 346 347- (unsigned)pageNumberForRect:(CGRect)rect 348{ 349 size_t bestPageNumber = 0; 350 if (_PDFDocument != NULL && _pageRects != NULL) { 351 NSArray *pages = [self _pagesInRect:rect]; 352 float bestPageArea = 0; 353 size_t count = [pages count]; 354 size_t i; 355 for (i = 0; i < count; i++) { 356 size_t pageNumber = CGPDFPageGetPageNumber((CGPDFPageRef)[pages objectAtIndex:i]); 357 CGRect intersectionRect = CGRectIntersection(_pageRects[pageNumber - 1], rect); 358 float intersectionArea = intersectionRect.size.width * intersectionRect.size.height; 359 if (intersectionArea > bestPageArea) { 360 bestPageArea = intersectionArea; 361 bestPageNumber = pageNumber; 362 } 363 } 364 } 365 return bestPageNumber; 366} 367 368- (unsigned)totalPages 369{ 370 if (_PDFDocument != NULL) 371 return CGPDFDocumentGetNumberOfPages(_PDFDocument); 372 return 0; 373} 374 375- (CGPDFDocumentRef)doc 376{ 377 return _PDFDocument; 378} 379 380- (CGRect)rectForPageNumber:(unsigned)pageNum 381{ 382 return (_pageRects != NULL && pageNum > 0 ? _pageRects[pageNum - 1] : CGRectNull); 383} 384 385@end 386 387static int comparePageRects(const void *key, const void *array) 388{ 389 const CGRect *keyRect = (const CGRect *)key; 390 const CGRect *arrayRect = (const CGRect *)array; 391 if (CGRectIntersectsRect(*arrayRect, *keyRect)) 392 return 0; 393 return CGRectGetMinY(*keyRect) > CGRectGetMaxY(*arrayRect) ? 1 : -1; 394} 395 396#endif // PLATFORM(IOS) 397