Move instance members from WebPDFView to implementation file
[WebKit-https.git] / Source / WebKit / ios / WebView / WebPDFViewIOS.mm
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/StdLibExtras.h>
54
55 using namespace WebCore;
56 using namespace std;
57
58 static int comparePageRects(const void *key, const void *array);
59
60 static const float PAGE_WIDTH_INSET     = 4.0 * 2.0;
61 static const float PAGE_HEIGHT_INSET    = 4.0 * 2.0;
62
63 static CGColorRef createCGColorWithDeviceWhite(CGFloat white, CGFloat alpha)
64 {
65     static CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray();
66     const CGFloat components[] = { white, alpha };
67     return CGColorCreate(graySpace, components);
68 }
69
70 @implementation WebPDFView {
71     BOOL dataSourceHasBeenSet;
72     CGPDFDocumentRef _PDFDocument;
73     NSString *_title;
74     CGRect *_pageRects;
75 }
76
77 + (NSArray *)supportedMIMETypes
78 {
79     return [NSArray arrayWithObjects:
80         @"text/pdf",
81         @"application/pdf",
82         nil];
83 }
84
85 + (CGColorRef)shadowColor
86 {
87     static CGColorRef shadowColor = createCGColorWithDeviceWhite(0, 2.0 / 3);
88     return shadowColor;
89 }
90
91 + (CGColorRef)backgroundColor
92 {
93     static CGColorRef backgroundColor = createCGColorWithDeviceWhite(204.0 / 255, 1);
94     return backgroundColor;
95 }
96
97 // This is a secret protocol for WebDataSource and WebFrameView to offer us the opportunity to do something different 
98 // depending upon which frame is trying to instantiate a representation for PDF.
99 + (Class)_representationClassForWebFrame:(WebFrame *)webFrame
100 {
101     // If this is not the main frame, use the old WAK PDF view.
102     if ([[webFrame webView] mainFrame] != webFrame)
103         return [WebPDFView class];
104     
105     return [WebPDFViewPlaceholder class];
106 }
107
108 - (void)dealloc
109 {
110     if (_PDFDocument != NULL)
111         CGPDFDocumentRelease(_PDFDocument);
112     free(_pageRects);
113     [_title release];
114     [super dealloc];
115 }
116
117 - (void)drawPage:(CGPDFPageRef)aPage
118 {
119     CGContextRef context = WKGetCurrentGraphicsContext();
120     size_t pageNumber = CGPDFPageGetPageNumber(aPage);
121     CGRect pageRect = _pageRects[pageNumber-1];
122     
123     // Draw page.
124     CGContextSaveGState(context);
125     CGFloat height = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_FLIPPED_SHADOWS) ? 2.0f : -2.0f;
126     CGContextSetShadowWithColor(context, CGSizeMake(0.0f, height), 3.0f, [[self class] shadowColor]);
127     CGContextSetFillColorWithColor(context, cachedCGColor(Color::white));
128     CGContextFillRect(context, pageRect);
129     CGContextRestoreGState(context);    
130     
131     CGContextSaveGState(context);
132
133     CGContextTranslateCTM(context, CGRectGetMinX(pageRect), CGRectGetMinY(pageRect));
134     CGContextScaleCTM(context, 1.0, -1.0);
135     CGContextTranslateCTM(context, 0, -CGRectGetHeight(pageRect));
136
137     CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(aPage, kCGPDFCropBox, CGRectMake(0.0, 0.0, CGRectGetWidth(pageRect), CGRectGetHeight(pageRect)), 0, true));
138     
139     CGRect cropBox = CGPDFPageGetBoxRect(aPage, kCGPDFCropBox);
140     CGContextClipToRect(context, cropBox);
141     
142     CGContextDrawPDFPage(context, aPage);
143     CGContextRestoreGState(context);    
144 }
145
146 - (NSArray *)_pagesInRect:(CGRect)rect
147 {
148     NSMutableArray *pages = [NSMutableArray array];
149     size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument);
150     CGRect *firstFoundRect = (CGRect *)bsearch(&rect, _pageRects, pageCount, sizeof(CGRect), comparePageRects);
151     if (firstFoundRect != NULL) {
152         size_t firstFoundIndex = firstFoundRect - _pageRects;
153         id page = (id)CGPDFDocumentGetPage(_PDFDocument, firstFoundIndex+1);
154         ASSERT(page);
155         if (!page)
156             return pages;
157         [pages addObject:page];
158         size_t i;
159         for (i = firstFoundIndex - 1; i < pageCount; i--) {
160             if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) {
161                 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1);
162                 ASSERT(page);
163                 if (page)
164                     [pages addObject:page];
165             } else
166                 break;
167         }
168         for (i = firstFoundIndex + 1; i < pageCount; i++) {
169             if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) {
170                 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1);
171                 ASSERT(page);
172                 if (page)
173                     [pages addObject:page];
174             } else
175                 break;
176         }        
177     }
178     return pages;
179 }
180
181 - (void)drawRect:(CGRect)aRect
182 {
183     CGContextRef context = WKGetCurrentGraphicsContext();
184
185     // Draw Background.
186     CGContextSaveGState(context);
187     CGContextSetFillColorWithColor(context, [[self class] backgroundColor]);
188     CGContextFillRect(context, aRect);
189     CGContextRestoreGState(context);
190
191     if (!_PDFDocument)
192         return;
193     
194     CGPDFPageRef page = NULL;
195     NSEnumerator * enumerator = [[self _pagesInRect:aRect] objectEnumerator];
196
197     while ((page = (CGPDFPageRef)[enumerator nextObject]))
198         [self drawPage:page];
199 }
200
201 - (void)setDataSource:(WebDataSource *)dataSource
202 {
203     // Since this class is a WebDocumentView and WebDocumentRepresentation, setDataSource: will be called twice. Do work only once.
204     if (dataSourceHasBeenSet)
205         return;
206
207     if (!_title)
208         _title = [[[[dataSource request] URL] lastPathComponent] copy];
209
210     WAKView * superview = [self superview];
211     
212     // As noted above, -setDataSource: will be called twice -- once for the WebDocumentRepresentation, once for the WebDocumentView.
213     if (!superview)
214         return;
215
216     [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size];
217     
218     dataSourceHasBeenSet = YES;
219 }
220
221 - (void)dataSourceUpdated:(WebDataSource *)dataSource
222 {
223
224 }
225
226 - (void)setNeedsLayout:(BOOL)flag
227 {
228
229 }
230
231 - (void)layout
232 {
233     // <rdar://problem/7790957> Problem with UISplitViewController on iPad when displaying PDF
234     // Since we don't have anything to layout, just repaint the visible tiles.
235     [self setNeedsDisplay:YES];
236 }
237
238 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
239 {
240
241 }
242
243 - (void)viewDidMoveToHostWindow
244 {
245
246 }
247
248 - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource
249 {
250 }
251
252 - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource
253 {
254 }
255
256 - (void)_computePageRects
257 {
258     size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument);
259     _pageRects = (CGRect *)malloc(sizeof(CGRect) * pageCount);    
260     
261     CGSize size = CGSizeMake(0.0, PAGE_HEIGHT_INSET);
262     size_t i;
263     for (i = 1; i <= pageCount; i++) {
264         
265         CGPDFPageRef page = CGPDFDocumentGetPage(_PDFDocument, i);
266         CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
267         CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180);
268         if (rotation != 0) {
269             boxRect = CGRectApplyAffineTransform(boxRect, CGAffineTransformMakeRotation(rotation));
270             boxRect.size.width = roundf(boxRect.size.width);
271             boxRect.size.height = roundf(boxRect.size.height);
272         }
273
274         _pageRects[i-1] = boxRect;
275         _pageRects[i-1].origin.y = size.height;
276
277         size.height += boxRect.size.height + PAGE_HEIGHT_INSET;
278         size.width = max(size.width, boxRect.size.width);
279     }
280     
281     size.width += PAGE_WIDTH_INSET * 2.0;
282     [self setBoundsSize:size];
283     
284     for (i = 0; i < pageCount; i++)
285         _pageRects[i].origin.x = roundf((size.width - _pageRects[i].size.width) / 2);
286 }
287
288 - (void)_checkPDFTitle
289 {
290     if (!_PDFDocument)
291         return;
292
293     NSString *title = nil;
294
295     CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_PDFDocument);
296     CGPDFStringRef value;
297     if (CGPDFDictionaryGetString(info, "Title", &value))
298         title = [(NSString *)CGPDFStringCopyTextString(value) autorelease];
299
300     if ([title length]) {
301         [_title release];
302         _title = [title copy];
303         core([self _frame])->loader().client().dispatchDidReceiveTitle({ title, LTR });
304     }
305 }
306
307 - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource
308 {
309     CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]);
310
311     if (!provider) 
312         return;
313     
314     _PDFDocument = CGPDFDocumentCreateWithProvider(provider);
315     CGDataProviderRelease(provider);
316         
317     if (!_PDFDocument)
318         return;
319     
320     [self _checkPDFTitle];
321     [self _computePageRects];
322
323     NSArray *scripts = allScriptsInPDFDocument(_PDFDocument);
324
325     NSUInteger scriptCount = [scripts count];
326     if (!scriptCount)
327         return;
328
329     JSGlobalContextRef ctx = JSGlobalContextCreate(0);
330     JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, dataSource);
331
332     for (NSUInteger i = 0; i < scriptCount; ++i) {
333         JSStringRef script = JSStringCreateWithCFString((CFStringRef)[scripts objectAtIndex:i]);
334         JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0);
335         JSStringRelease(script);
336     }
337
338     JSGlobalContextRelease(ctx);
339
340     [self setNeedsDisplay:YES];
341 }
342
343 - (BOOL)canProvideDocumentSource
344 {
345     return NO;
346 }
347
348 - (NSString *)documentSource
349 {
350     return nil;
351 }
352
353 - (NSString *)title
354 {
355     return _title;
356 }
357
358 - (unsigned)pageNumberForRect:(CGRect)rect
359 {
360     size_t bestPageNumber = 0;
361     if (_PDFDocument != NULL && _pageRects != NULL) {
362         NSArray *pages = [self _pagesInRect:rect];
363         float bestPageArea = 0;
364         size_t count = [pages count];
365         size_t i;
366         for (i = 0; i < count; i++) {
367             size_t pageNumber = CGPDFPageGetPageNumber((CGPDFPageRef)[pages objectAtIndex:i]);
368             CGRect intersectionRect = CGRectIntersection(_pageRects[pageNumber - 1], rect);
369             float intersectionArea = intersectionRect.size.width * intersectionRect.size.height;
370             if (intersectionArea > bestPageArea) {
371                 bestPageArea = intersectionArea;
372                 bestPageNumber = pageNumber;
373             }
374         }
375     }
376     return bestPageNumber;
377 }
378
379 - (unsigned)totalPages
380 {
381     if (_PDFDocument != NULL)
382         return CGPDFDocumentGetNumberOfPages(_PDFDocument);
383     return 0;
384 }
385
386 - (CGPDFDocumentRef)doc
387 {
388     return _PDFDocument;
389 }
390
391 - (CGRect)rectForPageNumber:(unsigned)pageNum
392 {
393     return (_pageRects != NULL && pageNum > 0 ? _pageRects[pageNum - 1] : CGRectNull);
394 }
395
396 @end
397
398 static int comparePageRects(const void *key, const void *array)
399 {
400     const CGRect *keyRect = (const CGRect *)key;
401     const CGRect *arrayRect = (const CGRect *)array;
402     if (CGRectIntersectsRect(*arrayRect, *keyRect))
403         return 0;
404     return CGRectGetMinY(*keyRect) > CGRectGetMaxY(*arrayRect) ? 1 : -1;
405 }
406
407 #endif // PLATFORM(IOS)