[iOS] Teach WKPDFView to navigate to pageNumber links
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKPDFPageNumberIndicator.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 "WKPDFPageNumberIndicator.h"
28
29 #if PLATFORM(IOS)
30
31 #import <QuartzCore/CAFilter.h>
32 #import <QuartzCore/CALayerPrivate.h>
33 #import <UIKit/UIGeometry_Private.h>
34 #import <UIKit/UIKit.h>
35 #import <UIKit/UIView_Private.h>
36 #import <UIKit/_UIBackdropView_Private.h>
37 #import <WebCore/LocalizedStrings.h>
38 #import <wtf/RetainPtr.h>
39 #import <wtf/text/WTFString.h>
40
41 const CGFloat indicatorMargin = 20;
42 const CGFloat indicatorVerticalPadding = 6;
43 const CGFloat indicatorHorizontalPadding = 10;
44 const CGFloat indicatorCornerRadius = 7;
45 const CGFloat indicatorLabelOpacity = 0.4;
46 const CGFloat indicatorFontSize = 16;
47
48 const NSTimeInterval indicatorTimeout = 2;
49 const NSTimeInterval indicatorFadeDuration = 0.5;
50 const NSTimeInterval indicatorMoveDuration = 0.3;
51
52 @implementation WKPDFPageNumberIndicator {
53     RetainPtr<UILabel> _label;
54     RetainPtr<_UIBackdropView> _backdropView;
55     RetainPtr<NSTimer> _timer;
56
57     bool _hasValidPageCountAndCurrentPage;
58 }
59
60 - (id)initWithFrame:(CGRect)frame
61 {
62     self = [super initWithFrame:frame];
63     if (!self)
64         return nil;
65
66     self.alpha = 0;
67     self.layer.allowsGroupOpacity = NO;
68     self.layer.allowsGroupBlending = NO;
69
70     _backdropView = adoptNS([[_UIBackdropView alloc] initWithPrivateStyle:_UIBackdropViewStyle_Light]);
71     [_backdropView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
72     [self addSubview:_backdropView.get()];
73     [self _makeRoundedCorners];
74
75     _label = adoptNS([[UILabel alloc] initWithFrame:CGRectZero]);
76
77     [_label setOpaque:NO];
78     [_label setBackgroundColor:nil];
79     [_label setTextAlignment:NSTextAlignmentCenter];
80     [_label setFont:[UIFont boldSystemFontOfSize:indicatorFontSize]];
81     [_label setTextColor:[UIColor blackColor]];
82     [_label setAlpha:indicatorLabelOpacity];
83     [_label setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
84     [[_label layer] setCompositingFilter:[CAFilter filterWithType:kCAFilterPlusD]];
85
86     [self addSubview:_label.get()];
87     
88     return self;
89 }
90
91 - (void)dealloc
92 {
93     [_timer invalidate];
94     [super dealloc];
95 }
96
97 - (void)setCurrentPageNumber:(unsigned)currentPageNumber
98 {
99     if (_currentPageNumber == currentPageNumber)
100         return;
101
102     _currentPageNumber = currentPageNumber;
103     [self _updateLabel];
104 }
105
106 - (void)setPageCount:(unsigned)pageCount
107 {
108     if (_pageCount == pageCount)
109         return;
110
111     _pageCount = pageCount;
112     [self _updateLabel];
113 }
114
115 - (void)show
116 {
117     self.alpha = 1;
118
119     if (_timer)
120         [_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:indicatorTimeout]];
121     else
122         _timer = [NSTimer scheduledTimerWithTimeInterval:indicatorTimeout target:self selector:@selector(hide:) userInfo:nil repeats:NO];
123 }
124
125 - (void)hide
126 {
127     [self hide:nil];
128 }
129
130 - (void)hide:(NSTimer *)timer
131 {
132     [UIView animateWithDuration:indicatorFadeDuration animations:^{
133         self.alpha = 0;
134     }];
135
136     [_timer invalidate];
137     _timer = nullptr;
138 }
139
140 - (void)moveToPoint:(CGPoint)point animated:(BOOL)animated
141 {
142     point.x += indicatorMargin;
143     point.y += indicatorMargin;
144
145     void (^animations)() = ^{
146         self.frameOrigin = point;
147     };
148     if (animated)
149         [UIView animateWithDuration:indicatorMoveDuration animations:animations];
150     else
151         animations();
152 }
153
154 - (CGSize)sizeThatFits:(CGSize)size
155 {
156     CGSize labelSize = [_label sizeThatFits:[_label size]];
157     labelSize.width += 2 * indicatorHorizontalPadding;
158     labelSize.height += 2 * indicatorVerticalPadding;
159     return labelSize;
160 }
161
162 - (void)_updateLabel
163 {
164     [_label setText:[NSString localizedStringWithFormat:WEB_UI_STRING("%d of %d", "Label for PDF page number indicator."), _currentPageNumber, _pageCount]];
165     [self sizeToFit];
166
167     if (!_pageCount || !_currentPageNumber)
168         return;
169
170     // We don't want to show the indicator when the PDF loads, but rather on the first subsequent change.
171     if (_hasValidPageCountAndCurrentPage)
172         [self show];
173     _hasValidPageCountAndCurrentPage = true;
174 }
175
176 - (void)_makeRoundedCorners
177 {
178     CGSize cornerImageSize = CGSizeMake(2 * indicatorCornerRadius + 2, 2 * indicatorCornerRadius + 2);
179     CGRect cornerImageBounds = CGRectMake(0, 0, cornerImageSize.width, cornerImageSize.height);
180
181     UIGraphicsBeginImageContextWithOptions(cornerImageSize, NO, [UIScreen mainScreen].scale);
182
183     CGContextRef context = UIGraphicsGetCurrentContext();
184     CGContextSaveGState(context);
185     CGContextAddRect(context, cornerImageBounds);
186
187     UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:cornerImageBounds byRoundingCorners:(UIRectCorner)UIAllCorners cornerRadii:CGSizeMake(indicatorCornerRadius, indicatorCornerRadius)];
188     CGContextAddPath(context, [cornerPath CGPath]);
189     CGContextEOClip(context);
190     CGContextFillRect(context, cornerImageBounds);
191     CGContextRestoreGState(context);
192
193     UIImage *cornerImage = UIGraphicsGetImageFromCurrentImageContext();
194
195     UIGraphicsEndImageContext();
196
197     UIView *contentView = [_backdropView contentView];
198
199     UIEdgeInsets capInsets = UIEdgeInsetsMake(indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius);
200     cornerImage = [cornerImage resizableImageWithCapInsets:capInsets];
201
202     RetainPtr<UIImageView> cornerMaskView = adoptNS([[UIImageView alloc] initWithImage:cornerImage]);
203     [cornerMaskView setAlpha:0];
204     [cornerMaskView _setBackdropMaskViewFlags:_UIBackdropMaskViewAll];
205     [cornerMaskView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
206     [cornerMaskView setFrame:contentView.bounds];
207
208     [contentView addSubview:cornerMaskView.get()];
209 }
210
211 @end
212
213 #endif /* PLATFORM(IOS) */