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
41const CGFloat indicatorMargin = 20;
42const CGFloat indicatorVerticalPadding = 6;
43const CGFloat indicatorHorizontalPadding = 10;
44const CGFloat indicatorCornerRadius = 7;
45const CGFloat indicatorLabelOpacity = 0.4;
46const CGFloat indicatorFontSize = 16;
47
48const NSTimeInterval indicatorTimeout = 2;
49const NSTimeInterval indicatorFadeDuration = 0.5;
50const 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:(NSTimer *)timer
126{
127    [UIView animateWithDuration:indicatorFadeDuration animations:^{
128        self.alpha = 0;
129    }];
130
131    [_timer invalidate];
132    _timer = nullptr;
133}
134
135- (void)moveToPoint:(CGPoint)point animated:(BOOL)animated
136{
137    point.x += indicatorMargin;
138    point.y += indicatorMargin;
139
140    void (^animations)() = ^{
141        self.frameOrigin = point;
142    };
143    if (animated)
144        [UIView animateWithDuration:indicatorMoveDuration animations:animations];
145    else
146        animations();
147}
148
149- (CGSize)sizeThatFits:(CGSize)size
150{
151    CGSize labelSize = [_label sizeThatFits:[_label size]];
152    labelSize.width += 2 * indicatorHorizontalPadding;
153    labelSize.height += 2 * indicatorVerticalPadding;
154    return labelSize;
155}
156
157- (void)_updateLabel
158{
159    [_label setText:[NSString localizedStringWithFormat:WEB_UI_STRING("%d of %d", "Label for PDF page number indicator."), _currentPageNumber, _pageCount]];
160    [self sizeToFit];
161
162    if (!_pageCount || !_currentPageNumber)
163        return;
164
165    // We don't want to show the indicator when the PDF loads, but rather on the first subsequent change.
166    if (_hasValidPageCountAndCurrentPage)
167        [self show];
168    _hasValidPageCountAndCurrentPage = true;
169}
170
171- (void)_makeRoundedCorners
172{
173    CGSize cornerImageSize = CGSizeMake(2 * indicatorCornerRadius + 2, 2 * indicatorCornerRadius + 2);
174    CGRect cornerImageBounds = CGRectMake(0, 0, cornerImageSize.width, cornerImageSize.height);
175
176    UIGraphicsBeginImageContextWithOptions(cornerImageSize, NO, [UIScreen mainScreen].scale);
177
178    CGContextRef context = UIGraphicsGetCurrentContext();
179    CGContextSaveGState(context);
180    CGContextAddRect(context, cornerImageBounds);
181
182    UIBezierPath *cornerPath = [UIBezierPath bezierPathWithRoundedRect:cornerImageBounds byRoundingCorners:(UIRectCorner)UIAllCorners cornerRadii:CGSizeMake(indicatorCornerRadius, indicatorCornerRadius)];
183    CGContextAddPath(context, [cornerPath CGPath]);
184    CGContextEOClip(context);
185    CGContextFillRect(context, cornerImageBounds);
186    CGContextRestoreGState(context);
187
188    UIImage *cornerImage = UIGraphicsGetImageFromCurrentImageContext();
189
190    UIGraphicsEndImageContext();
191
192    UIView *contentView = [_backdropView contentView];
193
194    UIEdgeInsets capInsets = UIEdgeInsetsMake(indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius, indicatorCornerRadius);
195    cornerImage = [cornerImage resizableImageWithCapInsets:capInsets];
196
197    RetainPtr<UIImageView> cornerMaskView = adoptNS([[UIImageView alloc] initWithImage:cornerImage]);
198    [cornerMaskView setAlpha:0];
199    [cornerMaskView _setBackdropMaskViewFlags:_UIBackdropMaskViewAll];
200    [cornerMaskView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
201    [cornerMaskView setFrame:contentView.bounds];
202
203    [contentView addSubview:cornerMaskView.get()];
204}
205
206@end
207
208#endif /* PLATFORM(IOS) */
209