WKPDFView paints rotated pages squished
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKPDFView.mm
1 /*
2  * Copyright (C) 2014 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 #import "config.h"
27 #import "WKPDFView.h"
28
29 #if PLATFORM(IOS)
30
31 #import "WKPDFPageNumberIndicator.h"
32 #import "WKWebViewInternal.h"
33 #import <CorePDF/UIPDFDocument.h>
34 #import <CorePDF/UIPDFPage.h>
35 #import <CorePDF/UIPDFPageView.h>
36 #import <UIKit/UIScrollView_Private.h>
37 #import <WebCore/FloatRect.h>
38 #import <wtf/RetainPtr.h>
39 #import <wtf/Vector.h>
40
41 using namespace WebCore;
42
43 const CGFloat pdfPageMargin = 8;
44 const CGFloat pdfMinimumZoomScale = 1;
45 const CGFloat pdfMaximumZoomScale = 5;
46
47 const float overdrawHeightMultiplier = 1.5;
48
49 static const CGFloat smartMagnificationElementPadding = 0.05;
50
51 typedef struct {
52     CGRect frame;
53     RetainPtr<UIPDFPageView> view;
54     RetainPtr<UIPDFPage> page;
55 } PDFPageInfo;
56
57 @implementation WKPDFView {
58     RetainPtr<UIPDFDocument> _pdfDocument;
59     RetainPtr<NSString> _suggestedFilename;
60     RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
61
62     Vector<PDFPageInfo> _pages;
63     unsigned _centerPageNumber;
64
65     CGSize _minimumSize;
66     CGSize _overlaidAccessoryViewsInset;
67     WKWebView *_webView;
68     UIScrollView *_scrollView;
69     UIView *_fixedOverlayView;
70
71     BOOL _isStartingZoom;
72 }
73
74 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
75 {
76     if (!(self = [super initWithFrame:frame]))
77         return nil;
78
79     self.backgroundColor = [UIColor grayColor];
80
81     _webView = webView;
82
83     _scrollView = webView.scrollView;
84     [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
85     [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
86     [_scrollView setBackgroundColor:[UIColor grayColor]];
87
88     return self;
89 }
90
91 - (void)dealloc
92 {
93     [self _clearPages];
94     [_pageNumberIndicator removeFromSuperview];
95     [super dealloc];
96 }
97
98 - (NSString *)suggestedFilename
99 {
100     return _suggestedFilename.get();
101 }
102
103 - (CGPDFDocumentRef)pdfDocument
104 {
105     return [_pdfDocument CGDocument];
106 }
107
108 - (void)_clearPages
109 {
110     for (auto& page : _pages) {
111         [page.view removeFromSuperview];
112         [page.view setDelegate:nil];
113     }
114     
115     _pages.clear();
116 }
117
118 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
119 {
120     _suggestedFilename = adoptNS([filename copy]);
121
122     [self _clearPages];
123
124     RetainPtr<CGDataProvider> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
125     RetainPtr<CGPDFDocumentRef> cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
126     _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:cgPDFDocument.get()]);
127
128     [self _computePageAndDocumentFrames];
129     [self _revalidateViews];
130 }
131
132 - (void)web_setMinimumSize:(CGSize)size
133 {
134     _minimumSize = size;
135
136     [self _computePageAndDocumentFrames];
137     [self _revalidateViews];
138 }
139
140 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
141 {
142     if (scrollView.isZoomBouncing || scrollView._isAnimatingZoom)
143         return;
144
145     [self _revalidateViews];
146     [_pageNumberIndicator show];
147 }
148
149 - (void)_revalidateViews
150 {
151     if (_isStartingZoom)
152         return;
153
154     CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self];
155
156     // We apply overdraw after applying scale in order to avoid excessive
157     // memory use caused by scaling the overdraw.
158     CGRect targetRectWithOverdraw = CGRectInset(targetRect, 0, -targetRect.size.height * overdrawHeightMultiplier);
159     CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2);
160
161     _centerPageNumber = 0;
162     unsigned currentPage = 0;
163
164     for (auto& pageInfo : _pages) {
165         ++currentPage;
166
167         if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw)) {
168             [pageInfo.view removeFromSuperview];
169             pageInfo.view = nullptr;
170             continue;
171         }
172
173         if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
174             _centerPageNumber = currentPage;
175
176         if (pageInfo.view)
177             continue;
178
179         pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
180         [pageInfo.view setUseBackingLayer:YES];
181         [pageInfo.view setDelegate:self];
182         [self addSubview:pageInfo.view.get()];
183
184         [pageInfo.view setFrame:pageInfo.frame];
185         [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
186     }
187
188     [self _updatePageNumberIndicator];
189 }
190
191 - (CGPoint)_offsetForPageNumberIndicator
192 {
193     UIEdgeInsets contentInset = [_webView _computedContentInset];
194     return CGPointMake(contentInset.left, contentInset.top + _overlaidAccessoryViewsInset.height);
195 }
196
197 - (void)_updatePageNumberIndicator
198 {
199     if (!_pageNumberIndicator)
200         _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]);
201
202     [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
203
204     [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber];
205     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO];
206 }
207
208 - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset
209 {
210     _overlaidAccessoryViewsInset = inset;
211     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES];
212 }
213
214 - (void)web_computedContentInsetDidChange
215 {
216     [self _updatePageNumberIndicator];
217 }
218
219 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView
220 {
221     _fixedOverlayView = fixedOverlayView;
222
223     if (_pageNumberIndicator)
224         [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
225 }
226
227 - (void)_computePageAndDocumentFrames
228 {
229     NSUInteger pageCount = [_pdfDocument numberOfPages];
230     [_pageNumberIndicator setPageCount:pageCount];
231     
232     [self _clearPages];
233
234     _pages.reserveCapacity(pageCount);
235
236     CGRect pageFrame = CGRectMake(0, 0, _minimumSize.width, _minimumSize.height);
237     for (NSUInteger pageNumber = 0; pageNumber < pageCount; ++pageNumber) {
238         UIPDFPage *page = [_pdfDocument pageAtIndex:pageNumber];
239         if (!page)
240             continue;
241
242         CGSize pageSize = [page cropBoxAccountForRotation].size;
243         pageFrame.size.height = pageSize.height / pageSize.width * pageFrame.size.width;
244         CGRect pageFrameWithMarginApplied = CGRectInset(pageFrame, pdfPageMargin, pdfPageMargin);
245
246         PDFPageInfo pageInfo;
247         pageInfo.page = page;
248         pageInfo.frame = pageFrameWithMarginApplied;
249         _pages.append(pageInfo);
250         pageFrame.origin.y += pageFrame.size.height - pdfPageMargin;
251     }
252
253     CGFloat scale = _scrollView.zoomScale;
254     CGRect newFrame = [self frame];
255     newFrame.size.width = _minimumSize.width * scale;
256     newFrame.size.height = std::max(pageFrame.origin.y + pdfPageMargin, _minimumSize.height) * scale;
257
258     [self setFrame:newFrame];
259     [_scrollView setContentSize:newFrame.size];
260 }
261
262
263 - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind
264 {
265     _isStartingZoom = YES;
266
267     BOOL isImage = kind == kUIPDFObjectKindGraphic;
268
269     if (!isImage)
270         targetRect = CGRectInset(targetRect, smartMagnificationElementPadding * targetRect.size.width, smartMagnificationElementPadding * targetRect.size.height);
271
272     CGRect rectInDocumentCoordinates = [pageView convertRect:targetRect toView:self];
273     CGPoint originInDocumentCoordinates = [pageView convertPoint:origin toView:self];
274
275     [_webView _zoomToRect:rectInDocumentCoordinates withOrigin:originInDocumentCoordinates fitEntireRect:isImage minimumScale:pdfMinimumZoomScale maximumScale:pdfMaximumZoomScale minimumScrollDistance:0];
276
277     _isStartingZoom = NO;
278 }
279
280 - (void)resetZoom:(UIPDFPageView *)pageView
281 {
282     _isStartingZoom = YES;
283     
284     CGRect scrollViewBounds = _scrollView.bounds;
285     CGPoint centerOfPageInDocumentCoordinates = [_scrollView convertPoint:CGPointMake(CGRectGetMidX(scrollViewBounds), CGRectGetMidY(scrollViewBounds)) toView:self];
286     [_webView _zoomOutWithOrigin:centerOfPageInDocumentCoordinates];
287
288     _isStartingZoom = NO;
289 }
290
291 @end
292
293 #endif /* PLATFORM(IOS) */