| /* |
| * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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 "TestFontOptions.h" |
| |
| #if PLATFORM(MAC) |
| |
| #import "AppKitSPI.h" |
| #import "ClassMethodSwizzler.h" |
| #import <objc/runtime.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/SetForScope.h> |
| |
| static TestFontOptions *sharedFontOptionsForTesting() |
| { |
| return TestFontOptions.sharedInstance; |
| } |
| |
| @interface TestFontOptions () |
| - (instancetype)initWithFontOptions:(NSFontOptions *)fontOptions; |
| @end |
| |
| @implementation TestFontOptions { |
| RetainPtr<NSFontOptions> _fontOptions; |
| CGSize _shadowOffset; |
| CGFloat _shadowBlurRadius; |
| BOOL _hasShadow; |
| BOOL _hasPendingShadowChanges; |
| |
| RetainPtr<NSColor> _foregroundColor; |
| RetainPtr<NSColor> _backgroundColor; |
| BOOL _hasPendingColorChanges; |
| |
| std::unique_ptr<ClassMethodSwizzler> _replaceFontOptionsSwizzler; |
| RetainPtr<NSDictionary> _selectedAttributes; |
| BOOL _hasMultipleFonts; |
| } |
| |
| @synthesize hasShadow=_hasShadow; |
| @synthesize shadowBlurRadius=_shadowBlurRadius; |
| @synthesize hasMultipleFonts=_hasMultipleFonts; |
| |
| + (instancetype)sharedInstance |
| { |
| static TestFontOptions *sharedInstance; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| NSFontOptions *sharedFontOptions = [NSClassFromString(@"NSFontOptions") sharedFontOptions]; |
| sharedInstance = [[TestFontOptions alloc] initWithFontOptions:sharedFontOptions]; |
| }); |
| return sharedInstance; |
| } |
| |
| - (instancetype)initWithFontOptions:(NSFontOptions *)fontOptions |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _fontOptions = fontOptions; |
| ASSERT(_fontOptions); |
| |
| _shadowOffset = CGSizeZero; |
| _shadowBlurRadius = 0; |
| _replaceFontOptionsSwizzler = makeUnique<ClassMethodSwizzler>(NSClassFromString(@"NSFontOptions"), @selector(sharedFontOptions), reinterpret_cast<IMP>(sharedFontOptionsForTesting)); |
| _hasPendingShadowChanges = NO; |
| _hasMultipleFonts = NO; |
| |
| return self; |
| } |
| |
| - (NSDictionary *)selectedAttributes |
| { |
| return _selectedAttributes.get(); |
| } |
| |
| - (NSFontOptions *)fontOptions |
| { |
| return _fontOptions.get(); |
| } |
| |
| - (CGFloat)shadowWidth |
| { |
| return _shadowOffset.width; |
| } |
| |
| - (void)setShadowWidth:(CGFloat)shadowWidth |
| { |
| if (_shadowOffset.width == shadowWidth) |
| return; |
| |
| SetForScope<BOOL> hasPendingFontShadowChanges(_hasPendingShadowChanges, YES); |
| _shadowOffset.width = shadowWidth; |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (CGFloat)shadowHeight |
| { |
| return _shadowOffset.height; |
| } |
| |
| - (void)setShadowHeight:(CGFloat)shadowHeight |
| { |
| if (_shadowOffset.height == shadowHeight) |
| return; |
| |
| SetForScope<BOOL> hasPendingFontShadowChanges(_hasPendingShadowChanges, YES); |
| _shadowOffset.height = shadowHeight; |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (void)setShadowBlurRadius:(CGFloat)shadowBlurRadius |
| { |
| if (_shadowBlurRadius == shadowBlurRadius) |
| return; |
| |
| SetForScope<BOOL> hasPendingFontShadowChanges(_hasPendingShadowChanges, YES); |
| _shadowBlurRadius = shadowBlurRadius; |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (void)setHasShadow:(BOOL)hasShadow |
| { |
| if (_hasShadow == hasShadow) |
| return; |
| |
| SetForScope<BOOL> hasPendingFontShadowChanges(_hasPendingShadowChanges, YES); |
| _hasShadow = hasShadow; |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (NSColor *)foregroundColor |
| { |
| return _foregroundColor.get(); |
| } |
| |
| - (void)setForegroundColor:(NSColor *)color |
| { |
| SetForScope<BOOL> hasPendingColorChanges(_hasPendingColorChanges, YES); |
| _foregroundColor = adoptNS([color copy]); |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (NSColor *)backgroundColor |
| { |
| return _backgroundColor.get(); |
| } |
| |
| - (void)setBackgroundColor:(NSColor *)color |
| { |
| SetForScope<BOOL> hasPendingColorChanges(_hasPendingColorChanges, YES); |
| _backgroundColor = adoptNS([color copy]); |
| [self _dispatchFontAttributeChanges]; |
| } |
| |
| - (void)_dispatchFontAttributeChanges |
| { |
| [NSFontManager.sharedFontManager.target performSelector:@selector(changeAttributes:) withObject:self]; |
| } |
| |
| - (NSDictionary *)convertAttributes:(NSDictionary *)attributes |
| { |
| auto convertedAttributes = adoptNS([attributes mutableCopy]); |
| |
| if (_hasPendingShadowChanges) { |
| if (_hasShadow) { |
| auto shadow = adoptNS([[NSShadow alloc] init]); |
| [shadow setShadowBlurRadius:_shadowBlurRadius]; |
| [shadow setShadowOffset:_shadowOffset]; |
| [convertedAttributes setObject:shadow.get() forKey:NSShadowAttributeName]; |
| } else |
| [convertedAttributes removeObjectForKey:NSShadowAttributeName]; |
| } |
| |
| if (_hasPendingColorChanges) { |
| if (_foregroundColor) |
| [convertedAttributes setObject:_foregroundColor.get() forKey:NSForegroundColorAttributeName]; |
| else |
| [convertedAttributes removeObjectForKey:NSForegroundColorAttributeName]; |
| |
| if (_backgroundColor) |
| [convertedAttributes setObject:_backgroundColor.get() forKey:NSBackgroundColorAttributeName]; |
| else |
| [convertedAttributes removeObjectForKey:NSBackgroundColorAttributeName]; |
| } |
| |
| return convertedAttributes.autorelease(); |
| } |
| |
| - (void)setSelectedAttributes:(NSDictionary *)attributes isMultiple:(BOOL)multiple |
| { |
| _hasMultipleFonts = multiple; |
| _selectedAttributes = attributes; |
| } |
| |
| - (void)forwardInvocation:(NSInvocation *)invocation |
| { |
| if ([_fontOptions respondsToSelector:invocation.selector]) { |
| [invocation invokeWithTarget:_fontOptions.get()]; |
| return; |
| } |
| |
| [super forwardInvocation:invocation]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |