/* * Copyright (C) 2011 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "LocalizedDateCache.h" #import "DateComponents.h" #import "FontCascade.h" #import #import #import #import #import namespace WebCore { LocalizedDateCache& localizedDateCache() { static NeverDestroyed cache; return cache; } static void _localeChanged(CFNotificationCenterRef, void*, CFStringRef, const void*, CFDictionaryRef) { localizedDateCache().localeChanged(); } LocalizedDateCache::LocalizedDateCache() { // Listen to CF Notifications for locale change, and clear the cache when it does. CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), (void*)this, _localeChanged, kCFLocaleCurrentLocaleDidChangeNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately); } LocalizedDateCache::~LocalizedDateCache() { // NOTE: Singleton does not expect to be deconstructed. CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), (void*)this, kCFLocaleCurrentLocaleDidChangeNotification, NULL); } void LocalizedDateCache::localeChanged() { m_maxWidthMap.clear(); m_formatterMap.clear(); } NSDateFormatter *LocalizedDateCache::formatterForDateType(DateComponentsType type) { int key = static_cast(type); if (m_formatterMap.contains(key)) return m_formatterMap.get(key).get(); auto dateFormatter = createFormatterForType(type); m_formatterMap.set(key, dateFormatter.get()); return dateFormatter.autorelease(); } float LocalizedDateCache::maximumWidthForDateType(DateComponentsType type, const FontCascade& font, const MeasureTextClient& measurer) { int key = static_cast(type); if (m_font == font) { if (m_maxWidthMap.contains(key)) return m_maxWidthMap.get(key); } else { m_font = FontCascade(font); m_maxWidthMap.clear(); } float calculatedMaximum = calculateMaximumWidth(type, measurer); m_maxWidthMap.set(key, calculatedMaximum); return calculatedMaximum; } RetainPtr LocalizedDateCache::createFormatterForType(DateComponentsType type) { auto dateFormatter = adoptNS([[NSDateFormatter alloc] init]); NSLocale *currentLocale = [NSLocale currentLocale]; [dateFormatter setLocale:currentLocale]; switch (type) { case DateComponentsType::Invalid: ASSERT_NOT_REACHED(); break; case DateComponentsType::Date: [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; break; case DateComponentsType::DateTimeLocal: [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; break; case DateComponentsType::Month: [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"MMMMyyyy" options:0 locale:currentLocale]]; break; case DateComponentsType::Time: [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [dateFormatter setDateStyle:NSDateFormatterNoStyle]; break; case DateComponentsType::Week: ASSERT_NOT_REACHED(); break; } return dateFormatter; } // NOTE: This does not check for the widest day of the week. // We assume no formatter option shows that information. float LocalizedDateCache::calculateMaximumWidth(DateComponentsType type, const MeasureTextClient& measurer) { float maximumWidth = 0; // Get the formatter we would use, copy it because we will force its time zone to be UTC. auto dateFormatter = adoptNS([formatterForDateType(type) copy]); [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; // Sample date with a 4 digit year and 2 digit day, hour, and minute. Digits are // typically all equally wide. Force UTC timezone for the test date below so the // date doesn't adjust for the current timezone. This is an arbitrary date // (x-27-2007) and time (10:45 PM). RetainPtr gregorian = adoptNS([[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]); [gregorian setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; RetainPtr components = adoptNS([[NSDateComponents alloc] init]); [components setDay:27]; [components setYear:2007]; [components setHour:22]; [components setMinute:45]; static const NSUInteger numberOfGregorianMonths = [[dateFormatter monthSymbols] count]; ASSERT(numberOfGregorianMonths == 12); // For each month (in the Gregorian Calendar), format a date and measure its length. NSUInteger totalMonthsToTest = 1; if (type == DateComponentsType::Date || type == DateComponentsType::DateTimeLocal || type == DateComponentsType::Month) totalMonthsToTest = numberOfGregorianMonths; for (NSUInteger i = 0; i < totalMonthsToTest; ++i) { [components setMonth:(i + 1)]; NSDate *date = [gregorian dateFromComponents:components.get()]; NSString *formattedDate = [dateFormatter stringFromDate:date]; maximumWidth = std::max(maximumWidth, measurer.measureText(String(formattedDate))); } return maximumWidth; } } // namespace WebCore