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