http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm new file mode 100644 index 0000000..c951068 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm @@ -0,0 +1,1170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#import "WXTextComponent.h" +#import "WXSDKInstance_private.h" +#import "WXComponent_internal.h" +#import "WXLayer.h" +#import "WXUtility.h" +#import "WXConvert.h" +#import "WXRuleManager.h" +#import "WXDefine.h" +#import "WXView.h" +#import "WXComponent+Layout.h" +#import <pthread/pthread.h> +#import <CoreText/CoreText.h> +#import "WXComponent+Layout.h" + +// WXText is a non-public is not permitted +@interface WXTextView : WXView +@property (nonatomic, strong) NSTextStorage *textStorage; +@end + +@implementation WXTextView + +- (instancetype)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + self.accessibilityTraits |= UIAccessibilityTraitStaticText; + + self.opaque = NO; + self.contentMode = UIViewContentModeRedraw; + self.textStorage = [NSTextStorage new]; + } + return self; +} + ++ (Class)layerClass +{ + return [WXLayer class]; +} + +- (void)copy:(id)sender +{ + [[UIPasteboard generalPasteboard] setString:((WXTextComponent*)self.wx_component).text]; +} + +- (void)setTextStorage:(NSTextStorage *)textStorage +{ + if (_textStorage != textStorage) { + _textStorage = textStorage; + [self.wx_component setNeedsDisplay]; + } +} + +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + if (action == @selector(copy:)) { + return [[self.wx_component valueForKey:@"_enableCopy"] boolValue]; + } + return [super canPerformAction:action withSender:sender]; +} + +- (NSString *)description +{ + NSString *superDescription = super.description; + NSRange semicolonRange = [superDescription rangeOfString:@";"]; + NSString * content = _textStorage.string; + if ([(WXTextComponent*)self.wx_component useCoreText]) { + content = ((WXTextComponent*)self.wx_component).text; + } + NSString *replacement = [NSString stringWithFormat:@"; text: %@; frame:%f,%f,%f,%f", content, self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height]; + return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement]; +} + +- (NSString *)accessibilityValue +{ + if (self.wx_component && self.wx_component->_ariaLabel) { + return [super accessibilityValue]; + } + if (![(WXTextComponent*)self.wx_component useCoreText]) { + return _textStorage.string; + } + return ((WXTextComponent*)self.wx_component).text; +} + +- (NSString *)accessibilityLabel +{ + if (self.wx_component) { + if (self.wx_component->_ariaLabel) { + return self.wx_component->_ariaLabel; + } + } + return [super accessibilityLabel]; +} + +@end + +static BOOL textRenderUsingCoreText = YES; + +NSString *const WXTextTruncationToken = @"\u2026"; +CGFloat WXTextDefaultLineThroughWidth = 1.2; + +@interface WXTextComponent() +@property (nonatomic, strong) NSString *useCoreTextAttr; +@end + +@implementation WXTextComponent +{ + UIEdgeInsets _border; + UIEdgeInsets _padding; + NSTextStorage *_textStorage; + CGFloat _textStorageWidth; + + UIColor *_color; + NSString *_fontFamily; + CGFloat _fontSize; + CGFloat _fontWeight; + WXTextStyle _fontStyle; + NSUInteger _lines; + NSTextAlignment _textAlign; + NSString *_direction; + WXTextDecoration _textDecoration; + NSString *_textOverflow; + CGFloat _lineHeight; + CGFloat _letterSpacing; + BOOL _truncationLine; // support trunk tail + + NSAttributedString * _ctAttributedString; + NSString *_wordWrap; + + pthread_mutex_t _ctAttributedStringMutex; + pthread_mutexattr_t _propertMutexAttr; + BOOL _observerIconfont; + BOOL _enableCopy; +} + ++ (void)setRenderUsingCoreText:(BOOL)usingCoreText +{ + textRenderUsingCoreText = usingCoreText; +} + ++ (BOOL)textRenderUsingCoreText +{ + return textRenderUsingCoreText; +} + +- (instancetype)initWithRef:(NSString *)ref + type:(NSString *)type + styles:(NSDictionary *)styles + attributes:(NSDictionary *)attributes + events:(NSArray *)events + weexInstance:(WXSDKInstance *)weexInstance +{ + self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; + if (self) { + // just for coretext and textkit render replacement + pthread_mutexattr_init(&(_propertMutexAttr)); + pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&(_ctAttributedStringMutex), &(_propertMutexAttr)); + + if ([attributes objectForKey:@"coretext"]) { + _useCoreTextAttr = [WXConvert NSString:attributes[@"coretext"]]; + } else { + _useCoreTextAttr = nil; + } + + [self fillCSSStyles:styles]; + [self fillAttributes:attributes]; + + } + return self; +} + +- (BOOL)useCoreText +{ + if ([_useCoreTextAttr isEqualToString:@"true"]) { + return YES; + } + if ([_useCoreTextAttr isEqualToString:@"false"]) { + return NO; + } + + if ([WXTextComponent textRenderUsingCoreText]) { + return YES; + } + return NO; +} + +- (void)dealloc +{ + if (_fontFamily && _observerIconfont) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil]; + } + pthread_mutex_destroy(&_ctAttributedStringMutex); + pthread_mutexattr_destroy(&_propertMutexAttr); +} + +#define WX_STYLE_FILL_TEXT(key, prop, type, needLayout)\ +do {\ + id value = styles[@#key];\ + if (value) {\ + _##prop = [WXConvert type:value];\ + [self setNeedsRepaint];\ + if (needLayout) {\ + [self setNeedsLayout];\ + }\ + }\ +} while(0); + +#define WX_STYLE_FILL_TEXT_WITH_DEFAULT_VALUE(key, prop, type, defaultValue,needLayout)\ +do {\ + id value = styles[@#key];\ + if (value) {\ + if([WXUtility isBlankString:value]){\ + _##prop = defaultValue;\ + }else {\ + _##prop = [WXConvert type:value];\ + }\ + [self setNeedsRepaint];\ + if (needLayout) {\ + [self setNeedsLayout];\ + }\ + }\ +} while(0); + + +#define WX_STYLE_FILL_TEXT_PIXEL(key, prop, needLayout)\ +do {\ + id value = styles[@#key];\ + if (value) {\ + _##prop = [WXConvert WXPixelType:value scaleFactor:self.weexInstance.pixelScaleFactor];\ + [self setNeedsRepaint];\ + if (needLayout) {\ + [self setNeedsLayout];\ + }\ +}\ +} while(0); + +- (void)fillCSSStyles:(NSDictionary *)styles +{ + WX_STYLE_FILL_TEXT_WITH_DEFAULT_VALUE(color, color, UIColor, [UIColor blackColor], NO) + WX_STYLE_FILL_TEXT(fontFamily, fontFamily, NSString, YES) + WX_STYLE_FILL_TEXT_PIXEL(fontSize, fontSize, YES) + WX_STYLE_FILL_TEXT(fontWeight, fontWeight, WXTextWeight, YES) + WX_STYLE_FILL_TEXT(fontStyle, fontStyle, WXTextStyle, YES) + WX_STYLE_FILL_TEXT(lines, lines, NSUInteger, YES) + WX_STYLE_FILL_TEXT(textAlign, textAlign, NSTextAlignment, NO) + WX_STYLE_FILL_TEXT(textDecoration, textDecoration, WXTextDecoration, YES) + WX_STYLE_FILL_TEXT(textOverflow, textOverflow, NSString, NO) + WX_STYLE_FILL_TEXT_PIXEL(lineHeight, lineHeight, YES) + WX_STYLE_FILL_TEXT_PIXEL(letterSpacing, letterSpacing, YES) + WX_STYLE_FILL_TEXT(wordWrap, wordWrap, NSString, YES); + WX_STYLE_FILL_TEXT(direction, direction, NSString, YES) + if (_fontFamily && !_observerIconfont) { + // notification received when custom icon font file download finish + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repaintText:) name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil]; + _observerIconfont = YES; + } + +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + UIEdgeInsets padding = { + WXFloorPixelValue(self.cssNode->style.padding[CSS_TOP] + self.cssNode->style.border[CSS_TOP]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_LEFT] + self.cssNode->style.border[CSS_LEFT]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_BOTTOM] + self.cssNode->style.border[CSS_BOTTOM]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_RIGHT] + self.cssNode->style.border[CSS_RIGHT]) + }; + + if (!UIEdgeInsetsEqualToEdgeInsets(padding, _padding)) { + _padding = padding; + [self setNeedsRepaint]; + } + } + +//#else + else + { + UIEdgeInsets flex_padding = { + WXFloorPixelValue(self.flexCssNode->getPaddingTop()+ self.flexCssNode->getBorderWidthTop()), + WXFloorPixelValue(self.flexCssNode->getPaddingLeft() + self.flexCssNode->getBorderWidthLeft()), + WXFloorPixelValue(self.flexCssNode->getPaddingBottom() + self.flexCssNode->getBorderWidthBottom()), + WXFloorPixelValue(self.flexCssNode->getPaddingRight() + self.flexCssNode->getBorderWidthRight()) + }; + + if (!UIEdgeInsetsEqualToEdgeInsets(flex_padding, _padding)) { + _padding = flex_padding; + [self setNeedsRepaint]; + } + } +//#endif + +} + +- (void)fillAttributes:(NSDictionary *)attributes +{ + id text = [WXConvert NSString:attributes[@"value"]]; + if (text && ![self.text isEqualToString:text]) { + self.text = text; + [self setNeedsRepaint]; + [self setNeedsLayout]; + } + if (attributes[@"enableCopy"]) { + _enableCopy = [WXConvert BOOL:attributes[@"enableCopy"]]; + } +} + +- (void)setNeedsRepaint +{ + _textStorage = nil; + + pthread_mutex_lock(&(_ctAttributedStringMutex)); + _ctAttributedString = nil; + pthread_mutex_unlock(&(_ctAttributedStringMutex)); + +} + +#pragma mark - Subclass + +- (void)setNeedsLayout +{ + [super setNeedsLayout]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + BOOL useCoreText = NO; + if ([self.view.wx_component isKindOfClass:NSClassFromString(@"WXTextComponent")] && [self.view.wx_component respondsToSelector:@selector(useCoreText)]) { + useCoreText = [(WXTextComponent*)self.view.wx_component useCoreText]; + } + if (!useCoreText) { + ((WXTextView *)self.view).textStorage = _textStorage; + } + if (_enableCopy) { + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(displayMenuController:)]; + [self.view addGestureRecognizer:longPress]; + } + self.view.isAccessibilityElement = YES; + + [self setNeedsDisplay]; +} + +- (void)displayMenuController:(id)sender +{ + if ([self.view becomeFirstResponder] && ((UILongPressGestureRecognizer*)sender).state == UIGestureRecognizerStateBegan) { + UIMenuController *theMenu = [UIMenuController sharedMenuController]; + CGSize size = [self ctAttributedString].size; + CGRect selectionRect = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, size.width, size.height); + [theMenu setTargetRect:selectionRect inView:self.view.superview]; + [theMenu setMenuVisible:YES animated:YES]; + } +} + +- (UIView *)loadView +{ + return [[WXTextView alloc] init]; +} + +- (BOOL)needsDrawRect +{ + return YES; +} + +- (UIImage *)drawRect:(CGRect)rect; +{ + CGContextRef context = UIGraphicsGetCurrentContext(); + if (_isCompositingChild) { + [self drawTextWithContext:context bounds:rect padding:_padding view:nil]; + } else { + WXTextView *textView = (WXTextView *)_view; + [self drawTextWithContext:context bounds:rect padding:_padding view:textView]; + } + + return nil; +} + +- (CGSize (^)(CGSize))measureBlock +{ + __weak typeof(self) weakSelf = self; + return ^CGSize (CGSize constrainedSize) { +#ifdef DEBUG + WXLogDebug(@"flexLayout -> measureblock %@, constrainedSize:%@", + self.type, + NSStringFromCGSize(constrainedSize) + ); +#endif + CGSize computedSize = CGSizeZero; + NSTextStorage *textStorage = nil; + + //TODO:more elegant way to use max and min constrained size +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + if (!isnan(weakSelf.cssNode->style.minDimensions[CSS_WIDTH])) { + constrainedSize.width = MAX(constrainedSize.width, weakSelf.cssNode->style.minDimensions[CSS_WIDTH]); + } + + if (!isnan(weakSelf.cssNode->style.maxDimensions[CSS_WIDTH])) { + constrainedSize.width = MIN(constrainedSize.width, weakSelf.cssNode->style.maxDimensions[CSS_WIDTH]); + } + } +//#else + else + { + if (!isnan(weakSelf.flexCssNode->getMinWidth())) { + constrainedSize.width = MAX(constrainedSize.width, weakSelf.flexCssNode->getMinWidth()); + } + + if (!isnan(weakSelf.flexCssNode->getMaxWidth())) { + constrainedSize.width = MIN(constrainedSize.width, weakSelf.flexCssNode->getMaxWidth()); + } + } +//#endif + + if (![self useCoreText]) { + textStorage = [weakSelf textStorageWithWidth:constrainedSize.width]; + NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; + NSTextContainer *textContainer = layoutManager.textContainers.firstObject; + computedSize = [layoutManager usedRectForTextContainer:textContainer].size; + } else { + computedSize = [weakSelf calculateTextHeightWithWidth:constrainedSize.width]; + } +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + //TODO:more elegant way to use max and min constrained size + if (!isnan(weakSelf.cssNode->style.minDimensions[CSS_WIDTH])) { + computedSize.width = MAX(computedSize.width, weakSelf.cssNode->style.minDimensions[CSS_WIDTH]); + } + + if (!isnan(weakSelf.cssNode->style.maxDimensions[CSS_WIDTH])) { + computedSize.width = MIN(computedSize.width, weakSelf.cssNode->style.maxDimensions[CSS_WIDTH]); + } + + if (!isnan(weakSelf.cssNode->style.minDimensions[CSS_HEIGHT])) { + computedSize.height = MAX(computedSize.height, weakSelf.cssNode->style.minDimensions[CSS_HEIGHT]); + } + + if (!isnan(weakSelf.cssNode->style.maxDimensions[CSS_HEIGHT])) { + computedSize.height = MIN(computedSize.height, weakSelf.cssNode->style.maxDimensions[CSS_HEIGHT]); + } + } + +//#else + else + { + if (!isnan(weakSelf.flexCssNode->getMinWidth())) { + computedSize.width = MAX(computedSize.width, weakSelf.flexCssNode->getMinWidth()); + } + + if (!isnan(weakSelf.flexCssNode->getMaxWidth())) { + computedSize.width = MIN(computedSize.width, weakSelf.flexCssNode->getMaxWidth()); + } + + if (!isnan(weakSelf.flexCssNode->getMinHeight())) { + computedSize.height = MAX(computedSize.height, weakSelf.flexCssNode->getMinHeight()); + } + + if (!isnan(weakSelf.flexCssNode->getMaxHeight())) { + computedSize.height = MIN(computedSize.height, weakSelf.flexCssNode->getMaxHeight()); + } + } + +//#endif + if (textStorage && [WXUtility isBlankString:textStorage.string]) { + // if the text value is empty or nil, then set the height is 0. + computedSize.height = 0; + } + + return (CGSize) { + WXCeilPixelValue(computedSize.width), + WXCeilPixelValue(computedSize.height) + }; + }; +} + +#pragma mark Text Building + +- (NSAttributedString *)ctAttributedString +{ + if (!self.text) { + return nil; + } + NSAttributedString * attributedString = nil; + pthread_mutex_lock(&(_ctAttributedStringMutex)); + if (!_ctAttributedString) { + _ctAttributedString = [self buildCTAttributeString]; + WXPerformBlockOnComponentThread(^{ + [self.weexInstance.componentManager startComponentTasks]; + }); + } + attributedString = [_ctAttributedString copy]; + pthread_mutex_unlock(&(_ctAttributedStringMutex)); + return attributedString; +} + +- (void)repaintText:(NSNotification *)notification +{ + if (![_fontFamily isEqualToString:notification.userInfo[@"fontFamily"]]) { + return; + } + [self setNeedsRepaint]; + WXPerformBlockOnComponentThread(^{ + [self.weexInstance.componentManager startComponentTasks]; + WXPerformBlockOnMainThread(^{ + [self setNeedsLayout]; + [self setNeedsDisplay]; + }); + }); +} + +- (NSMutableAttributedString *)buildCTAttributeString +{ + NSString * string = self.text; + if (![string isKindOfClass:[NSString class]]) { + WXLogError(@"text %@ is invalid", self.text); + string = @""; + } + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString: string]; + if (_color) { + [attributedString addAttribute:NSForegroundColorAttributeName value:_color range:NSMakeRange(0, string.length)]; + } + + // set font + UIFont *font = [WXUtility fontWithSize:_fontSize textWeight:_fontWeight textStyle:_fontStyle fontFamily:_fontFamily scaleFactor:self.weexInstance.pixelScaleFactor useCoreText:[self useCoreText]]; + CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, + font.pointSize, + NULL); + if (ctFont) { + [attributedString addAttribute:(id)kCTFontAttributeName value:(__bridge id)(ctFont) range:NSMakeRange(0, string.length)]; + CFRelease(ctFont); + } + + if(_textDecoration == WXTextDecorationUnderline){ + [attributedString addAttribute:(id)kCTUnderlineStyleAttributeName value:@(kCTUnderlinePatternSolid | kCTUnderlineStyleSingle) range:NSMakeRange(0, string.length)]; + } else if(_textDecoration == WXTextDecorationLineThrough){ + [attributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlinePatternSolid | NSUnderlineStyleSingle) range:NSMakeRange(0, string.length)]; + } + + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + + // handle text direction style, default ltr + BOOL isRtl = false; + if ([WXComponent isUseFlex]) { + isRtl = [_direction isEqualToString:@"rtl"]; + }else{ + isRtl = _cssNode->layout.direction == CSS_DIRECTION_RTL; + } + if (isRtl) { + if (0 == _textAlign) { + //force text right-align if don't specified any align. + _textAlign = NSTextAlignmentRight; + } + paragraphStyle.baseWritingDirection = NSWritingDirectionRightToLeft; + } else { + //if you specify NSWritingDirectionNaturalDirection, the receiver resolves the writing + //directionto eitherNSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, + //depending on the direction for the userâs language preference setting. + paragraphStyle.baseWritingDirection = NSWritingDirectionNatural; + } + + if (_textAlign) { + paragraphStyle.alignment = _textAlign; + } + + if ([[_wordWrap lowercaseString] isEqualToString:@"break-word"]) { + paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; + } else if ([[_wordWrap lowercaseString] isEqualToString:@"normal"]){ + paragraphStyle.lineBreakMode = NSLineBreakByClipping; + } else { + // set default lineBreakMode + paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping; + } + _truncationLine = NO; + if (_textOverflow && [_textOverflow length] > 0) { + if (_lines && [_textOverflow isEqualToString:@"ellipsis"]) + _truncationLine = YES; + } + + if (_lineHeight) { + paragraphStyle.maximumLineHeight = _lineHeight; + paragraphStyle.minimumLineHeight = _lineHeight; + } + if (_lineHeight || _textAlign || [_textOverflow length] > 0) { + [attributedString addAttribute:NSParagraphStyleAttributeName + value:paragraphStyle + range:(NSRange){0, attributedString.length}]; + } + + if (_letterSpacing) { + [attributedString addAttribute:NSKernAttributeName value:@(_letterSpacing) range:(NSRange){0, attributedString.length}]; + } + + if ([self adjustLineHeight]) { + if (_lineHeight > font.lineHeight) { + [attributedString addAttribute:NSBaselineOffsetAttributeName + value:@((_lineHeight - font.lineHeight)/ 2) + range:(NSRange){0, attributedString.length}]; + } + } + + return attributedString; +} + +- (NSAttributedString *)buildAttributeString +{ + NSString *string = self.text ?: @""; + + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + + // set textColor + if(_color) { + [attributedString addAttribute:NSForegroundColorAttributeName value:_color range:NSMakeRange(0, string.length)]; + } + + // set font + UIFont *font = [WXUtility fontWithSize:_fontSize textWeight:_fontWeight textStyle:_fontStyle fontFamily:_fontFamily scaleFactor:self.weexInstance.pixelScaleFactor]; + if (font) { + [attributedString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, string.length)]; + } + + if(_textDecoration == WXTextDecorationUnderline){ + [attributedString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlinePatternSolid | NSUnderlineStyleSingle) range:NSMakeRange(0, string.length)]; + } else if(_textDecoration == WXTextDecorationLineThrough){ + [attributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlinePatternSolid | NSUnderlineStyleSingle) range:NSMakeRange(0, string.length)]; + } + + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + + // handle text direction style, default ltr + BOOL isRtl = false; + if ([WXComponent isUseFlex]) { + isRtl = [_direction isEqualToString:@"rtl"]; + }else{ + isRtl = _cssNode->layout.direction == CSS_DIRECTION_RTL; + } + if (isRtl) { + if (0 == _textAlign) { + //force text right-align if don't specified any align. + _textAlign = NSTextAlignmentRight; + } + paragraphStyle.baseWritingDirection = NSWritingDirectionRightToLeft; + } else { + //if you specify NSWritingDirectionNaturalDirection, the receiver resolves the writing + //directionto eitherNSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, + //depending on the direction for the userâs language preference setting. + paragraphStyle.baseWritingDirection = NSWritingDirectionNatural; + } + + if (_textAlign) { + paragraphStyle.alignment = _textAlign; + } + + if (_lineHeight) { + paragraphStyle.maximumLineHeight = _lineHeight; + paragraphStyle.minimumLineHeight = _lineHeight; + } + + if (_lineHeight || _textAlign) { + [attributedString addAttribute:NSParagraphStyleAttributeName + value:paragraphStyle + range:(NSRange){0, attributedString.length}]; + } + if ([self adjustLineHeight]) { + if (_lineHeight > font.lineHeight) { + [attributedString addAttribute:NSBaselineOffsetAttributeName + value:@((_lineHeight - font.lineHeight)/ 2) + range:(NSRange){0, attributedString.length}]; + } + } + + return attributedString; +} + +- (BOOL)adjustLineHeight +{ + if (WX_SYS_VERSION_LESS_THAN(@"10.0")) { + return true; + } + return ![self useCoreText]; +} + +- (NSTextStorage *)textStorageWithWidth:(CGFloat)width +{ + if (_textStorage && width == _textStorageWidth) { + return _textStorage; + } + + NSLayoutManager *layoutManager = [NSLayoutManager new]; + + // build AttributeString + NSAttributedString *attributedString = [self buildAttributeString]; + + NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; + [textStorage addLayoutManager:layoutManager]; + + NSTextContainer *textContainer = [NSTextContainer new]; + textContainer.lineFragmentPadding = 0.0; + + if ([[_wordWrap lowercaseString] isEqualToString:@"break-word"]) { + textContainer.lineBreakMode = NSLineBreakByWordWrapping; + } else if ([[_wordWrap lowercaseString] isEqualToString:@"normal"]){ + textContainer.lineBreakMode = NSLineBreakByClipping; + } else { + // set default lineBreakMode + textContainer.lineBreakMode = NSLineBreakByCharWrapping; + } + + if (_textOverflow && [_textOverflow length] > 0) { + if ([_textOverflow isEqualToString:@"ellipsis"]) + textContainer.lineBreakMode = NSLineBreakByTruncatingTail; + } + textContainer.maximumNumberOfLines = _lines > 0 ? _lines : 0; + textContainer.size = (CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX}; + + [layoutManager addTextContainer:textContainer]; + [layoutManager ensureLayoutForTextContainer:textContainer]; + + _textStorageWidth = width; + _textStorage = textStorage; + + return textStorage; +} + +- (void)syncTextStorageForView +{ + CGFloat width = self.calculatedFrame.size.width - (_padding.left + _padding.right); + NSTextStorage *textStorage = nil; + if (![self useCoreText]) { + textStorage = [self textStorageWithWidth:width]; + } + [self.weexInstance.componentManager _addUITask:^{ + if ([self isViewLoaded]) { + if (![self useCoreText]) { + ((WXTextView *)self.view).textStorage = textStorage; + } + [self readyToRender]; // notify super component + [self setNeedsDisplay]; + } + }]; +} + +- (void)_frameDidCalculated:(BOOL)isChanged +{ + [super _frameDidCalculated:isChanged]; + [self syncTextStorageForView]; +} + +- (void)_updateStylesOnComponentThread:(NSDictionary *)styles resetStyles:(NSMutableArray *)resetStyles isUpdateStyles:(BOOL)isUpdateStyles +{ + [super _updateStylesOnComponentThread:styles resetStyles:(NSMutableArray *)resetStyles isUpdateStyles:isUpdateStyles]; + NSMutableDictionary * newStyles = [styles mutableCopy]; + for (NSString * key in [resetStyles copy]) { + [newStyles setObject:@"" forKey:key]; + } + [self fillCSSStyles:newStyles]; + + [self syncTextStorageForView]; +} + +- (void)_updateAttributesOnComponentThread:(NSDictionary *)attributes +{ + [super _updateAttributesOnComponentThread:attributes]; + + [self fillAttributes:attributes]; + + [self syncTextStorageForView]; +} + +- (void)drawTextWithContext:(CGContextRef)context bounds:(CGRect)bounds padding:(UIEdgeInsets)padding view:(WXTextView *)view +{ + if (bounds.size.width <= 0 || bounds.size.height <= 0) { + return; + } + + if ([self _needsDrawBorder]) { + [self _drawBorderWithContext:context size:bounds.size]; + } else { + WXPerformBlockOnMainThread(^{ + [self _resetNativeBorderRadius]; + }); + } + if (![self useCoreText]) { + NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject; + NSTextContainer *textContainer = layoutManager.textContainers.firstObject; + + CGRect textFrame = UIEdgeInsetsInsetRect(bounds, padding); + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin]; + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin]; + } else { + CGRect textFrame = UIEdgeInsetsInsetRect(bounds, padding); + // sufficient height for text to draw, or frame lines will be empty + textFrame.size.height = bounds.size.height * 2; + CGContextSaveGState(context); + //flip the coordinate system + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + CGContextTranslateCTM(context, 0, textFrame.size.height); + CGContextScaleCTM(context, 1.0, -1.0); + + NSAttributedString * attributedStringCopy = [self ctAttributedString]; + //add path + CGPathRef cgPath = NULL; + cgPath = CGPathCreateWithRect(textFrame, NULL); + CTFrameRef _coreTextFrameRef = NULL; + if (_coreTextFrameRef) { + CFRelease(_coreTextFrameRef); + _coreTextFrameRef = NULL; + } + if(!attributedStringCopy) { + return; + } + CTFramesetterRef ctframesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedStringCopy)); + _coreTextFrameRef = CTFramesetterCreateFrame(ctframesetterRef, CFRangeMake(0, attributedStringCopy.length), cgPath, NULL); + CFArrayRef ctLines = NULL; + if (NULL == _coreTextFrameRef) { + // try to protect crash from frame is NULL + return; + } + CFRelease(ctframesetterRef); + ctframesetterRef = NULL; + ctLines = CTFrameGetLines(_coreTextFrameRef); + CFIndex lineCount = CFArrayGetCount(ctLines); + NSMutableArray * mutableLines = [NSMutableArray new]; + CGPoint lineOrigins[lineCount]; + NSUInteger rowCount = 0; + BOOL needTruncation = NO; + CTLineRef ctTruncatedLine = NULL; + CTFrameGetLineOrigins(_coreTextFrameRef, CFRangeMake(0, 0), lineOrigins); + for (CFIndex lineIndex = 0;(!_lines || _lines > lineIndex) && lineIndex < lineCount; lineIndex ++) { + CTLineRef lineRef = NULL; + lineRef = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex); + if (!lineRef) { + break; + } + CGPoint lineOrigin = lineOrigins[lineIndex]; + lineOrigin.x += padding.left; + lineOrigin.y -= padding.top; + CFArrayRef runs = CTLineGetGlyphRuns(lineRef); + [mutableLines addObject:(__bridge id _Nonnull)(lineRef)]; + // lineIndex base 0 + rowCount = lineIndex + 1; + if (_lines > 0 && _truncationLine) { + if (_truncationLine && rowCount > _lines) { + needTruncation = YES; + do { + NSUInteger lastRow = [mutableLines count]; + if (lastRow < rowCount) { + break; + } + [mutableLines removeLastObject]; + } while (1); + + } + } + if (_lines > 0 && _truncationLine) { + if (rowCount >= _lines &&!needTruncation && (CTLineGetStringRange(lineRef).length + CTLineGetStringRange(lineRef).location) < attributedStringCopy.length) { + needTruncation = YES; + } + } + + if (needTruncation) { + CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y); + ctTruncatedLine = [self buildTruncatedLineWithRuns:runs lines:mutableLines path:cgPath]; + if (ctTruncatedLine) { + CFArrayRef truncatedRuns = CTLineGetGlyphRuns(ctTruncatedLine); + [self drawTextWithRuns:truncatedRuns context:context lineOrigin:lineOrigin]; + CFRelease(ctTruncatedLine); + ctTruncatedLine = NULL; + continue; + } + }else { + [self drawTextWithRuns:runs context:context lineOrigin:lineOrigin]; + } + } + + [mutableLines removeAllObjects]; + CGPathRelease(cgPath); + CFRelease(_coreTextFrameRef); + _coreTextFrameRef = NULL; + cgPath = NULL; + CGContextRestoreGState(context); + } +} + +- (void)drawTextWithRuns:(CFArrayRef)runs context:(CGContextRef)context lineOrigin:(CGPoint)lineOrigin +{ + for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runs); runIndex ++) { + CTRunRef run = NULL; + run = (CTRunRef)CFArrayGetValueAtIndex(runs, runIndex); + CFDictionaryRef attr = NULL; + attr = CTRunGetAttributes(run); + if (0 == runIndex) { + NSNumber *baselineOffset = (NSNumber*)CFDictionaryGetValue(attr, (__bridge void *)NSBaselineOffsetAttributeName); + if (baselineOffset) { + lineOrigin.y += [baselineOffset doubleValue]; + } + } + CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y); + CTRunDraw(run, context, CFRangeMake(0, 0)); + CFIndex glyphCount = CTRunGetGlyphCount(run); + if (glyphCount <= 0) continue; + + long longForStrikethroughStyleAttributeName= (long)CFDictionaryGetValue(attr, (__bridge void *)NSStrikethroughStyleAttributeName); + NSUnderlineStyle strikethrough = (NSUnderlineStyle)longForStrikethroughStyleAttributeName; + + if (strikethrough) { + // draw strikethrough + [self drawLineThroughWithRun:runs context:context index:runIndex origin:lineOrigin]; + } + } +} + +- (CTLineRef)buildTruncatedLineWithRuns:(CFArrayRef)runs lines:(NSMutableArray*)mutableLines path:(CGPathRef)cgPath +{ + NSAttributedString * truncationToken = nil; + CTLineRef ctTruncatedLine = NULL; + CTLineRef lastLine = (__bridge CTLineRef)(mutableLines.lastObject); + + CFArrayRef lastLineRuns = CTLineGetGlyphRuns(lastLine); + NSUInteger lastLineRunCount = CFArrayGetCount(lastLineRuns); + + CTLineRef truncationTokenLine = NULL; + NSMutableDictionary *attrs = nil; + if (lastLineRunCount > 0) { + CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, lastLineRunCount - 1); + attrs = (id)CTRunGetAttributes(run); + attrs = attrs ? attrs.mutableCopy : [NSMutableDictionary new]; + CTFontRef font = (__bridge CTFontRef)(attrs[(id)kCTFontAttributeName]); + CGFloat fontSize = font ? CTFontGetSize(font):32 * self.weexInstance.pixelScaleFactor; + UIFont * uiFont = [UIFont systemFontOfSize:fontSize]; + if (uiFont) { + font = CTFontCreateWithName((__bridge CFStringRef)uiFont.fontName, uiFont.pointSize, NULL); + } + if (font) { + attrs[(id)kCTFontAttributeName] = (__bridge id)(font); + uiFont = nil; + CFRelease(font); + } + CGColorRef color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); + if (color && CFGetTypeID(color) == CGColorGetTypeID() && CGColorGetAlpha(color) == 0) { + [attrs removeObjectForKey:(id)kCTForegroundColorAttributeName]; + } + + attrs = attrs?:[NSMutableDictionary new]; + truncationToken = [[NSAttributedString alloc] initWithString:WXTextTruncationToken attributes:attrs]; + truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationToken); + } + + if (truncationTokenLine) { + // default truncationType is kCTLineTruncationEnd + CTLineTruncationType truncationType = kCTLineTruncationEnd; + NSAttributedString *attributedString = [self ctAttributedString]; + NSAttributedString * lastLineText = nil; + NSRange lastLineTextRange = WXNSRangeFromCFRange(CTLineGetStringRange(lastLine)); + NSRange attributeStringRange = NSMakeRange(0, attributedString.string.length); + NSRange interSectionRange = NSIntersectionRange(lastLineTextRange, attributeStringRange); + if (!NSEqualRanges(interSectionRange, lastLineTextRange)) { + // out of bounds + lastLineTextRange = interSectionRange; + } + lastLineText = [attributedString attributedSubstringFromRange: lastLineTextRange]; + if (!lastLineText) { + lastLineText = attributedString; + } + NSMutableAttributedString *mutableLastLineText = lastLineText.mutableCopy; + [mutableLastLineText appendAttributedString:truncationToken]; + CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)[mutableLastLineText copy]); + if (ctLastLineExtend) { + CGRect cgPathRect = CGRectZero; + CGFloat truncatedWidth = 0; + if (CGPathIsRect(cgPath, &cgPathRect)) { + truncatedWidth = cgPathRect.size.width; + } + ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, truncationType, truncationTokenLine); + CFRelease(ctLastLineExtend); + ctLastLineExtend = NULL; + CFRelease(truncationTokenLine); + truncationTokenLine = NULL; + } + } + + return ctTruncatedLine; +} + +- (void)drawLineThroughWithRun:(CFArrayRef)runs context:(CGContextRef)context index:(CFIndex)runIndex origin:(CGPoint)lineOrigin +{ + CFRetain(runs); + CGContextRetain(context); + + CGContextSaveGState(context); + CGFloat xHeight = 0, underLinePosition = 0, lineThickness = 0; + CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, runIndex); + WXTextGetRunsMaxMetric(runs, &xHeight, &underLinePosition, &lineThickness); + + CGPoint strikethroughStart; + strikethroughStart.x = lineOrigin.x - underLinePosition; + strikethroughStart.y = lineOrigin.y + xHeight/2; + CGPoint runPosition = CGPointZero; + CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); + strikethroughStart.x = lineOrigin.x + runPosition.x; + CGContextSetLineWidth(context, WXTextDefaultLineThroughWidth); + double length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); + CGContextMoveToPoint(context, strikethroughStart.x, strikethroughStart.y); + CGContextAddLineToPoint(context, strikethroughStart.x + length, strikethroughStart.y); + CGContextStrokePath(context); + + CGContextRestoreGState(context); + CFRelease(runs); + CGContextRelease(context); +} + +- (CGSize)calculateTextHeightWithWidth:(CGFloat)aWidth +{ + CGFloat totalHeight = 0; + CGSize suggestSize = CGSizeZero; + NSAttributedString * attributedStringCpy = [self ctAttributedString]; + if (!attributedStringCpy) { + return CGSizeZero; + } + if (isnan(aWidth)) { + aWidth = CGFLOAT_MAX; + } + aWidth = [attributedStringCpy boundingRectWithSize:CGSizeMake(aWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size.width; + CTFramesetterRef ctframesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedStringCpy)); + suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(ctframesetterRef, CFRangeMake(0, 0), NULL, CGSizeMake(aWidth, MAXFLOAT), NULL); + + CGMutablePathRef path = NULL; + path = CGPathCreateMutable(); + // sufficient height to draw text + CGPathAddRect(path, NULL, CGRectMake(0, 0, aWidth, suggestSize.height * 10)); + + CTFrameRef frameRef = NULL; + frameRef = CTFramesetterCreateFrame(ctframesetterRef, CFRangeMake(0, attributedStringCpy.length), path, NULL); + CGPathRelease(path); + + CFArrayRef lines = NULL; + if (NULL == frameRef) { + //try to protect unexpected crash. + return suggestSize; + } + CFRelease(ctframesetterRef); + ctframesetterRef = NULL; + lines = CTFrameGetLines(frameRef); + CFIndex lineCount = CFArrayGetCount(lines); + CGFloat ascent = 0; + CGFloat descent = 0; + CGFloat leading = 0; + + // height = ascent + descent + lineCount*leading + // ignore linespaing + NSUInteger actualLineCount = 0; + for (CFIndex lineIndex = 0; (!_lines|| lineIndex < _lines) && lineIndex < lineCount; lineIndex ++) + { + CTLineRef lineRef = NULL; + lineRef = (CTLineRef)CFArrayGetValueAtIndex(lines, lineIndex); + CTLineGetTypographicBounds(lineRef, &ascent, &descent, &leading); + totalHeight += ascent + descent; + actualLineCount ++; + } + + totalHeight = totalHeight + actualLineCount * leading; + CFRelease(frameRef); + frameRef = NULL; + + if (WX_SYS_VERSION_LESS_THAN(@"10.0")) { + // there is something wrong with coreText drawing text height, trying to fix this with more efficent way. + if(actualLineCount && actualLineCount < lineCount) { + suggestSize.height = suggestSize.height * actualLineCount / lineCount; + } + return CGSizeMake(aWidth, suggestSize.height); + } + return CGSizeMake(aWidth, totalHeight); +} + +static void WXTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) +{ + CFRetain(runs); + CGFloat maxXHeight = 0; + CGFloat maxUnderlinePos = 0; + CGFloat maxLineThickness = 0; + for (NSUInteger index = 0, runsCount = CFArrayGetCount(runs); index < runsCount; index ++) { + CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, index); + CFDictionaryRef attrs = CTRunGetAttributes(run); + if (attrs) { + CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); + if (font) { + CGFloat xHeight = CTFontGetXHeight(font); + if (xHeight > maxXHeight) { + maxXHeight = xHeight; + } + + CGFloat underlinePos = CTFontGetUnderlinePosition(font); + if (underlinePos < maxUnderlinePos) { + maxUnderlinePos = underlinePos; + } + + CGFloat lineThickness = CTFontGetUnderlineThickness(font); + if (lineThickness > maxLineThickness) { + maxLineThickness = lineThickness; + } + } + } + } + + if (xHeight) { + *xHeight = maxXHeight; + } + + if (underlinePosition) { + *underlinePosition = maxUnderlinePos; + } + + if (lineThickness) { + *lineThickness = maxLineThickness; + } + + CFRelease(runs); +} + +NS_INLINE NSRange WXNSRangeFromCFRange(CFRange range) { + return NSMakeRange(range.location, range.length); +} + +#ifdef UITEST +- (NSString *)description +{ + return super.description; +} +#endif + +- (void)_resetCSSNodeStyles:(NSArray *)styles +{ + [super _resetCSSNodeStyles:styles]; + if ([styles containsObject:@"color"]) { + _color = [UIColor blackColor]; + [self setNeedsRepaint]; + } + if ([styles containsObject:@"fontSize"]) { + _fontSize = WX_TEXT_FONT_SIZE; + [self setNeedsRepaint]; + [self setNeedsLayout]; + } +} + +@end +
http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m index dad372c..e2d35d6 100644 --- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m +++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m @@ -38,6 +38,8 @@ #import "WXUtility.h" #import "WXExtendCallNativeManager.h" #import "WXExceptionUtils.h" +#import "WXConfigCenterProtocol.h" +#import "WXComponent+Layout.h" @implementation WXSDKEngine http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h index 1baa20f..651af7c 100644 --- a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h +++ b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.h @@ -20,7 +20,65 @@ #import "WXComponent.h" #import "WXSDKInstance.h" #import "WXUtility.h" +#import "WXLayoutDefine.h" +#import "WXCoreLayout.h" -@interface WXComponent (Layout) +#define FlexUndefined NAN +//#define USE_FLEX + + + + + +#ifdef __cplusplus +extern "C" { +#endif + bool flexIsUndefined(float value); +#ifdef __cplusplus +} +#endif +@interface WXComponent () +{ + @package + /** + * Layout + */ +//#ifndef USE_FLEX + css_node_t *_cssNode; +//#else +#ifdef __cplusplus + WeexCore::WXCoreLayoutNode *_flexCssNode; +#endif // __cplusplus +//#endif //USE_FLEX + BOOL _isLayoutDirty; + CGRect _calculatedFrame; + CGPoint _absolutePosition; + WXPositionType _positionType; +} + +//#ifndef USE_FLEX +/** + * @abstract Return the css node used to layout. + * + * @warning Subclasses must not override this. + */ +@property(nonatomic, readonly, assign) css_node_t *cssNode; +//#else +#ifdef __cplusplus +@property(nonatomic, readonly, assign) WeexCore::WXCoreLayoutNode *flexCssNode; +#endif +//#endif + +@end + +@interface WXComponent (Layout) +//#ifndef USE_FLEX +//#else +- (void)_insertChildCssNode:(WXComponent*)subcomponent atIndex:(NSInteger)index; +- (void)_rmChildCssNode:(WXComponent*)subcomponent; +- (NSInteger) getActualNodeIndex:(WXComponent*)subcomponent atIndex:(NSInteger) index; ++ (void) setUseFlex:(BOOL) useFlex; ++ (BOOL) isUseFlex; +//#endif @end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm new file mode 100644 index 0000000..4bdb8f4 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Layout/WXComponent+Layout.mm @@ -0,0 +1,963 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#import "WXComponent+Layout.h" +#import "WXComponent_internal.h" +#import "WXTransform.h" +#import "WXAssert.h" +#import "WXSDKInstance_private.h" +#import "WXComponent+BoxShadow.h" +#import "WXLog.h" + +bool flexIsUndefined(float value) { + return isnan(value); +} + +static BOOL sUseFlex = TRUE; + + +@implementation WXComponent (Layout) + +#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" + +#pragma mark Public + +- (void)setNeedsLayout +{ + _isLayoutDirty = YES; + WXComponent *supercomponent = [self supercomponent]; + if ([WXComponent isUseFlex]) { + //protet nil ptr + if (self.flexCssNode) { + self.flexCssNode->markDirty(); + }else{ + WXLogError(@"flexCssNode is nil"); + } + } + + if(supercomponent){ + [supercomponent setNeedsLayout]; + } +} + +- (BOOL)needsLayout +{ + return _isLayoutDirty; +} + +- (CGSize (^)(CGSize))measureBlock +{ + return nil; +} + +- (void)layoutDidFinish +{ + WXAssertMainThread(); +} + +#pragma mark Private + +- (void)_initCSSNodeWithStyles:(NSDictionary *)styles +{ +//#ifndef USE_FLEX + if (! [WXComponent isUseFlex]) { + _cssNode = new_css_node(); + + _cssNode->print = cssNodePrint; + _cssNode->get_child = cssNodeGetChild; + _cssNode->is_dirty = cssNodeIsDirty; + if ([self measureBlock]) { + _cssNode->measure = cssNodeMeasure; + } + _cssNode->context = (__bridge void *)self; + + [self _recomputeCSSNodeChildren]; + [self _fillCSSNode:styles isUpdate:NO]; + + // To be in conformity with Android/Web, hopefully remove this in the future. + if ([self.ref isEqualToString:WX_SDK_ROOT_REF]) { + if (isUndefined(_cssNode->style.dimensions[CSS_HEIGHT]) && self.weexInstance.frame.size.height) { + _cssNode->style.dimensions[CSS_HEIGHT] = self.weexInstance.frame.size.height; + } + + if (isUndefined(_cssNode->style.dimensions[CSS_WIDTH]) && self.weexInstance.frame.size.width) { + _cssNode->style.dimensions[CSS_WIDTH] = self.weexInstance.frame.size.width; + } + } + } + +//#else + else + { + _flexCssNode = new WeexCore::WXCoreLayoutNode(); + if ([self measureBlock]) { + _flexCssNode->setMeasureFunc(flexCssNodeMeasure); + } + _flexCssNode->setContext((__bridge void *)self); + [self _recomputeCSSNodeChildren]; + [self _fillCSSNode:styles isUpdate:NO]; + + if ([self.ref isEqualToString:WX_SDK_ROOT_REF]) { + if (flexIsUndefined(_flexCssNode->getStyleHeight()) && self.weexInstance.frame.size.height) { + _flexCssNode->setStyleHeight(self.weexInstance.frame.size.height); + } + + if (flexIsUndefined(_flexCssNode->getStyleWidth()) && self.weexInstance.frame.size.width) { + _flexCssNode->setStyleWidth(self.weexInstance.frame.size.width,NO); + } + } + } +//#endif +} + +- (void)_updateCSSNodeStyles:(NSDictionary *)styles +{ + [self _fillCSSNode:styles isUpdate:YES]; +} + +-(void)_resetCSSNodeStyles:(NSArray *)styles +{ + [self _resetCSSNode:styles]; +} + +- (void)_recomputeCSSNodeChildren +{ +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + _cssNode->children_count = (int)[self _childrenCountForLayout]; + } +//#else + +//#endif +} + +- (NSUInteger)_childrenCountForLayout +{ + NSArray *subcomponents = _subcomponents; + NSUInteger count = subcomponents.count; + for (WXComponent *component in subcomponents) { + if (!component->_isNeedJoinLayoutSystem) { + count--; + } + } + return (int)(count); +} + + +- (void)_frameDidCalculated:(BOOL)isChanged +{ + WXAssertComponentThread(); + + if ([self isViewLoaded] && isChanged && [self isViewFrameSyncWithCalculated]) { + + __weak typeof(self) weakSelf = self; + [self.weexInstance.componentManager _addUITask:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (strongSelf->_transform && !CATransform3DEqualToTransform(strongSelf.layer.transform, CATransform3DIdentity)) { + // From the UIView's frame documentation: + // https://developer.apple.com/reference/uikit/uiview#//apple_ref/occ/instp/UIView/frame + // Warning : If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored. + // So layer's transform must be reset to CATransform3DIdentity before setFrame, otherwise frame will be incorrect + strongSelf.layer.transform = CATransform3DIdentity; + } + + if (!CGRectEqualToRect(strongSelf.view.frame,strongSelf.calculatedFrame)) { + strongSelf.view.frame = strongSelf.calculatedFrame; + strongSelf->_absolutePosition = CGPointMake(NAN, NAN); + [strongSelf configBoxShadow:_boxShadow]; + } else { + if (![strongSelf equalBoxShadow:_boxShadow withBoxShadow:_lastBoxShadow]) { + [strongSelf configBoxShadow:_boxShadow]; + } + } + + [self _resetNativeBorderRadius]; + + if (strongSelf->_transform) { + [strongSelf->_transform applyTransformForView:strongSelf.view]; + } + + if (strongSelf->_backgroundImage) { + [strongSelf setGradientLayer]; + } + [strongSelf setNeedsDisplay]; + }]; + } +} + +- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition + gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents +{ + WXAssertComponentThread(); + +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) + { + if (!_cssNode->layout.should_update) { + return; + } + _cssNode->layout.should_update = false; + _isLayoutDirty = NO; + + CGRect newFrame = CGRectMake(isnan(WXRoundPixelValue(_cssNode->layout.position[CSS_LEFT]))?0:WXRoundPixelValue(_cssNode->layout.position[CSS_LEFT]), + isnan(WXRoundPixelValue(_cssNode->layout.position[CSS_TOP]))?0:WXRoundPixelValue(_cssNode->layout.position[CSS_TOP]), + isnan(WXRoundPixelValue(_cssNode->layout.dimensions[CSS_WIDTH]))?0:WXRoundPixelValue(_cssNode->layout.dimensions[CSS_WIDTH]), + isnan(WXRoundPixelValue(_cssNode->layout.dimensions[CSS_HEIGHT]))?0:WXRoundPixelValue(_cssNode->layout.dimensions[CSS_HEIGHT])); + + BOOL isFrameChanged = NO; + if (!CGRectEqualToRect(newFrame, _calculatedFrame)) { + isFrameChanged = YES; + _calculatedFrame = newFrame; + [dirtyComponents addObject:self]; + } + + _cssNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; + _cssNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + _cssNode->layout.position[CSS_LEFT] = 0; + _cssNode->layout.position[CSS_TOP] = 0; + + [self _frameDidCalculated:isFrameChanged]; + NSArray * subcomponents = [_subcomponents copy]; + for (WXComponent *subcomponent in subcomponents) { + [subcomponent _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; + } +#ifdef DEBUG + WXLogDebug(@"flexLayout -> newFrame ,type:%@,ref:%@, parentRef:%@,size :%@ ,instance:%@",self.type,self.ref,self.supercomponent.ref,NSStringFromCGRect(newFrame),self.weexInstance.instanceId); +#endif + } + +//#else + else + { + if (self.flexCssNode->hasNewLayout()) { + self.flexCssNode->setHasNewLayout(false); + _isLayoutDirty = NO; + CGRect newFrame = CGRectMake( + isnan(WXRoundPixelValue(_flexCssNode->getLayoutPositionLeft()))?0:WXRoundPixelValue(_flexCssNode->getLayoutPositionLeft()) + ,isnan(WXRoundPixelValue(_flexCssNode->getLayoutPositionTop()))?0:WXRoundPixelValue(_flexCssNode->getLayoutPositionTop()) + ,isnan(WXRoundPixelValue(_flexCssNode->getLayoutWidth()))?0:WXRoundPixelValue(_flexCssNode->getLayoutWidth()) + ,isnan(WXRoundPixelValue(_flexCssNode->getLayoutHeight()))?0:WXRoundPixelValue(_flexCssNode->getLayoutHeight()) + ); + BOOL isFrameChanged = NO; + + if (!CGRectEqualToRect(newFrame, _calculatedFrame)) { + + isFrameChanged = YES; + _calculatedFrame = newFrame; + [dirtyComponents addObject:self]; + } + + [self _frameDidCalculated:isFrameChanged]; +#ifdef DEBUG + WXLogDebug(@"flexLayout -> newFrame ,type:%@,ref:%@, parentRef:%@,size :%@ ,instance:%@",self.type,self.ref,self.supercomponent.ref,NSStringFromCGRect(newFrame),self.weexInstance.instanceId); +#endif + } + + NSArray * subcomponents = [_subcomponents copy]; + for (WXComponent *subcomponent in subcomponents) { + [subcomponent _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; + } + } +//#endif +} + +- (void)_layoutDidFinish +{ + WXAssertMainThread(); + + if (_positionType == WXPositionTypeSticky) { + [self.ancestorScroller adjustSticky]; + } + [self layoutDidFinish]; +} + +//#ifndef USE_FLEX + +#define WX_STYLE_FILL_CSS_NODE(key, cssProp, type)\ +do {\ + id value = styles[@#key];\ + if (value) {\ + typeof(_cssNode->style.cssProp) convertedValue = (typeof(_cssNode->style.cssProp))[WXConvert type:value];\ + _cssNode->style.cssProp = convertedValue;\ + [self setNeedsLayout];\ + }\ +} while(0); + +#define WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp)\ +do {\ + id value = styles[@#key];\ + if (value) {\ + CGFloat pixel = [self WXPixelType:value];\ + if (isnan(pixel)) {\ + WXLogError(@"Invalid NaN value for style:%@, ref:%@", @#key, self.ref);\ + } else {\ + _cssNode->style.cssProp = pixel;\ + [self setNeedsLayout];\ + }\ + }\ +} while(0); + +#define WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(key, cssProp)\ +do {\ + WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_TOP])\ + WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_LEFT])\ + WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_RIGHT])\ + WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_BOTTOM])\ +} while(0); + +//#else + +#define WX_STYLE_FLEX_NODE_JUDGE_LEGAL(key) styles[key]&&!isnan([WXConvert WXPixelType:styles[key] scaleFactor:self.weexInstance.pixelScaleFactor]) + +//#endif + +- (CGFloat)WXPixelType:(id)value +{ + return [WXConvert WXPixelType:value scaleFactor:self.weexInstance.pixelScaleFactor]; +} + +- (void)_fillCSSNode:(NSDictionary *)styles isUpdate:(BOOL)isUpdate +{ +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + WX_STYLE_FILL_CSS_NODE(direction, direction, css_direction_t) + // flex + WX_STYLE_FILL_CSS_NODE(flex, flex, CGFloat) + WX_STYLE_FILL_CSS_NODE(flexDirection, flex_direction, css_flex_direction_t) + WX_STYLE_FILL_CSS_NODE(alignItems, align_items, css_align_t) + WX_STYLE_FILL_CSS_NODE(alignSelf, align_self, css_align_t) + WX_STYLE_FILL_CSS_NODE(flexWrap, flex_wrap, css_wrap_type_t) + WX_STYLE_FILL_CSS_NODE(justifyContent, justify_content, css_justify_t) + + // position + WX_STYLE_FILL_CSS_NODE(position, position_type, css_position_type_t) + WX_STYLE_FILL_CSS_NODE_PIXEL(top, position[CSS_TOP]) + WX_STYLE_FILL_CSS_NODE_PIXEL(left, position[CSS_LEFT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(right, position[CSS_RIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(bottom, position[CSS_BOTTOM]) + + // dimension + WX_STYLE_FILL_CSS_NODE_PIXEL(width, dimensions[CSS_WIDTH]) + WX_STYLE_FILL_CSS_NODE_PIXEL(height, dimensions[CSS_HEIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(minWidth, minDimensions[CSS_WIDTH]) + WX_STYLE_FILL_CSS_NODE_PIXEL(minHeight, minDimensions[CSS_HEIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(maxWidth, maxDimensions[CSS_WIDTH]) + WX_STYLE_FILL_CSS_NODE_PIXEL(maxHeight, maxDimensions[CSS_HEIGHT]) + + // margin + WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(margin, margin) + WX_STYLE_FILL_CSS_NODE_PIXEL(marginTop, margin[CSS_TOP]) + WX_STYLE_FILL_CSS_NODE_PIXEL(marginLeft, margin[CSS_LEFT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(marginRight, margin[CSS_RIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(marginBottom, margin[CSS_BOTTOM]) + + // border + WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(borderWidth, border) + WX_STYLE_FILL_CSS_NODE_PIXEL(borderTopWidth, border[CSS_TOP]) + WX_STYLE_FILL_CSS_NODE_PIXEL(borderLeftWidth, border[CSS_LEFT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(borderRightWidth, border[CSS_RIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(borderBottomWidth, border[CSS_BOTTOM]) + + // padding + WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(padding, padding) + WX_STYLE_FILL_CSS_NODE_PIXEL(paddingTop, padding[CSS_TOP]) + WX_STYLE_FILL_CSS_NODE_PIXEL(paddingLeft, padding[CSS_LEFT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(paddingRight, padding[CSS_RIGHT]) + WX_STYLE_FILL_CSS_NODE_PIXEL(paddingBottom, padding[CSS_BOTTOM]) + } + +//#else + else + { + // flex + if (styles[@"flex"]) { + _flexCssNode->setFlex([WXConvert CGFloat:styles[@"flex"]]); + } + if (isnan(_flexCssNode->getFlex())) { + // to make the default flex value is zero, yoga is nan, maybe this can configured by yoga config + _flexCssNode->setFlex(0); + } + + if (styles[@"flexDirection"]) { + _flexCssNode->setFlexDirection([self fxFlexDirection:styles[@"flexDirection"]],isUpdate); + } + if (styles[@"alignItems"]) { + _flexCssNode->setAlignItems([self fxAlign:styles[@"alignItems"]]); + } + if (styles[@"alignSelf"]) { + _flexCssNode->setAlignSelf([self fxAlignSelf:styles[@"alignSelf"]]); + } + if (styles[@"flexWrap"]) { + _flexCssNode->setFlexWrap([self fxWrap:styles[@"flexWrap"]]); + } + if (styles[@"justifyContent"]) { + _flexCssNode->setJustifyContent([self fxJustify:styles[@"justifyContent"]]); + } + + // position + if (styles[@"position"]) { + _flexCssNode->setStylePositionType([self fxPositionType:styles[@"position"]]); + } + if (styles[@"top"]) { + _flexCssNode->setStylePosition(WeexCore::kPositionEdgeTop, + [self judgePropValuePropValue:styles[@"top"] defaultValue:NAN]); + } + if (styles[@"left"]) { + _flexCssNode->setStylePosition(WeexCore::kPositionEdgeLeft, + [self judgePropValuePropValue:styles[@"left"] defaultValue:NAN]); + } + if(styles[@"right"]) { + _flexCssNode->setStylePosition(WeexCore::kPositionEdgeRight, + [self judgePropValuePropValue:styles[@"right"] defaultValue:NAN]); + } + if (styles[@"bottom"]) { + _flexCssNode->setStylePosition(WeexCore::kPositionEdgeBottom, + [self judgePropValuePropValue:styles[@"bottom"] defaultValue:NAN]); + } + + // dimension + if (styles[@"width"]) { + _flexCssNode->setStyleWidth([self judgePropValuePropValue:styles[@"width"] defaultValue:NAN] + ,isUpdate); + } + if (styles[@"height"]) { + _flexCssNode->setStyleHeight([self judgePropValuePropValue:styles[@"height"] defaultValue:NAN]); + } + if (styles[@"minWidth"]) { + _flexCssNode->setMinWidth([self judgePropValuePropValue:styles[@"minWidth"] defaultValue:NAN] + ,isUpdate); + } + if (styles[@"minHeight"]) { + _flexCssNode->setMinHeight([self judgePropValuePropValue:styles[@"minHeight"] defaultValue:NAN]); + } + if (styles[@"maxWidth"]) { + _flexCssNode->setMaxWidth([self judgePropValuePropValue:styles[@"maxWidth"] defaultValue:NAN] + ,isUpdate); + } + if (styles[@"maxHeight"]) { + _flexCssNode->setMaxHeight([self judgePropValuePropValue:styles[@"maxHeight"] defaultValue:NAN]); + } + + // margin + if (styles[@"margin"]) { + _flexCssNode->setMargin(WeexCore::kMarginALL, + [self judgePropValuePropValue:styles[@"margin"] defaultValue:0]); + } + if (styles[@"marginTop"]) { + _flexCssNode->setMargin(WeexCore::kMarginTop, + [self judgePropValuePropValue:styles[@"marginTop"] defaultValue:0]); + } + if (styles[@"marginBottom"]) { + _flexCssNode->setMargin(WeexCore::kMarginBottom, + [self judgePropValuePropValue:styles[@"marginBottom"] defaultValue:0]); + } + if (styles[@"marginRight"]) { + _flexCssNode->setMargin(WeexCore::kMarginRight, + [self judgePropValuePropValue:styles[@"marginRight"] defaultValue:0]); + } + if (styles[@"marginLeft"]) { + _flexCssNode->setMargin(WeexCore::kMarginLeft, + [self judgePropValuePropValue:styles[@"marginLeft"] defaultValue:0]); + } + + // border + if (styles[@"borderWidth"]) { + _flexCssNode->setBorderWidth(WeexCore::kBorderWidthALL, + [self judgePropValuePropValue:styles[@"borderWidth"] defaultValue:0]); + } + if (styles[@"borderTopWidth"]) { + _flexCssNode->setBorderWidth(WeexCore::kBorderWidthTop, + [self judgePropValuePropValue:styles[@"borderTopWidth"] defaultValue:0]); + } + + if (styles[@"borderLeftWidth"]) { + _flexCssNode->setBorderWidth(WeexCore::kBorderWidthLeft, + [self judgePropValuePropValue:styles[@"borderLeftWidth"] defaultValue:0]); + } + + if (styles[@"borderBottomWidth"]) { + _flexCssNode->setBorderWidth(WeexCore::kBorderWidthBottom, + [self judgePropValuePropValue:styles[@"borderBottomWidth"] defaultValue:0]); + } + if (styles[@"borderRightWidth"]) { + _flexCssNode->setBorderWidth(WeexCore::kBorderWidthRight, + [self judgePropValuePropValue:styles[@"borderRightWidth"] defaultValue:0]); + } + + // padding + if (styles[@"padding"]) { + _flexCssNode->setPadding(WeexCore::kPaddingALL, + [self judgePropValuePropValue:styles[@"padding"] defaultValue:0]); + } + if (styles[@"paddingTop"]) { + _flexCssNode->setPadding(WeexCore::kPaddingTop, + [self judgePropValuePropValue:styles[@"paddingTop"] defaultValue:0]); + } + if (styles[@"paddingLeft"]) { + _flexCssNode->setPadding(WeexCore::kPaddingLeft, + [self judgePropValuePropValue:styles[@"paddingLeft"] defaultValue:0]); + } + if (styles[@"paddingBottom"]) { + _flexCssNode->setPadding(WeexCore::kPaddingBottom, + [self judgePropValuePropValue:styles[@"paddingBottom"] defaultValue:0]); + } + if (styles[@"paddingRight"]) { + _flexCssNode->setPadding(WeexCore::kPaddingRight, + [self judgePropValuePropValue:styles[@"paddingRight"] defaultValue:0]); + } + + [self setNeedsLayout]; + } + +//#endif +} + +-(CGFloat)judgePropValuePropValue:(NSString *)propValue defaultValue:(CGFloat)defaultValue{ + CGFloat convertValue = (CGFloat)[WXConvert WXPixelType:propValue scaleFactor:self.weexInstance.pixelScaleFactor]; + if (!isnan(convertValue)) { + return convertValue; + } + return defaultValue; +} + +//#ifndef USE_FLEX + +#define WX_STYLE_RESET_CSS_NODE(key, cssProp, defaultValue)\ +do {\ + if (styles && [styles containsObject:@#key]) {\ + _cssNode->style.cssProp = defaultValue;\ + [self setNeedsLayout];\ + }\ +} while(0); + +#define WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(key, cssProp, defaultValue)\ +do {\ + WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_TOP], defaultValue)\ + WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_LEFT], defaultValue)\ + WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_RIGHT], defaultValue)\ + WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_BOTTOM], defaultValue)\ +} while(0); + +//#else + +#define WX_FLEX_STYLE_RESET_CSS_NODE(key, defaultValue)\ +do {\ + WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_KEY(key,key,defaultValue)\ +} while(0); + +#define WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_KEY(judgeKey, propKey, defaultValue)\ +do {\ + if (styles && [styles containsObject:@#judgeKey]) {\ + NSMutableDictionary *resetStyleDic = [[NSMutableDictionary alloc] init];\ + [resetStyleDic setValue:defaultValue forKey:@#propKey];\ + [self _updateCSSNodeStyles:resetStyleDic];\ + [self setNeedsLayout];\ + }\ +} while(0); + +#define WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_DIRECTION_KEY(judgeKey, propTopKey,propLeftKey,propRightKey,propBottomKey, defaultValue)\ +do {\ + if (styles && [styles containsObject:@#judgeKey]) {\ + NSMutableDictionary *resetStyleDic = [[NSMutableDictionary alloc] init];\ + [resetStyleDic setValue:defaultValue forKey:@#propTopKey];\ + [resetStyleDic setValue:defaultValue forKey:@#propLeftKey];\ + [resetStyleDic setValue:defaultValue forKey:@#propRightKey];\ + [resetStyleDic setValue:defaultValue forKey:@#propBottomKey];\ + [self _updateCSSNodeStyles:resetStyleDic];\ + [self setNeedsLayout];\ + }\ +} while(0); + +//#endif + + +- (void)_resetCSSNode:(NSArray *)styles +{ +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + WX_STYLE_RESET_CSS_NODE(direction, direction, CSS_DIRECTION_LTR) + // flex + WX_STYLE_RESET_CSS_NODE(flex, flex, 0.0) + WX_STYLE_RESET_CSS_NODE(flexDirection, flex_direction, CSS_FLEX_DIRECTION_COLUMN) + WX_STYLE_RESET_CSS_NODE(alignItems, align_items, CSS_ALIGN_STRETCH) + WX_STYLE_RESET_CSS_NODE(alignSelf, align_self, CSS_ALIGN_AUTO) + WX_STYLE_RESET_CSS_NODE(flexWrap, flex_wrap, CSS_NOWRAP) + WX_STYLE_RESET_CSS_NODE(justifyContent, justify_content, CSS_JUSTIFY_FLEX_START) + + // position + WX_STYLE_RESET_CSS_NODE(position, position_type, CSS_POSITION_RELATIVE) + WX_STYLE_RESET_CSS_NODE(top, position[CSS_TOP], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(left, position[CSS_LEFT], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(right, position[CSS_RIGHT], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(bottom, position[CSS_BOTTOM], CSS_UNDEFINED) + + // dimension + WX_STYLE_RESET_CSS_NODE(width, dimensions[CSS_WIDTH], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(height, dimensions[CSS_HEIGHT], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(minWidth, minDimensions[CSS_WIDTH], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(minHeight, minDimensions[CSS_HEIGHT], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(maxWidth, maxDimensions[CSS_WIDTH], CSS_UNDEFINED) + WX_STYLE_RESET_CSS_NODE(maxHeight, maxDimensions[CSS_HEIGHT], CSS_UNDEFINED) + + // margin + WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(margin, margin, 0.0) + WX_STYLE_RESET_CSS_NODE(marginTop, margin[CSS_TOP], 0.0) + WX_STYLE_RESET_CSS_NODE(marginLeft, margin[CSS_LEFT], 0.0) + WX_STYLE_RESET_CSS_NODE(marginRight, margin[CSS_RIGHT], 0.0) + WX_STYLE_RESET_CSS_NODE(marginBottom, margin[CSS_BOTTOM], 0.0) + + // border + WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(borderWidth, border, 0.0) + WX_STYLE_RESET_CSS_NODE(borderTopWidth, border[CSS_TOP], 0.0) + WX_STYLE_RESET_CSS_NODE(borderLeftWidth, border[CSS_LEFT], 0.0) + WX_STYLE_RESET_CSS_NODE(borderRightWidth, border[CSS_RIGHT], 0.0) + WX_STYLE_RESET_CSS_NODE(borderBottomWidth, border[CSS_BOTTOM], 0.0) + + // padding + WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(padding, padding, 0.0) + WX_STYLE_RESET_CSS_NODE(paddingTop, padding[CSS_TOP], 0.0) + WX_STYLE_RESET_CSS_NODE(paddingLeft, padding[CSS_LEFT], 0.0) + WX_STYLE_RESET_CSS_NODE(paddingRight, padding[CSS_RIGHT], 0.0) + WX_STYLE_RESET_CSS_NODE(paddingBottom, padding[CSS_BOTTOM], 0.0) + } + +//#else + else + { + if (styles.count<=0) { + return; + } + + WX_FLEX_STYLE_RESET_CSS_NODE(flex, @0.0) + WX_FLEX_STYLE_RESET_CSS_NODE(flexDirection, @(CSS_FLEX_DIRECTION_COLUMN)) + WX_FLEX_STYLE_RESET_CSS_NODE(alignItems, @(CSS_ALIGN_STRETCH)) + WX_FLEX_STYLE_RESET_CSS_NODE(alignSelf, @(CSS_ALIGN_AUTO)) + WX_FLEX_STYLE_RESET_CSS_NODE(flexWrap, @(CSS_NOWRAP)) + WX_FLEX_STYLE_RESET_CSS_NODE(justifyContent, @(CSS_JUSTIFY_FLEX_START)) + + // position + WX_FLEX_STYLE_RESET_CSS_NODE(position, @(CSS_POSITION_RELATIVE)) + WX_FLEX_STYLE_RESET_CSS_NODE(top, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(left, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(right, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(bottom, @(CSS_UNDEFINED)) + + // dimension + WX_FLEX_STYLE_RESET_CSS_NODE(width, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(height, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(minWidth, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(minHeight, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(maxWidth, @(CSS_UNDEFINED)) + WX_FLEX_STYLE_RESET_CSS_NODE(maxHeight, @(CSS_UNDEFINED)) + + // margin + WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_DIRECTION_KEY(margin + ,marginTop + ,marginLeft + ,marginRight + ,marginBottom + , @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(marginTop, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(marginLeft, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(marginRight, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(marginBottom, @(0.0)) + + // border + WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_DIRECTION_KEY(borderWidth + , borderTopWidth + , borderLeftWidth + , borderRightWidth + , borderBottomWidth + , @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(borderTopWidth, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(borderLeftWidth, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(borderRightWidth, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(borderBottomWidth, @(0.0)) + + // padding + WX_FLEX_STYLE_RESET_CSS_NODE_GIVEN_DIRECTION_KEY(padding + , paddingTop + , paddingLeft + , paddingRight + , paddingBottom + , @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(paddingTop, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(paddingLeft, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(paddingRight, @(0.0)) + WX_FLEX_STYLE_RESET_CSS_NODE(paddingBottom, @(0.0)) + } + + +//#endif +} + +#pragma mark CSS Node Override + +//#ifndef USE_FLEX +#if defined __cplusplus +extern "C" { +#endif +static void cssNodePrint(void *context) +{ + WXComponent *component = (__bridge WXComponent *)context; + // TODO: + printf("%s:%s ", component.ref.UTF8String, component->_type.UTF8String); +} + +static css_node_t * cssNodeGetChild(void *context, int i) +{ + WXComponent *component = (__bridge WXComponent *)context; + NSArray *subcomponents = component->_subcomponents; + for (int j = 0; j <= i && j < subcomponents.count; j++) { + WXComponent *child = subcomponents[j]; + if (!child->_isNeedJoinLayoutSystem) { + i++; + } + } + + if(i >= 0 && i < subcomponents.count){ + WXComponent *child = subcomponents[i]; +// WXLogInfo(@"FlexLayout -- P:%@ -> C:%@",component,(__bridge WXComponent *)child->_cssNode->context); + return child->_cssNode; + } + + WXAssert(NO, @"Can not find component:%@'s css node child at index: %ld, totalCount:%ld", component, i, subcomponents.count); + return NULL; +} + +static bool cssNodeIsDirty(void *context) +{ + WXAssertComponentThread(); + + WXComponent *component = (__bridge WXComponent *)context; + BOOL needsLayout = [component needsLayout]; + + return needsLayout; +} + +static css_dim_t cssNodeMeasure(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode) +{ + WXComponent *component = (__bridge WXComponent *)context; + CGSize (^measureBlock)(CGSize) = [component measureBlock]; + + if (!measureBlock) { + return (css_dim_t){NAN, NAN}; + } + + CGSize constrainedSize = CGSizeMake(width, height); + CGSize resultSize = measureBlock(constrainedSize); +#ifdef DEBUG + WXLogDebug(@"flexLayout -> measureblock %@, resultSize:%@", + component.type, + NSStringFromCGSize(resultSize) + ); +#endif + + return (css_dim_t){(float)resultSize.width, (float)resultSize.height}; +} +#if defined __cplusplus +}; +#endif + +//#else + +static WeexCore::WXCoreSize flexCssNodeMeasure(WeexCore::WXCoreLayoutNode *node, float width, WeexCore::MeasureMode widthMeasureMode,float height, WeexCore::MeasureMode heightMeasureMode){ + + if (node->getContext() == nullptr) { //为空 + return WeexCore::WXCoreSize(); + } + WXComponent *component = (__bridge WXComponent *)(node->getContext()); + + if (![component respondsToSelector:@selector(measureBlock)]) { + return WeexCore::WXCoreSize(); + } + + CGSize (^measureBlock)(CGSize) = [component measureBlock]; + + if (!measureBlock) { + return WeexCore::WXCoreSize(); + } + + CGSize constrainedSize = CGSizeMake(width, height); + CGSize resultSize = measureBlock(constrainedSize); +#ifdef DEBUG + WXLogDebug(@"flexLayout -> measureblock %@, resultSize:%@", + component.type, + NSStringFromCGSize(resultSize) + ); +#endif + WeexCore::WXCoreSize size; + size.height=(float)resultSize.height; + size.width=(float)resultSize.width; + return size; +} + +-(WeexCore::WXCorePositionType)fxPositionType:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"absolute"]) { + return WeexCore::kAbsolute; + } else if ([value isEqualToString:@"relative"]) { + return WeexCore::kRelative; + } else if ([value isEqualToString:@"fixed"]) { + return WeexCore::kFixed; + } else if ([value isEqualToString:@"sticky"]) { + return WeexCore::kRelative; + } + } + return WeexCore::kRelative; +} + +- (WeexCore::WXCoreFlexDirection)fxFlexDirection:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"column"]) { + return WeexCore::kFlexDirectionColumn; + } else if ([value isEqualToString:@"column-reverse"]) { + return WeexCore::kFlexDirectionColumnReverse; + } else if ([value isEqualToString:@"row"]) { + return WeexCore::kFlexDirectionRow; + } else if ([value isEqualToString:@"row-reverse"]) { + return WeexCore::kFlexDirectionRowReverse; + } + } + return WeexCore::kFlexDirectionColumn; +} + +//TODO +- (WeexCore::WXCoreAlignItems)fxAlign:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"stretch"]) { + return WeexCore::kAlignItemsStretch; + } else if ([value isEqualToString:@"flex-start"]) { + return WeexCore::kAlignItemsFlexStart; + } else if ([value isEqualToString:@"flex-end"]) { + return WeexCore::kAlignItemsFlexEnd; + } else if ([value isEqualToString:@"center"]) { + return WeexCore::kAlignItemsCenter; + //return WXCoreFlexLayout::WXCore_AlignItems_Center; + } else if ([value isEqualToString:@"auto"]) { +// return YGAlignAuto; + } else if ([value isEqualToString:@"baseline"]) { +// return YGAlignBaseline; + } + } + + return WeexCore::kAlignItemsStretch; +} + +- (WeexCore::WXCoreAlignSelf)fxAlignSelf:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"stretch"]) { + return WeexCore::kAlignSelfStretch; + } else if ([value isEqualToString:@"flex-start"]) { + return WeexCore::kAlignSelfFlexStart; + } else if ([value isEqualToString:@"flex-end"]) { + return WeexCore::kAlignSelfFlexEnd; + } else if ([value isEqualToString:@"center"]) { + return WeexCore::kAlignSelfCenter; + } else if ([value isEqualToString:@"auto"]) { + return WeexCore::kAlignSelfAuto; + } else if ([value isEqualToString:@"baseline"]) { + // return YGAlignBaseline; + } + } + + return WeexCore::kAlignSelfStretch; +} + +- (WeexCore::WXCoreFlexWrap)fxWrap:(id)value +{ + if([value isKindOfClass:[NSString class]]) { + if ([value isEqualToString:@"nowrap"]) { + return WeexCore::kNoWrap; + } else if ([value isEqualToString:@"wrap"]) { + return WeexCore::kWrap; + } else if ([value isEqualToString:@"wrap-reverse"]) { + return WeexCore::kWrapReverse; + } + } + return WeexCore::kNoWrap; +} + +- (WeexCore::WXCoreJustifyContent)fxJustify:(id)value +{ + if([value isKindOfClass:[NSString class]]){ + if ([value isEqualToString:@"flex-start"]) { + return WeexCore::kJustifyFlexStart; + } else if ([value isEqualToString:@"center"]) { + return WeexCore::kJustifyCenter; + } else if ([value isEqualToString:@"flex-end"]) { + return WeexCore::kJustifyFlexEnd; + } else if ([value isEqualToString:@"space-between"]) { + return WeexCore::kJustifySpaceBetween; + } else if ([value isEqualToString:@"space-around"]) { + return WeexCore::kJustifySpaceAround; + } + } + return WeexCore::kJustifyFlexStart; +} + + +- (NSInteger) getActualNodeIndex:(WXComponent*)subcomponent atIndex:(NSInteger) index +{ + NSInteger actualIndex = 0; //å®é é¤å»ä¸éè¦å¸å±çsubComponentï¼æ¤æ¶æå¨çæ£ç¡®ä½ç½® + for (WXComponent *child in _subcomponents) { + if ([child.ref isEqualToString:subcomponent.ref]) { + break; + } + if (child->_isNeedJoinLayoutSystem) { + actualIndex ++; + } + } + return actualIndex; +} + +- (void)_insertChildCssNode:(WXComponent*)subcomponent atIndex:(NSInteger)index +{ + self.flexCssNode->addChildAt(subcomponent.flexCssNode, (uint32_t)index); +} + +- (void)_rmChildCssNode:(WXComponent *)subcomponent +{ + self.flexCssNode->removeChild(subcomponent->_flexCssNode); +#ifdef DEBUG + WXLogDebug(@"flexLayout -> ref:%@ ,flexCssNode->removeChild ,childRef:%@",self.ref,subcomponent.ref); +#endif +} + + ++ (void) setUseFlex:(BOOL)useFlex +{ + sUseFlex =useFlex; +} + + ++ (BOOL) isUseFlex +{ + return sUseFlex; +} + +//#endif + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Layout/WXCoreFlexEnum.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Layout/WXCoreFlexEnum.h b/ios/sdk/WeexSDK/Sources/Layout/WXCoreFlexEnum.h new file mode 100644 index 0000000..26fe8a3 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Layout/WXCoreFlexEnum.h @@ -0,0 +1,107 @@ +#ifdef __cplusplus + +#ifndef WEEXCORE_FLEXLAYOUT_WXCOREFLEXENUM_H +#define WEEXCORE_FLEXLAYOUT_WXCOREFLEXENUM_H + +namespace WeexCore { + + /** + * MainAxis direction + */ + enum WXCoreFlexDirection { + kFlexDirectionColumn, + kFlexDirectionColumnReverse, + kFlexDirectionRow, + kFlexDirectionRowReverse, + }; + + /** + * Controls the position of the element on the MainAxis + */ + enum WXCoreJustifyContent { + kJustifyFlexStart, + kJustifyCenter, + kJustifyFlexEnd, + kJustifySpaceBetween, + kJustifySpaceAround, + }; + + /** + * Controls the position of the element on the CrossAxis and whether Stretch + */ + enum WXCoreAlignItems { + kAlignItemsFlexStart, + kAlignItemsCenter, + kAlignItemsFlexEnd, + kAlignItemsStretch, + }; + + /** + * Controls the count of flexlines + */ + enum WXCoreFlexWrap { + kNoWrap, + kWrap, + kWrapReverse, + }; + + /** + * The align-self will overrides the align-items specified by the Flex container. + * The two attributes have the same range of values. + */ + enum WXCoreAlignSelf { + kAlignSelfAuto = -1, + kAlignSelfFlexStart = kAlignItemsFlexStart, + kAlignSelfCenter = kAlignItemsCenter, + kAlignSelfFlexEnd = kAlignItemsFlexEnd, + kAlignSelfStretch = kAlignItemsStretch, + }; + + enum WXCorePositionType { + kRelative, + kAbsolute, + kFixed, + kSticky = kRelative + }; + + enum WXCorePositionEdge { + kPositionEdgeTop, + kPositionEdgeBottom, + kPositionEdgeLeft, + kPositionEdgeRight, + }; + + enum WXCoreMarginEdge { + kMarginALL, + kMarginTop, + kMarginBottom, + kMarginLeft, + kMarginRight, + }; + + enum WXCorePaddingEdge { + kPaddingALL, + kPaddingTop, + kPaddingBottom, + kPaddingLeft, + kPaddingRight, + }; + + enum WXCoreBorderWidthEdge { + kBorderWidthALL, + kBorderWidthTop, + kBorderWidthBottom, + kBorderWidthLeft, + kBorderWidthRight, + }; + + enum WXCoreEdge{ + kTop, + kRight, + kBottom, + kLeft, + }; + +} +#endif //WEEXCORE_FLEXLAYOUT_WXCOREFLEXENUM_H +#endif \ No newline at end of file