/* * Copyright (C) 2006, 2007, 2008, 2010, 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #if PLATFORM(IOS) #import "WebPDFViewPlaceholder.h" #import "WebPDFViewIOS.h" #import "WebFrameInternal.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import using namespace WebCore; @interface WebPDFView (Secrets) + (Class)_representationClassForWebFrame:(WebFrame *)webFrame; @end #pragma mark Constants static const float PAGE_WIDTH_INSET = 4.0f * 2.0f; static const float PAGE_HEIGHT_INSET = 4.0f * 2.0f; #pragma mark NSValue helpers @interface NSValue (_web_Extensions) + (NSValue *)_web_valueWithCGRect:(CGRect)rect; @end @implementation NSValue (_web_Extensions) + (NSValue *)_web_valueWithCGRect:(CGRect)rect { return [NSValue valueWithBytes:&rect objCType:@encode(CGRect)]; } - (CGRect)CGRectValue { CGRect result; [self getValue:&result]; return result; } @end #pragma mark - @interface WebPDFViewPlaceholder () - (void)_evaluateJSForDocument:(CGPDFDocumentRef)document; - (void)_updateTitleForDocumentIfAvailable; - (void)_updateTitleForURL:(NSURL *)URL; - (CGSize)_computePageRects:(CGPDFDocumentRef)document; @property (retain) NSArray *pageRects; @property (retain) NSArray *pageYOrigins; @property (retain) NSString *title; @end #pragma mark WebPDFViewPlaceholder implementation @implementation WebPDFViewPlaceholder @synthesize delegate = _delegate; @synthesize pageRects = _pageRects; @synthesize pageYOrigins = _pageYOrigins; @synthesize document = _document; @synthesize title = _title; @synthesize containerSize = _containerSize; - (CGPDFDocumentRef)document { @synchronized(self) { if (_document) return _document; } if ([self.delegate respondsToSelector:@selector(cgPDFDocument)]) return [self.delegate cgPDFDocument]; return nil; } - (void)setDocument:(CGPDFDocumentRef)document { @synchronized(self) { CGPDFDocumentRetain(document); CGPDFDocumentRelease(_document); _document = document; } } - (void)clearDocument { [self setDocument:nil]; } - (CGPDFDocumentRef)doc { return [self document]; } - (NSUInteger)totalPages { return CGPDFDocumentGetNumberOfPages([self document]); } + (void)setAsPDFDocRepAndView { [WebView _setPDFRepresentationClass:[WebPDFViewPlaceholder class]]; [WebView _setPDFViewClass:[WebPDFViewPlaceholder class]]; } // This is a secret protocol for WebDataSource and WebFrameView to offer us the opportunity to do something different // depending upon which frame is trying to instantiate a representation for PDF. + (Class)_representationClassForWebFrame:(WebFrame *)webFrame { return [WebPDFView _representationClassForWebFrame:webFrame]; } - (void)dealloc { if ([self.delegate respondsToSelector:@selector(viewWillClose)]) [self.delegate viewWillClose]; [self setTitle:nil]; [self setPageRects:nil]; [self setPageYOrigins:nil]; [self setDocument:nil]; [super dealloc]; } #pragma mark WebPDFDocumentView and WebPDFDocumentRepresentation protocols + (NSArray *)supportedMIMETypes { return [WebPDFView supportedMIMETypes]; } #pragma mark WebDocumentView and WebDocumentRepresentation protocols - (void)setDataSource:(WebDataSource *)dataSource { [self dataSourceUpdated:dataSource]; if ([dataSource request]) [self _updateTitleForURL:[[dataSource request] URL]]; WAKView *superview = [self superview]; if (superview) [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size]; } #pragma mark WebPDFViewPlaceholderDelegate stuff - (void)_notifyDidCompleteLayout { if ([NSThread isMainThread]) { if ([self.delegate respondsToSelector:@selector(didCompleteLayout)]) [self.delegate didCompleteLayout]; } else [self performSelectorOnMainThread:@selector(_notifyDidCompleteLayout) withObject:nil waitUntilDone:NO]; } #pragma mark WebDocumentView protocol - (void)dataSourceUpdated:(WebDataSource *)dataSource { _dataSource = dataSource; } - (void)layout { if (!_didFinishLoadAndMemoryMap) return; if (self.pageRects) return; CGSize boundingSize = [self _computePageRects:_document]; [self setBoundsSize:boundingSize]; _didCompleteLayout = YES; [self _notifyDidCompleteLayout]; } - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow { } - (void)viewDidMoveToHostWindow { } #pragma mark WebDataSourcePrivateDelegate protocol #if ENABLE(DISK_IMAGE_CACHE) - (void)dataSourceMemoryMapped { _didFinishLoadAndMemoryMap = YES; CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[_dataSource data]); if (!provider) return; _document = CGPDFDocumentCreateWithProvider(provider); // Dump the data provider as soon as possible since the CGPDFDocument will not hold onto it. CGDataProviderRelease(provider); [self _doPostLoadOrUnlockTasks]; } - (void)dataSourceMemoryMapFailed { // Nothing we can do about this, just do what we would normally do in memory. [self dataSourceMemoryMapped]; } #endif #pragma mark WebDocumentRepresentation protocol - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource { } - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource { } - (void)_doPostLoadOrUnlockTasks { if (!_document) return; [self _updateTitleForDocumentIfAvailable]; [self _evaluateJSForDocument:_document]; // Any remaining work on the document should be done before this call to -layout, // which will hand ownership of the document to the delegate (UIWebPDFView) and // release the placeholder's document. [self layout]; } - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource { [self dataSourceUpdated:dataSource]; #if ENABLE(DISK_IMAGE_CACHE) [dataSource setDataSourceDelegate:(NSObject *)self]; [dataSource _setAllowToBeMemoryMapped]; #else [self dataSourceMemoryMapped]; #endif } - (BOOL)canProvideDocumentSource { return NO; } - (NSString *)documentSource { return nil; } #pragma mark internal stuff - (void)_evaluateJSForDocument:(CGPDFDocumentRef)pdfDocument { if (!pdfDocument || !CGPDFDocumentIsUnlocked(pdfDocument)) return; NSArray *scripts = allScriptsInPDFDocument(pdfDocument); NSUInteger scriptCount = [scripts count]; if (scriptCount) { JSGlobalContextRef ctx = JSGlobalContextCreate(0); JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, _dataSource); for (NSUInteger i = 0; i < scriptCount; ++i) { JSStringRef script = JSStringCreateWithCFString((CFStringRef)[scripts objectAtIndex:i]); JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0); JSStringRelease(script); } JSGlobalContextRelease(ctx); } } - (void)_updateTitleForURL:(NSURL *)URL { NSString *titleFromURL = [URL lastPathComponent]; if (![titleFromURL length] || [titleFromURL isEqualToString:@"/"]) titleFromURL = [[URL _web_hostString] _web_decodeHostName]; [self setTitle:titleFromURL]; [[self _frame] _dispatchDidReceiveTitle:titleFromURL]; } - (void)_updateTitleForDocumentIfAvailable { if (!_document || !CGPDFDocumentIsUnlocked(_document)) return; NSString *title = nil; CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_document); CGPDFStringRef value; if (CGPDFDictionaryGetString(info, "Title", &value)) title = (NSString *)CGPDFStringCopyTextString(value); if ([title length]) { [self setTitle:title]; [[self _frame] _dispatchDidReceiveTitle:title]; } [title release]; } - (CGRect)_getPDFPageBounds:(CGPDFPageRef)page { if (!page) return CGRectZero; CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFCropBox); CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180.0f); if (rotation != 0) bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeRotation(rotation)); bounds.origin.x = roundf(bounds.origin.x); bounds.origin.y = roundf(bounds.origin.y); bounds.size.width = roundf(bounds.size.width); bounds.size.height = roundf(bounds.size.height); return bounds; } - (CGSize)_computePageRects:(CGPDFDocumentRef)pdfDocument { CGSize boundingSize = CGSizeZero; // Do we even need to compute the page rects? if (self.pageRects) return [self bounds].size; if (!pdfDocument) return boundingSize; if (!CGPDFDocumentIsUnlocked(pdfDocument)) return boundingSize; size_t pageCount = CGPDFDocumentGetNumberOfPages(pdfDocument); if (!pageCount) return boundingSize; NSMutableArray *pageRects = [NSMutableArray array]; if (!pageRects) return boundingSize; NSMutableArray *pageYOrigins = [NSMutableArray array]; if (!pageYOrigins) return boundingSize; // Temporary vector to avoid getting the page rects twice. WTF::Vector pageCropBoxes; // First we need to determine the width of the widest page. CGFloat maxPageWidth = 0.0f; // CG uses page numbers instead of page indices, so 1 based. for (size_t i = 1; i <= pageCount; i++) { CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i); if (!page) { // So if there is a missing page, then effectively the document ends here. // See iOS Mail crashes when opening a PDF pageCount = i - 1; break; } CGRect pageRect = [self _getPDFPageBounds:page]; maxPageWidth = std::max(maxPageWidth, pageRect.size.width); pageCropBoxes.append(pageRect); } CGFloat scalingFactor = _containerSize.width / maxPageWidth; if (_containerSize.width < FLT_EPSILON) scalingFactor = 1.0; // Including the margins, what is the desired width of the content? CGFloat desiredWidth = (maxPageWidth + (PAGE_WIDTH_INSET * 2.0f)) * scalingFactor; CGFloat desiredHeight = PAGE_HEIGHT_INSET; for (size_t i = 0; i < pageCount; i++) { CGRect pageRect = pageCropBoxes[i]; // Apply our scaling to this page's bounds. pageRect.size.width = roundf(pageRect.size.width * scalingFactor); pageRect.size.height = roundf(pageRect.size.height * scalingFactor); // Center this page horizontally and update the starting vertical offset. pageRect.origin.x = roundf((desiredWidth - pageRect.size.width) / 2.0f); pageRect.origin.y = desiredHeight; // Save this page's rect and minimum y-offset. [pageRects addObject:[NSValue _web_valueWithCGRect:pageRect]]; [pageYOrigins addObject:[NSNumber numberWithFloat:CGRectGetMinY(pageRect)]]; // Update the next starting vertical offset. desiredHeight += pageRect.size.height + PAGE_HEIGHT_INSET; } // Save the resulting page rects array and minimum y-offsets array. self.pageRects = pageRects; self.pageYOrigins = pageYOrigins; // Determine the result desired bounds. return CGSizeMake(roundf(desiredWidth), roundf(desiredHeight)); } - (void)didUnlockDocument { [self _doPostLoadOrUnlockTasks]; } - (CGRect)rectForPageNumber:(NSUInteger)pageNumber { // Page number is 1-based, not 0 based. if ((!pageNumber) || (pageNumber > [_pageRects count])) return CGRectNull; return [[_pageRects objectAtIndex:pageNumber - 1] CGRectValue]; } - (void)simulateClickOnLinkToURL:(NSURL *)URL { if (!URL) return; // Construct an event to simulate a click. RefPtr event = MouseEvent::create(eventNames().clickEvent, true, true, currentTime(), 0, 1, 0, 0, 0, 0, false, false, false, false, 0, 0, 0, true); // Call to the frame loader because this is where our security checks are made. Frame* frame = core([_dataSource webFrame]); frame->loader().loadFrameRequest(FrameLoadRequest(frame->document()->securityOrigin(), ResourceRequest(URL)), false, false, event.get(), 0, MaybeSendReferrer); } @end #endif /* PLATFORM(IOS) */