[iOS] Input type=time elements styled with SVG fonts have 0 width
[WebKit-https.git] / Source / WebCore / platform / text / ios / LocalizedDateCache.mm
1 /*
2  * Copyright (C) 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "LocalizedDateCache.h"
28
29 #import "Font.h"
30 #import "TextRun.h"
31 #import <math.h>
32 #import <wtf/Assertions.h>
33 #import <wtf/StdLibExtras.h>
34 #import <CoreFoundation/CFNotificationCenter.h>
35
36 // FIXME: Rename this file to LocalizedDataCacheIOS.mm and remove this guard.
37 #if PLATFORM(IOS)
38
39 using namespace std;
40
41 namespace WebCore {
42
43 LocalizedDateCache& localizedDateCache()
44 {
45     static NeverDestroyed<LocalizedDateCache> cache;
46     return cache;
47 }
48
49 static void _localeChanged(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef)
50 {
51     localizedDateCache().localeChanged();
52 }
53
54 LocalizedDateCache::LocalizedDateCache()
55     : m_font(Font())
56 {
57     // Listen to CF Notifications for locale change, and clear the cache when it does.
58     CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), (void*)this, _localeChanged,
59                                     kCFLocaleCurrentLocaleDidChangeNotification,
60                                     NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
61 }
62
63 LocalizedDateCache::~LocalizedDateCache()
64 {
65     // NOTE: Singleton does not expect to be deconstructed.
66     CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), (void*)this,
67                                        kCFLocaleCurrentLocaleDidChangeNotification, NULL);
68 }
69
70 void LocalizedDateCache::localeChanged()
71 {
72     m_maxWidthMap.clear();
73     m_formatterMap.clear();
74 }
75
76 NSDateFormatter *LocalizedDateCache::formatterForDateType(DateComponents::Type type)
77 {
78     int key = static_cast<int>(type);
79     if (m_formatterMap.contains(key))
80         return m_formatterMap.get(key).get();
81
82     NSDateFormatter *dateFormatter = [createFormatterForType(type) autorelease];
83     m_formatterMap.set(key, dateFormatter);
84     return dateFormatter;
85 }
86
87 float LocalizedDateCache::maximumWidthForDateType(DateComponents::Type type, const Font& font, const MeasureTextClient& measurer)
88 {
89     int key = static_cast<int>(type);
90     if (m_font == font) {
91         if (m_maxWidthMap.contains(key))
92             return m_maxWidthMap.get(key);
93     } else {
94         m_font = Font(font);
95         m_maxWidthMap.clear();
96     }
97
98     float calculatedMaximum = calculateMaximumWidth(type, measurer);
99     m_maxWidthMap.set(key, calculatedMaximum);
100     return calculatedMaximum;
101 }
102
103 NSDateFormatter *LocalizedDateCache::createFormatterForType(DateComponents::Type type)
104 {
105     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
106     NSLocale *currentLocale = [NSLocale currentLocale];
107     [dateFormatter setLocale:currentLocale];
108
109     switch (type) {
110     case DateComponents::Invalid:
111         ASSERT_NOT_REACHED();
112         break;
113     case DateComponents::Date:
114         [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
115         [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
116         [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
117         break;
118     case DateComponents::DateTime:
119         [dateFormatter setTimeZone:[NSTimeZone localTimeZone]];
120         [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
121         [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
122         break;
123     case DateComponents::DateTimeLocal:
124         [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
125         [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
126         [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
127         break;
128     case DateComponents::Month:
129         [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
130         [dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"MMMMyyyy" options:0 locale:currentLocale]];
131         break;
132     case DateComponents::Time:
133         [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
134         [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
135         [dateFormatter setDateStyle:NSDateFormatterNoStyle];
136         break;
137     case DateComponents::Week:
138         ASSERT_NOT_REACHED();
139         break;
140     }
141
142     return dateFormatter;
143 }
144
145 // NOTE: This does not check for the widest day of the week.
146 // We assume no formatter option shows that information.
147 float LocalizedDateCache::calculateMaximumWidth(DateComponents::Type type, const MeasureTextClient& measurer)
148 {
149     float maximumWidth = 0;
150
151     // Get the formatter we would use, copy it because we will force its time zone to be UTC.
152     NSDateFormatter *dateFormatter = [[formatterForDateType(type) copy] autorelease];
153     [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
154
155     // Sample date with a 4 digit year and 2 digit day, hour, and minute. Digits are
156     // typically all equally wide. Force UTC timezone for the test date below so the
157     // date doesn't adjust for the current timezone. This is an arbitrary date
158     // (x-27-2007) and time (10:45 PM).
159 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
160     RetainPtr<NSCalendar> gregorian = adoptNS([[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]);
161 #else
162     RetainPtr<NSCalendar> gregorian = adoptNS([[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]);
163 #endif
164     [gregorian.get() setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
165     RetainPtr<NSDateComponents> components = adoptNS([[NSDateComponents alloc] init]);
166     [components.get() setDay:27];
167     [components.get() setYear:2007];
168     [components.get() setHour:22];
169     [components.get() setMinute:45];
170
171     static NSUInteger numberOfGregorianMonths = [[dateFormatter monthSymbols] count];
172     ASSERT(numberOfGregorianMonths == 12);
173
174     // For each month (in the Gregorian Calendar), format a date and measure its length.
175     NSUInteger totalMonthsToTest = 1;
176     if (type == DateComponents::Date
177         || type == DateComponents::DateTime
178         || type == DateComponents::DateTimeLocal
179         || type == DateComponents::Month)
180         totalMonthsToTest = numberOfGregorianMonths;
181     for (NSUInteger i = 0; i < totalMonthsToTest; ++i) {
182         [components.get() setMonth:(i + 1)];
183         NSDate *date = [gregorian.get() dateFromComponents:components.get()];
184         NSString *formattedDate = [dateFormatter stringFromDate:date];
185         maximumWidth = max(maximumWidth, measurer.measureText(String(formattedDate)));
186     }
187
188     return maximumWidth;
189 }
190
191 } // namespace WebCore
192
193 #endif // PLATFORM(IOS)