[iOS][WK2] Rotating a zoomed PDF leads to weird scrolling behavior
[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 <WebCore/FloatRect.h>
37 #import <wtf/RetainPtr.h>
38 #import <wtf/Vector.h>
39
40 using namespace WebCore;
41
42 const CGFloat pdfPageMargin = 8;
43 const CGFloat pdfMinimumZoomScale = 1;
44 const CGFloat pdfMaximumZoomScale = 5;
45
46 const float overdrawHeightMultiplier = 1.5;
47
48 typedef struct {
49     CGRect frame;
50     RetainPtr<UIPDFPageView> view;
51     RetainPtr<UIPDFPage> page;
52 } PDFPageInfo;
53
54 @implementation WKPDFView {
55     RetainPtr<UIPDFDocument> _pdfDocument;
56     RetainPtr<NSString> _suggestedFilename;
57     RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
58
59     Vector<PDFPageInfo> _pages;
60     unsigned _centerPageNumber;
61
62     CGSize _minimumSize;
63     CGSize _overlaidAccessoryViewsInset;
64     WKWebView *_webView;
65     UIScrollView *_scrollView;
66     UIView *_fixedOverlayView;
67 }
68
69 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
70 {
71     if (!(self = [super initWithFrame:frame]))
72         return nil;
73
74     self.backgroundColor = [UIColor grayColor];
75
76     _webView = webView;
77
78     _scrollView = webView.scrollView;
79     [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
80     [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
81     [_scrollView setBackgroundColor:[UIColor grayColor]];
82
83     return self;
84 }
85
86 - (void)dealloc
87 {
88     [_pageNumberIndicator removeFromSuperview];
89     [super dealloc];
90 }
91
92 - (NSString *)suggestedFilename
93 {
94     return _suggestedFilename.get();
95 }
96
97 - (CGPDFDocumentRef)pdfDocument
98 {
99     return [_pdfDocument CGDocument];
100 }
101
102 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
103 {
104     _suggestedFilename = adoptNS([filename copy]);
105
106     for (auto& page : _pages)
107         [page.view removeFromSuperview];
108
109     _pages.clear();
110
111     RetainPtr<CGDataProvider> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
112     RetainPtr<CGPDFDocumentRef> cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
113     _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:cgPDFDocument.get()]);
114
115     [self _computePageAndDocumentFrames];
116     [self _revalidateViews];
117 }
118
119 - (void)web_setMinimumSize:(CGSize)size
120 {
121     _minimumSize = size;
122
123     [self _computePageAndDocumentFrames];
124     [self _revalidateViews];
125 }
126
127 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
128 {
129     if (scrollView.isZoomBouncing)
130         return;
131
132     [self _revalidateViews];
133     [_pageNumberIndicator show];
134 }
135
136 - (void)_revalidateViews
137 {
138     CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self];
139
140     // We apply overdraw after applying scale in order to avoid excessive
141     // memory use caused by scaling the overdraw.
142     CGRect targetRectWithOverdraw = CGRectInset(targetRect, 0, -targetRect.size.height * overdrawHeightMultiplier);
143     CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2);
144
145     _centerPageNumber = 0;
146     unsigned currentPage = 0;
147
148     for (auto& pageInfo : _pages) {
149         ++currentPage;
150
151         if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw)) {
152             [pageInfo.view removeFromSuperview];
153             pageInfo.view = nullptr;
154             continue;
155         }
156
157         if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
158             _centerPageNumber = currentPage;
159
160         if (pageInfo.view)
161             continue;
162
163         pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
164         [pageInfo.view setUseBackingLayer:YES];
165         [self addSubview:pageInfo.view.get()];
166
167         [pageInfo.view setFrame:pageInfo.frame];
168         [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
169     }
170
171     [self _updatePageNumberIndicator];
172 }
173
174 - (CGPoint)_offsetForPageNumberIndicator
175 {
176     UIEdgeInsets contentInset = [_webView _computedContentInset];
177     return CGPointMake(contentInset.left, contentInset.top + _overlaidAccessoryViewsInset.height);
178 }
179
180 - (void)_updatePageNumberIndicator
181 {
182     if (!_pageNumberIndicator)
183         _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]);
184
185     [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
186
187     [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber];
188     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO];
189 }
190
191 - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset
192 {
193     _overlaidAccessoryViewsInset = inset;
194     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES];
195 }
196
197 - (void)web_computedContentInsetDidChange
198 {
199     [self _updatePageNumberIndicator];
200 }
201
202 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView
203 {
204     _fixedOverlayView = fixedOverlayView;
205
206     if (_pageNumberIndicator)
207         [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
208 }
209
210 - (void)_computePageAndDocumentFrames
211 {
212     NSUInteger pageCount = [_pdfDocument numberOfPages];
213     [_pageNumberIndicator setPageCount:pageCount];
214
215     for (auto& pageInfo : _pages)
216         [pageInfo.view removeFromSuperview];
217
218     _pages.clear();
219     _pages.reserveCapacity(pageCount);
220
221     CGRect pageFrame = CGRectMake(0, 0, _minimumSize.width, _minimumSize.height);
222     for (NSUInteger pageNumber = 0; pageNumber < pageCount; ++pageNumber) {
223         UIPDFPage *page = [_pdfDocument pageAtIndex:pageNumber];
224         if (!page)
225             continue;
226
227         CGSize pageSize = [page size];
228         pageFrame.size.height = pageSize.height / pageSize.width * pageFrame.size.width;
229         CGRect pageFrameWithMarginApplied = CGRectInset(pageFrame, pdfPageMargin, pdfPageMargin);
230
231         PDFPageInfo pageInfo;
232         pageInfo.page = page;
233         pageInfo.frame = pageFrameWithMarginApplied;
234         _pages.append(pageInfo);
235         pageFrame.origin.y += pageFrame.size.height - pdfPageMargin;
236     }
237
238     CGFloat scale = _scrollView.zoomScale;
239     CGRect newFrame = [self frame];
240     newFrame.size.width = _minimumSize.width * scale;
241     newFrame.size.height = std::max(pageFrame.origin.y + pdfPageMargin, _minimumSize.height) * scale;
242
243     [self setFrame:newFrame];
244     [_scrollView setContentSize:newFrame.size];
245 }
246
247 @end
248
249 #endif /* PLATFORM(IOS) */