Use a 1-byte enum class for TextDirection
[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/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     CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 2.0f), 3.0f, [[self class] shadowColor]);
126     CGContextSetFillColorWithColor(context, cachedCGColor(Color::white));
127     CGContextFillRect(context, pageRect);
128     CGContextRestoreGState(context);    
129     
130     CGContextSaveGState(context);
131
132     CGContextTranslateCTM(context, CGRectGetMinX(pageRect), CGRectGetMinY(pageRect));
133     CGContextScaleCTM(context, 1.0, -1.0);
134     CGContextTranslateCTM(context, 0, -CGRectGetHeight(pageRect));
135
136     CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(aPage, kCGPDFCropBox, CGRectMake(0.0, 0.0, CGRectGetWidth(pageRect), CGRectGetHeight(pageRect)), 0, true));
137     
138     CGRect cropBox = CGPDFPageGetBoxRect(aPage, kCGPDFCropBox);
139     CGContextClipToRect(context, cropBox);
140     
141     CGContextDrawPDFPage(context, aPage);
142     CGContextRestoreGState(context);    
143 }
144
145 - (NSArray *)_pagesInRect:(CGRect)rect
146 {
147     NSMutableArray *pages = [NSMutableArray array];
148     size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument);
149     CGRect *firstFoundRect = (CGRect *)bsearch(&rect, _pageRects, pageCount, sizeof(CGRect), comparePageRects);
150     if (firstFoundRect != NULL) {
151         size_t firstFoundIndex = firstFoundRect - _pageRects;
152         id page = (id)CGPDFDocumentGetPage(_PDFDocument, firstFoundIndex+1);
153         ASSERT(page);
154         if (!page)
155             return pages;
156         [pages addObject:page];
157         size_t i;
158         for (i = firstFoundIndex - 1; i < pageCount; i--) {
159             if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) {
160                 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1);
161                 ASSERT(page);
162                 if (page)
163                     [pages addObject:page];
164             } else
165                 break;
166         }
167         for (i = firstFoundIndex + 1; i < pageCount; i++) {
168             if (CGRectIntersectsRect(CGRectInset(_pageRects[i], 0.0, - PAGE_HEIGHT_INSET * 2), rect)) {
169                 id page = (id)CGPDFDocumentGetPage(_PDFDocument, i + 1);
170                 ASSERT(page);
171                 if (page)
172                     [pages addObject:page];
173             } else
174                 break;
175         }        
176     }
177     return pages;
178 }
179
180 - (void)drawRect:(CGRect)aRect
181 {
182     CGContextRef context = WKGetCurrentGraphicsContext();
183
184     // Draw Background.
185     CGContextSaveGState(context);
186     CGContextSetFillColorWithColor(context, [[self class] backgroundColor]);
187     CGContextFillRect(context, aRect);
188     CGContextRestoreGState(context);
189
190     if (!_PDFDocument)
191         return;
192     
193     CGPDFPageRef page = NULL;
194     NSEnumerator * enumerator = [[self _pagesInRect:aRect] objectEnumerator];
195
196     while ((page = (CGPDFPageRef)[enumerator nextObject]))
197         [self drawPage:page];
198 }
199
200 - (void)setDataSource:(WebDataSource *)dataSource
201 {
202     // Since this class is a WebDocumentView and WebDocumentRepresentation, setDataSource: will be called twice. Do work only once.
203     if (dataSourceHasBeenSet)
204         return;
205
206     if (!_title)
207         _title = [[[[dataSource request] URL] lastPathComponent] copy];
208
209     WAKView * superview = [self superview];
210     
211     // As noted above, -setDataSource: will be called twice -- once for the WebDocumentRepresentation, once for the WebDocumentView.
212     if (!superview)
213         return;
214
215     [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size];
216     
217     dataSourceHasBeenSet = YES;
218 }
219
220 - (void)dataSourceUpdated:(WebDataSource *)dataSource
221 {
222
223 }
224
225 - (void)setNeedsLayout:(BOOL)flag
226 {
227
228 }
229
230 - (void)layout
231 {
232     // <rdar://problem/7790957> Problem with UISplitViewController on iPad when displaying PDF
233     // Since we don't have anything to layout, just repaint the visible tiles.
234     [self setNeedsDisplay:YES];
235 }
236
237 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
238 {
239
240 }
241
242 - (void)viewDidMoveToHostWindow
243 {
244
245 }
246
247 - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource
248 {
249 }
250
251 - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource
252 {
253 }
254
255 - (void)_computePageRects
256 {
257     size_t pageCount = CGPDFDocumentGetNumberOfPages(_PDFDocument);
258     _pageRects = (CGRect *)malloc(sizeof(CGRect) * pageCount);    
259     
260     CGSize size = CGSizeMake(0.0, PAGE_HEIGHT_INSET);
261     size_t i;
262     for (i = 1; i <= pageCount; i++) {
263         
264         CGPDFPageRef page = CGPDFDocumentGetPage(_PDFDocument, i);
265         CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
266         CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180);
267         if (rotation != 0) {
268             boxRect = CGRectApplyAffineTransform(boxRect, CGAffineTransformMakeRotation(rotation));
269             boxRect.size.width = roundf(boxRect.size.width);
270             boxRect.size.height = roundf(boxRect.size.height);
271         }
272
273         _pageRects[i-1] = boxRect;
274         _pageRects[i-1].origin.y = size.height;
275
276         size.height += boxRect.size.height + PAGE_HEIGHT_INSET;
277         size.width = max(size.width, boxRect.size.width);
278     }
279     
280     size.width += PAGE_WIDTH_INSET * 2.0;
281     [self setBoundsSize:size];
282     
283     for (i = 0; i < pageCount; i++)
284         _pageRects[i].origin.x = roundf((size.width - _pageRects[i].size.width) / 2);
285 }
286
287 - (void)_checkPDFTitle
288 {
289     if (!_PDFDocument)
290         return;
291
292     NSString *title = nil;
293
294     CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_PDFDocument);
295     CGPDFStringRef value;
296     if (CGPDFDictionaryGetString(info, "Title", &value))
297         title = [(NSString *)CGPDFStringCopyTextString(value) autorelease];
298
299     if ([title length]) {
300         [_title release];
301         _title = [title copy];
302         core([self _frame])->loader().client().dispatchDidReceiveTitle({ title, TextDirection::LTR });
303     }
304 }
305
306 - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource
307 {
308     CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]);
309
310     if (!provider) 
311         return;
312     
313     _PDFDocument = CGPDFDocumentCreateWithProvider(provider);
314     CGDataProviderRelease(provider);
315         
316     if (!_PDFDocument)
317         return;
318     
319     [self _checkPDFTitle];
320     [self _computePageRects];
321
322     NSArray *scripts = allScriptsInPDFDocument(_PDFDocument);
323
324     NSUInteger scriptCount = [scripts count];
325     if (!scriptCount)
326         return;
327
328     JSGlobalContextRef ctx = JSGlobalContextCreate(0);
329     JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, dataSource);
330
331     for (NSUInteger i = 0; i < scriptCount; ++i) {
332         JSStringRef script = JSStringCreateWithCFString((CFStringRef)[scripts objectAtIndex:i]);
333         JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0);
334         JSStringRelease(script);
335     }
336
337     JSGlobalContextRelease(ctx);
338
339     [self setNeedsDisplay:YES];
340 }
341
342 - (BOOL)canProvideDocumentSource
343 {
344     return NO;
345 }
346
347 - (NSString *)documentSource
348 {
349     return nil;
350 }
351
352 - (NSString *)title
353 {
354     return _title;
355 }
356
357 - (unsigned)pageNumberForRect:(CGRect)rect
358 {
359     size_t bestPageNumber = 0;
360     if (_PDFDocument != NULL && _pageRects != NULL) {
361         NSArray *pages = [self _pagesInRect:rect];
362         float bestPageArea = 0;
363         size_t count = [pages count];
364         size_t i;
365         for (i = 0; i < count; i++) {
366             size_t pageNumber = CGPDFPageGetPageNumber((CGPDFPageRef)[pages objectAtIndex:i]);
367             CGRect intersectionRect = CGRectIntersection(_pageRects[pageNumber - 1], rect);
368             float intersectionArea = intersectionRect.size.width * intersectionRect.size.height;
369             if (intersectionArea > bestPageArea) {
370                 bestPageArea = intersectionArea;
371                 bestPageNumber = pageNumber;
372             }
373         }
374     }
375     return bestPageNumber;
376 }
377
378 - (unsigned)totalPages
379 {
380     if (_PDFDocument != NULL)
381         return CGPDFDocumentGetNumberOfPages(_PDFDocument);
382     return 0;
383 }
384
385 - (CGPDFDocumentRef)doc
386 {
387     return _PDFDocument;
388 }
389
390 - (CGRect)rectForPageNumber:(unsigned)pageNum
391 {
392     return (_pageRects != NULL && pageNum > 0 ? _pageRects[pageNum - 1] : CGRectNull);
393 }
394
395 @end
396
397 static int comparePageRects(const void *key, const void *array)
398 {
399     const CGRect *keyRect = (const CGRect *)key;
400     const CGRect *arrayRect = (const CGRect *)array;
401     if (CGRectIntersectsRect(*arrayRect, *keyRect))
402         return 0;
403     return CGRectGetMinY(*keyRect) > CGRectGetMaxY(*arrayRect) ? 1 : -1;
404 }
405
406 #endif // PLATFORM(IOS)