| /* |
| * Copyright (C) 2006, 2007, 2008, 2009, 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_FAMILY) |
| |
| #import "WebPDFViewIOS.h" |
| #import "WebDataSourceInternal.h" |
| |
| #import "WebFrameInternal.h" |
| #import "WebJSPDFDoc.h" |
| #import "WebKitVersionChecks.h" |
| #import "WebPDFDocumentExtras.h" |
| #import "WebPDFViewPlaceholder.h" |
| #import <JavaScriptCore/JSContextRef.h> |
| #import <JavaScriptCore/OpaqueJSString.h> |
| #import <WebCore/Color.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameLoader.h> |
| #import <WebCore/FrameLoaderClient.h> |
| #import <WebCore/GraphicsContext.h> |
| #import <WebCore/StringWithDirection.h> |
| #import <WebCore/WKGraphics.h> |
| #import <WebKitLegacy/WebFrame.h> |
| #import <WebKitLegacy/WebFrameLoadDelegate.h> |
| #import <WebKitLegacy/WebFramePrivate.h> |
| #import <WebKitLegacy/WebFrameView.h> |
| #import <WebKitLegacy/WebNSViewExtras.h> |
| #import <WebKitLegacy/WebViewPrivate.h> |
| #import <wtf/Assertions.h> |
| #import <wtf/StdLibExtras.h> |
| |
| using namespace WebCore; |
| using namespace std; |
| |
| static int comparePageRects(const void *key, const void *array); |
| |
| static const float PAGE_WIDTH_INSET = 4.0 * 2.0; |
| static const float PAGE_HEIGHT_INSET = 4.0 * 2.0; |
| |
| static CGColorRef createCGColorWithDeviceWhite(CGFloat white, CGFloat alpha) |
| { |
| static CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray(); |
| const CGFloat components[] = { white, alpha }; |
| return CGColorCreate(graySpace, components); |
| } |
| |
| @implementation WebPDFView { |
| BOOL dataSourceHasBeenSet; |
| CGPDFDocumentRef _PDFDocument; |
| NSString *_title; |
| CGRect *_pageRects; |
| } |
| |
| + (NSArray *)supportedMIMETypes |
| { |
| return [NSArray arrayWithObjects: |
| @"text/pdf", |
| @"application/pdf", |
| nil]; |
| } |
| |
| + (CGColorRef)shadowColor |
| { |
| static CGColorRef shadowColor = createCGColorWithDeviceWhite(0, 2.0 / 3); |
| return shadowColor; |
| } |
| |
| + (CGColorRef)backgroundColor |
| { |
| static CGColorRef backgroundColor = createCGColorWithDeviceWhite(204.0 / 255, 1); |
| return backgroundColor; |
| } |
| |
| // 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 |
| { |
| // If this is not the main frame, use the old WAK PDF view. |
| if ([[webFrame webView] mainFrame] != webFrame) |
| return [WebPDFView class]; |
| |
| return [WebPDFViewPlaceholder class]; |
| } |
| |
| - (void)dealloc |
| { |
| if (_PDFDocument != NULL) |
| CGPDFDocumentRelease(_PDFDocument); |
| free(_pageRects); |
| [_title release]; |
| [super dealloc]; |
| } |
| |
| - (void)drawPage:(CGPDFPageRef)aPage |
| { |
| CGContextRef context = WKGetCurrentGraphicsContext(); |
| size_t pageNumber = CGPDFPageGetPageNumber(aPage); |
| CGRect pageRect = _pageRects[pageNumber-1]; |
| |
| // Draw page. |
| CGContextSaveGState(context); |
| CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 2.0f), 3.0f, [[self class] shadowColor]); |
| CGContextSetFillColorWithColor(context, cachedCGColor(Color::white)); |
| CGContextFillRect(context, pageRect); |
| CGContextRestoreGState(context); |
| |
| CGContextSaveGState(context); |
| |
| CGContextTranslateCTM(context, CGRectGetMinX(pageRect), CGRectGetMinY(pageRect)); |
| CGContextScaleCTM(context, 1.0, -1.0); |
| CGContextTranslateCTM(context, 0, -CGRectGetHeight(pageRect)); |
| |
| CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(aPage, kCGPDFCropBox, CGRectMake(0.0, 0.0, CGRectGetWidth(pageRect), CGRectGetHeight(pageRect)), 0, true)); |
| |
| CGRect cropBox = CGPDFPageGetBoxRect(aPage, kCGPDFCropBox); |
| CGContextClipToRect(context, cropBox); |
| |
| CGContextDrawPDFPage(context, aPage); |
| CGContextRestoreGState(context); |
| } |
| |
| - (NSArray *)_pagesInRect:(CGRect)rect |
| { |
| NSMutableArray *pages = [NSMutableArray array]; |
| size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument); |
| CGRect *firstFoundRect = (CGRect *)bsearch(&rect, _pageRects, pageCount, sizeof(CGRect), comparePageRects); |
| if (firstFoundRect != NULL) { |
| size_t firstFoundIndex = firstFoundRect - _pageRects; |
| id page = (id)CGPDFDocumentGetPage(_PDFDocument, firstFoundIndex+1); |
| ASSERT(page); |
| if (!page) |
| return pages; |
| [pages addObject:page]; |
| size_t i; |
| for (i = firstFoundIndex - 1; i < pageCount; i--) { |
| if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) { |
| id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1); |
| ASSERT(page); |
| if (page) |
| [pages addObject:page]; |
| } else |
| break; |
| } |
| for (i = firstFoundIndex + 1; i < pageCount; i++) { |
| if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) { |
| id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1); |
| ASSERT(page); |
| if (page) |
| [pages addObject:page]; |
| } else |
| break; |
| } |
| } |
| return pages; |
| } |
| |
| - (void)drawRect:(CGRect)aRect |
| { |
| CGContextRef context = WKGetCurrentGraphicsContext(); |
| |
| // Draw Background. |
| CGContextSaveGState(context); |
| CGContextSetFillColorWithColor(context, [[self class] backgroundColor]); |
| CGContextFillRect(context, aRect); |
| CGContextRestoreGState(context); |
| |
| if (!_PDFDocument) |
| return; |
| |
| CGPDFPageRef page = NULL; |
| NSEnumerator * enumerator = [[self _pagesInRect:aRect] objectEnumerator]; |
| |
| while ((page = (CGPDFPageRef)[enumerator nextObject])) |
| [self drawPage:page]; |
| } |
| |
| - (void)setDataSource:(WebDataSource *)dataSource |
| { |
| // Since this class is a WebDocumentView and WebDocumentRepresentation, setDataSource: will be called twice. Do work only once. |
| if (dataSourceHasBeenSet) |
| return; |
| |
| if (!_title) |
| _title = [[[[dataSource request] URL] lastPathComponent] copy]; |
| |
| WAKView * superview = [self superview]; |
| |
| // As noted above, -setDataSource: will be called twice -- once for the WebDocumentRepresentation, once for the WebDocumentView. |
| if (!superview) |
| return; |
| |
| [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size]; |
| |
| dataSourceHasBeenSet = YES; |
| } |
| |
| - (void)dataSourceUpdated:(WebDataSource *)dataSource |
| { |
| |
| } |
| |
| - (void)setNeedsLayout:(BOOL)flag |
| { |
| |
| } |
| |
| - (void)layout |
| { |
| // <rdar://problem/7790957> Problem with UISplitViewController on iPad when displaying PDF |
| // Since we don't have anything to layout, just repaint the visible tiles. |
| [self setNeedsDisplay:YES]; |
| } |
| |
| - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow |
| { |
| |
| } |
| |
| - (void)viewDidMoveToHostWindow |
| { |
| |
| } |
| |
| - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource |
| { |
| } |
| |
| - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource |
| { |
| } |
| |
| - (void)_computePageRects |
| { |
| size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument); |
| _pageRects = (CGRect *)malloc(sizeof(CGRect) * pageCount); |
| |
| CGSize size = CGSizeMake(0.0, PAGE_HEIGHT_INSET); |
| size_t i; |
| for (i = 1; i <= pageCount; i++) { |
| |
| CGPDFPageRef page = CGPDFDocumentGetPage(_PDFDocument, i); |
| CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox); |
| CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180); |
| if (rotation != 0) { |
| boxRect = CGRectApplyAffineTransform(boxRect, CGAffineTransformMakeRotation(rotation)); |
| boxRect.size.width = roundf(boxRect.size.width); |
| boxRect.size.height = roundf(boxRect.size.height); |
| } |
| |
| _pageRects[i-1] = boxRect; |
| _pageRects[i-1].origin.y = size.height; |
| |
| size.height += boxRect.size.height + PAGE_HEIGHT_INSET; |
| size.width = max(size.width, boxRect.size.width); |
| } |
| |
| size.width += PAGE_WIDTH_INSET * 2.0; |
| [self setBoundsSize:size]; |
| |
| for (i = 0; i < pageCount; i++) |
| _pageRects[i].origin.x = roundf((size.width - _pageRects[i].size.width) / 2); |
| } |
| |
| - (void)_checkPDFTitle |
| { |
| if (!_PDFDocument) |
| return; |
| |
| NSString *title = nil; |
| |
| CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_PDFDocument); |
| CGPDFStringRef value; |
| if (CGPDFDictionaryGetString(info, "Title", &value)) |
| title = [(NSString *)CGPDFStringCopyTextString(value) autorelease]; |
| |
| if ([title length]) { |
| [_title release]; |
| _title = [title copy]; |
| core([self _frame])->loader().client().dispatchDidReceiveTitle({ title, TextDirection::LTR }); |
| } |
| } |
| |
| - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource |
| { |
| CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]); |
| |
| if (!provider) |
| return; |
| |
| _PDFDocument = CGPDFDocumentCreateWithProvider(provider); |
| CGDataProviderRelease(provider); |
| |
| if (!_PDFDocument) |
| return; |
| |
| [self _checkPDFTitle]; |
| [self _computePageRects]; |
| |
| NSArray *scripts = allScriptsInPDFDocument(_PDFDocument); |
| |
| if (![scripts count]) |
| return; |
| |
| JSGlobalContextRef ctx = JSGlobalContextCreate(0); |
| JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, dataSource); |
| for (NSString *script in scripts) |
| JSEvaluateScript(ctx, OpaqueJSString::tryCreate(script).get(), jsPDFDoc, nullptr, 0, nullptr); |
| JSGlobalContextRelease(ctx); |
| |
| [self setNeedsDisplay:YES]; |
| } |
| |
| - (BOOL)canProvideDocumentSource |
| { |
| return NO; |
| } |
| |
| - (NSString *)documentSource |
| { |
| return nil; |
| } |
| |
| - (NSString *)title |
| { |
| return _title; |
| } |
| |
| - (unsigned)pageNumberForRect:(CGRect)rect |
| { |
| size_t bestPageNumber = 0; |
| if (_PDFDocument != NULL && _pageRects != NULL) { |
| NSArray *pages = [self _pagesInRect:rect]; |
| float bestPageArea = 0; |
| size_t count = [pages count]; |
| size_t i; |
| for (i = 0; i < count; i++) { |
| size_t pageNumber = CGPDFPageGetPageNumber((CGPDFPageRef)[pages objectAtIndex:i]); |
| CGRect intersectionRect = CGRectIntersection(_pageRects[pageNumber - 1], rect); |
| float intersectionArea = intersectionRect.size.width * intersectionRect.size.height; |
| if (intersectionArea > bestPageArea) { |
| bestPageArea = intersectionArea; |
| bestPageNumber = pageNumber; |
| } |
| } |
| } |
| return bestPageNumber; |
| } |
| |
| - (unsigned)totalPages |
| { |
| if (_PDFDocument != NULL) |
| return CGPDFDocumentGetNumberOfPages(_PDFDocument); |
| return 0; |
| } |
| |
| - (CGPDFDocumentRef)doc |
| { |
| return _PDFDocument; |
| } |
| |
| - (CGRect)rectForPageNumber:(unsigned)pageNum |
| { |
| return (_pageRects != NULL && pageNum > 0 ? _pageRects[pageNum - 1] : CGRectNull); |
| } |
| |
| @end |
| |
| static int comparePageRects(const void *key, const void *array) |
| { |
| const CGRect *keyRect = (const CGRect *)key; |
| const CGRect *arrayRect = (const CGRect *)array; |
| if (CGRectIntersectsRect(*arrayRect, *keyRect)) |
| return 0; |
| return CGRectGetMinY(*keyRect) > CGRectGetMaxY(*arrayRect) ? 1 : -1; |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |