mirror of
https://github.com/wu736139669/ManagerDemo.git
synced 2026-08-05 05:55:42 +00:00
1634 lines
43 KiB
Objective-C
1634 lines
43 KiB
Objective-C
//
|
|
// DTHTMLElement.m
|
|
// CoreTextExtensions
|
|
//
|
|
// Created by Oliver Drobnik on 4/14/11.
|
|
// Copyright 2011 Drobnik.com. All rights reserved.
|
|
//
|
|
|
|
#import "DTCoreText.h"
|
|
#import "DTHTMLElement.h"
|
|
#import "DTAnchorHTMLElement.h"
|
|
#import "DTTextAttachmentHTMLElement.h"
|
|
#import "DTBreakHTMLElement.h"
|
|
#import "DTHorizontalRuleHTMLElement.h"
|
|
#import "DTListItemHTMLElement.h"
|
|
#import "DTStylesheetHTMLElement.h"
|
|
#import "DTTextHTMLElement.h"
|
|
#import "NSString+DTUtilities.h"
|
|
#import "DTColorFunctions.h"
|
|
#import "DTLog.h"
|
|
|
|
@interface DTHTMLElement ()
|
|
|
|
@property (nonatomic, strong) NSString *linkGUID;
|
|
|
|
// internal initializer
|
|
- (id)initWithName:(NSString *)name attributes:(NSDictionary *)attributes options:(NSDictionary *)options;
|
|
|
|
@end
|
|
|
|
// global variables
|
|
BOOL ___shouldUseiOS6Attributes = NO;
|
|
NSDictionary *_classesForNames = nil;
|
|
|
|
|
|
@implementation DTHTMLElement
|
|
{
|
|
NSSet *_CSSClassNamesToIgnoreForCustomAttributes;
|
|
}
|
|
|
|
+ (void)initialize
|
|
{
|
|
// prevent calling from children
|
|
if (self != [DTHTMLElement class])
|
|
{
|
|
return;
|
|
}
|
|
|
|
// lookup table so that we quickly get the correct class to instantiate for special tags
|
|
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
|
|
|
|
[tmpDict setObject:[DTAnchorHTMLElement class] forKey:@"a"];
|
|
[tmpDict setObject:[DTBreakHTMLElement class] forKey:@"br"];
|
|
[tmpDict setObject:[DTHorizontalRuleHTMLElement class] forKey:@"hr"];
|
|
[tmpDict setObject:[DTListItemHTMLElement class] forKey:@"li"];
|
|
[tmpDict setObject:[DTStylesheetHTMLElement class] forKey:@"style"];
|
|
[tmpDict setObject:[DTTextAttachmentHTMLElement class] forKey:@"img"];
|
|
[tmpDict setObject:[DTTextAttachmentHTMLElement class] forKey:@"object"];
|
|
[tmpDict setObject:[DTTextAttachmentHTMLElement class] forKey:@"video"];
|
|
[tmpDict setObject:[DTTextAttachmentHTMLElement class] forKey:@"iframe"];
|
|
|
|
_classesForNames = [tmpDict copy];
|
|
}
|
|
|
|
+ (instancetype)elementWithName:(NSString *)name attributes:(NSDictionary *)attributes options:(NSDictionary *)options
|
|
{
|
|
// look for specialized class
|
|
Class class = [_classesForNames objectForKey:name];
|
|
|
|
if (!class)
|
|
{
|
|
// see if this is a custom attachment class
|
|
Class attachmentClass = [DTTextAttachment registeredClassForTagName:name];
|
|
|
|
if (attachmentClass)
|
|
{
|
|
class = [DTTextAttachmentHTMLElement class];
|
|
}
|
|
else
|
|
{
|
|
// use generic of none found
|
|
class = [DTHTMLElement class];
|
|
}
|
|
}
|
|
|
|
DTHTMLElement *element = [[class alloc] initWithName:name attributes:attributes options:options];
|
|
|
|
return element;
|
|
}
|
|
|
|
- (instancetype)initWithName:(NSString *)name attributes:(NSDictionary *)attributes options:(NSDictionary *)options
|
|
{
|
|
// node does not need the options, but it needs the name and attributes
|
|
self = [super initWithName:name attributes:attributes];
|
|
if (self)
|
|
{
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Creating Attributed Strings
|
|
|
|
- (NSDictionary *)attributesForAttributedStringRepresentation
|
|
{
|
|
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
|
|
|
|
// add text attachment
|
|
if (_textAttachment)
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
// need run delegate for sizing (only supported on iOS)
|
|
CTRunDelegateRef embeddedObjectRunDelegate = createEmbeddedObjectRunDelegate(_textAttachment);
|
|
[tmpDict setObject:CFBridgingRelease(embeddedObjectRunDelegate) forKey:(id)kCTRunDelegateAttributeName];
|
|
#endif
|
|
|
|
// add attachment
|
|
[tmpDict setObject:_textAttachment forKey:NSAttachmentAttributeName];
|
|
|
|
// remember original paragraphSpacing
|
|
[tmpDict setObject:DTNSNumberFromCGFloat(self.paragraphStyle.paragraphSpacing) forKey:DTAttachmentParagraphSpacingAttribute];
|
|
}
|
|
|
|
CTFontRef font = [_fontDescriptor newMatchingFont];
|
|
|
|
if (font)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES && __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
|
|
if (___useiOS6Attributes)
|
|
{
|
|
UIFont *uiFont = [UIFont fontWithCTFont:font];
|
|
[tmpDict setObject:uiFont forKey:NSFontAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// __bridge since its already retained elsewhere
|
|
[tmpDict setObject:(__bridge id)(font) forKey:(id)kCTFontAttributeName];
|
|
}
|
|
|
|
|
|
// use this font to adjust the values needed for the run delegate during layout time
|
|
[_textAttachment adjustVerticalAlignmentForFont:font];
|
|
|
|
CFRelease(font);
|
|
}
|
|
|
|
// add hyperlink
|
|
if (_link)
|
|
{
|
|
[tmpDict setObject:_link forKey:DTLinkAttribute];
|
|
|
|
// add a GUID to group multiple glyph runs belonging to same link
|
|
[tmpDict setObject:_linkGUID forKey:DTGUIDAttribute];
|
|
}
|
|
|
|
// add anchor
|
|
if (_anchorName)
|
|
{
|
|
[tmpDict setObject:_anchorName forKey:DTAnchorAttribute];
|
|
}
|
|
|
|
// add strikout if applicable
|
|
if (_strikeOut)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
[tmpDict setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:[NSNumber numberWithBool:YES] forKey:DTStrikeOutAttribute];
|
|
}
|
|
}
|
|
|
|
// set underline style
|
|
if (_underlineStyle)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
[tmpDict setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:[NSNumber numberWithInteger:_underlineStyle] forKey:(id)kCTUnderlineStyleAttributeName];
|
|
}
|
|
|
|
// we could set an underline color as well if we wanted, but not supported by HTML
|
|
// [attributes setObject:(id)[DTImage redColor].CGColor forKey:(id)kCTUnderlineColorAttributeName];
|
|
}
|
|
|
|
if (_textColor)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
[tmpDict setObject:_textColor forKey:NSForegroundColorAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:(id)[_textColor CGColor] forKey:(id)kCTForegroundColorAttributeName];
|
|
}
|
|
}
|
|
|
|
if (_backgroundColor)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
[tmpDict setObject:_backgroundColor forKey:NSBackgroundColorAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:(id)[_backgroundColor CGColor] forKey:DTBackgroundColorAttribute];
|
|
}
|
|
}
|
|
|
|
if (_superscriptStyle)
|
|
{
|
|
[tmpDict setObject:(id)[NSNumber numberWithInteger:_superscriptStyle] forKey:(id)kCTSuperscriptAttributeName];
|
|
}
|
|
|
|
// add paragraph style
|
|
if (_paragraphStyle)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
NSParagraphStyle *style = [self.paragraphStyle NSParagraphStyle];
|
|
[tmpDict setObject:style forKey:NSParagraphStyleAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
CTParagraphStyleRef newParagraphStyle = [self.paragraphStyle createCTParagraphStyle];
|
|
[tmpDict setObject:CFBridgingRelease(newParagraphStyle) forKey:(id)kCTParagraphStyleAttributeName];
|
|
}
|
|
}
|
|
|
|
// add shadow array if applicable
|
|
if (_shadows)
|
|
{
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
// only a single shadow supported
|
|
NSDictionary *firstShadow = [_shadows objectAtIndex:0];
|
|
|
|
NSShadow *shadow = [[NSShadow alloc] init];
|
|
shadow.shadowOffset = [[firstShadow objectForKey:@"Offset"] CGSizeValue];
|
|
shadow.shadowColor = [firstShadow objectForKey:@"Color"];
|
|
shadow.shadowBlurRadius = [[firstShadow objectForKey:@"Blur"] floatValue];
|
|
[tmpDict setObject:shadow forKey:NSShadowAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:_shadows forKey:DTShadowsAttribute];
|
|
}
|
|
}
|
|
|
|
if (_letterSpacing)
|
|
{
|
|
NSNumber *letterSpacingNum = DTNSNumberFromCGFloat(_letterSpacing);
|
|
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
[tmpDict setObject:letterSpacingNum forKey:NSKernAttributeName];
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
[tmpDict setObject:letterSpacingNum forKey:(id)kCTKernAttributeName];
|
|
}
|
|
}
|
|
|
|
if (_headerLevel)
|
|
{
|
|
[tmpDict setObject:[NSNumber numberWithInteger:_headerLevel] forKey:DTHeaderLevelAttribute];
|
|
}
|
|
|
|
if (_paragraphStyle.textLists)
|
|
{
|
|
[tmpDict setObject:_paragraphStyle.textLists forKey:DTTextListsAttribute];
|
|
}
|
|
|
|
if (_paragraphStyle.textBlocks)
|
|
{
|
|
[tmpDict setObject:_paragraphStyle.textBlocks forKey:DTTextBlocksAttribute];
|
|
}
|
|
|
|
if (_backgroundStrokeColor)
|
|
{
|
|
[tmpDict setObject:(id)[_backgroundStrokeColor CGColor] forKey:DTBackgroundStrokeColorAttribute];
|
|
}
|
|
|
|
if (_backgroundStrokeWidth)
|
|
{
|
|
[tmpDict setObject:DTNSNumberFromCGFloat(_backgroundStrokeWidth) forKey:DTBackgroundStrokeWidthAttribute];
|
|
}
|
|
|
|
if (_backgroundCornerRadius)
|
|
{
|
|
[tmpDict setObject:DTNSNumberFromCGFloat(_backgroundCornerRadius) forKey:DTBackgroundCornerRadiusAttribute];
|
|
}
|
|
|
|
return tmpDict;
|
|
}
|
|
|
|
- (BOOL)needsOutput
|
|
{
|
|
@synchronized(self)
|
|
{
|
|
if ([self.childNodes count])
|
|
{
|
|
for (DTHTMLElement *oneChild in self.childNodes)
|
|
{
|
|
// ignore children that have display:none
|
|
if (oneChild.displayStyle == DTHTMLElementDisplayStyleNone)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!oneChild.didOutput)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
- (BOOL)_isNotChildOfList
|
|
{
|
|
DTHTMLElement *parent = self.parentElement;
|
|
|
|
if (parent.displayStyle == DTHTMLElementDisplayStyleListItem)
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
if (parent.displayStyle == DTHTMLElementDisplayStyleBlock)
|
|
{
|
|
if ([parent.name isEqualToString:@"ol"] || [parent.name isEqualToString:@"ul"])
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
// adds the attributes that have not been "dealt with" to the DTCustomAttributesAttribute
|
|
- (void)_addCustomHTMLAttributesToAttributedString:(NSMutableAttributedString *)attributedString
|
|
{
|
|
NSSet *attributesToIgnore = [[self class] attributesToIgnoreForCustomAttributesAttribute];
|
|
NSRange entireString = NSMakeRange(0, [attributedString length]);
|
|
|
|
[_attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
|
|
|
if ([attributesToIgnore containsObject:key]) return;
|
|
|
|
if (_CSSClassNamesToIgnoreForCustomAttributes && [key isEqualToString:@"class"])
|
|
{
|
|
NSMutableArray *classNamesToKeep = [NSMutableArray array];
|
|
|
|
NSArray *components = nil;
|
|
|
|
if ([value isKindOfClass:[NSString class]])
|
|
{
|
|
// split the class string
|
|
components = [value componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
}
|
|
else if ([value isKindOfClass:[NSArray class]])
|
|
{
|
|
// already an array
|
|
components = value;
|
|
}
|
|
|
|
for (NSString *oneClassName in components)
|
|
{
|
|
if (![_CSSClassNamesToIgnoreForCustomAttributes containsObject:oneClassName])
|
|
{
|
|
[classNamesToKeep addObject:oneClassName];
|
|
}
|
|
}
|
|
|
|
if ([classNamesToKeep count])
|
|
{
|
|
value = [classNamesToKeep componentsJoinedByString:@" "];
|
|
}
|
|
else
|
|
{
|
|
return; // continue enumeration, class attribute would be empty
|
|
}
|
|
}
|
|
|
|
// we preserve existing because they are from children
|
|
[attributedString addHTMLAttribute:key value:value range:entireString replaceExisting:NO];
|
|
}];
|
|
}
|
|
|
|
- (NSAttributedString *)attributedString
|
|
{
|
|
@synchronized(self)
|
|
{
|
|
if (_displayStyle == DTHTMLElementDisplayStyleNone || self.didOutput)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
NSDictionary *attributes = [self attributesForAttributedStringRepresentation];
|
|
|
|
NSMutableAttributedString *tmpString;
|
|
|
|
if (_textAttachment)
|
|
{
|
|
// ignore text, use unicode object placeholder
|
|
tmpString = [[NSMutableAttributedString alloc] initWithString:UNICODE_OBJECT_PLACEHOLDER attributes:attributes];
|
|
}
|
|
else
|
|
{
|
|
// walk through children
|
|
tmpString = [[NSMutableAttributedString alloc] init];
|
|
|
|
DTHTMLElement *previousChild = nil;
|
|
|
|
for (DTHTMLElement *oneChild in self.childNodes)
|
|
{
|
|
if (oneChild.displayStyle == DTHTMLElementDisplayStyleNone)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if previous node was inline and this child is block then we need a newline
|
|
if (previousChild && previousChild.displayStyle == DTHTMLElementDisplayStyleInline)
|
|
{
|
|
if (oneChild.displayStyle == DTHTMLElementDisplayStyleBlock)
|
|
{
|
|
// trim off whitespace suffix
|
|
while ([[tmpString string] hasSuffixCharacterFromSet:[NSCharacterSet ignorableWhitespaceCharacterSet]])
|
|
{
|
|
[tmpString deleteCharactersInRange:NSMakeRange([tmpString length]-1, 1)];
|
|
}
|
|
|
|
// paragraph break
|
|
[tmpString appendString:@"\n"];
|
|
}
|
|
}
|
|
|
|
NSAttributedString *nodeString = [oneChild attributedString];
|
|
|
|
if (nodeString)
|
|
{
|
|
if (!oneChild.containsAppleConvertedSpace)
|
|
{
|
|
// we already have a white space in the string so far
|
|
if ([[tmpString string] hasSuffixCharacterFromSet:[NSCharacterSet ignorableWhitespaceCharacterSet]])
|
|
{
|
|
// following e.g. a BR we don't want a space or NL
|
|
NSCharacterSet *charactersToIgnore = [NSCharacterSet characterSetWithCharactersInString:@" \n\t"];
|
|
|
|
while ([[nodeString string] hasPrefixCharacterFromSet:charactersToIgnore])
|
|
{
|
|
NSString *field = [nodeString attribute:DTFieldAttribute atIndex:0 effectiveRange:NULL];
|
|
|
|
// do not trim off field
|
|
if ([field isEqualToString:DTListPrefixField])
|
|
{
|
|
break;
|
|
}
|
|
|
|
// do not trim off HR character
|
|
BOOL isHR = [[nodeString attribute:DTHorizontalRuleStyleAttribute atIndex:0 effectiveRange:NULL] boolValue];
|
|
|
|
if (isHR)
|
|
{
|
|
break;
|
|
}
|
|
|
|
nodeString = [nodeString attributedSubstringFromRange:NSMakeRange(1, [nodeString length]-1)];
|
|
}
|
|
}
|
|
}
|
|
|
|
[tmpString appendAttributedString:nodeString];
|
|
}
|
|
|
|
previousChild = oneChild;
|
|
}
|
|
}
|
|
|
|
// block-level elements get space trimmed and a newline
|
|
if (_displayStyle != DTHTMLElementDisplayStyleInline)
|
|
{
|
|
// trim off whitespace prefix
|
|
while ([[tmpString string] hasPrefix:@" "])
|
|
{
|
|
[tmpString deleteCharactersInRange:NSMakeRange(0, 1)];
|
|
}
|
|
|
|
// trim off whitespace suffix
|
|
while ([[tmpString string] hasSuffix:@" "])
|
|
{
|
|
[tmpString deleteCharactersInRange:NSMakeRange([tmpString length]-1, 1)];
|
|
}
|
|
|
|
if (![self.name isEqualToString:@"html"] && ![self.name isEqualToString:@"body"])
|
|
{
|
|
if (![[tmpString string] hasSuffix:@"\n"])
|
|
{
|
|
if ([tmpString length])
|
|
{
|
|
// extend font and paragraph style with the \n
|
|
[tmpString appendEndOfParagraph];
|
|
}
|
|
else
|
|
{
|
|
// string is empty, need a new attributed string so that we have the attributes
|
|
attributes = [self attributesForAttributedStringRepresentation];
|
|
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:attributes];
|
|
[tmpString appendAttributedString:attributedString];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure the last sub-paragraph of this has no less than the specified paragraph spacing of this element
|
|
// e.g. last LI needs to inherit the margin-after of the UL
|
|
if (self.displayStyle == DTHTMLElementDisplayStyleBlock && [tmpString length]>0)
|
|
{
|
|
|
|
// if this is latest
|
|
if ([self _isNotChildOfList])
|
|
{
|
|
NSRange paragraphRange = [[tmpString string] rangeOfParagraphAtIndex:[tmpString length]-1];
|
|
|
|
#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES
|
|
if (___useiOS6Attributes)
|
|
{
|
|
NSParagraphStyle *paraStyle = [tmpString attribute:NSParagraphStyleAttributeName atIndex:paragraphRange.location effectiveRange:NULL];
|
|
|
|
DTCoreTextParagraphStyle *paragraphStyle = [DTCoreTextParagraphStyle paragraphStyleWithNSParagraphStyle:paraStyle];
|
|
|
|
if (paragraphStyle.paragraphSpacing < self.paragraphStyle.paragraphSpacing)
|
|
{
|
|
paragraphStyle.paragraphSpacing = self.paragraphStyle.paragraphSpacing;
|
|
|
|
// make new paragraph style
|
|
NSParagraphStyle *newParaStyle = [paragraphStyle NSParagraphStyle];
|
|
|
|
#if DTCORETEXT_NEEDS_ATTRIBUTE_REPLACEMENT_LEAK_FIX
|
|
if (NSFoundationVersionNumber <= NSFoundationVersionNumber10_6_8) // less than OS X 10.7 and less than iOS 5
|
|
{
|
|
// remove old (works around iOS 4.3 leak)
|
|
[tmpString removeAttribute:NSParagraphStyleAttributeName range:paragraphRange];
|
|
}
|
|
#endif
|
|
|
|
// set new
|
|
[tmpString addAttribute:NSParagraphStyleAttributeName value:newParaStyle range:paragraphRange];
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
CTParagraphStyleRef paraStyle = (__bridge CTParagraphStyleRef)[tmpString attribute:(id)kCTParagraphStyleAttributeName atIndex:paragraphRange.location effectiveRange:NULL];
|
|
|
|
DTCoreTextParagraphStyle *paragraphStyle = [DTCoreTextParagraphStyle paragraphStyleWithCTParagraphStyle:paraStyle];
|
|
|
|
if (paragraphStyle.paragraphSpacing < self.paragraphStyle.paragraphSpacing)
|
|
{
|
|
paragraphStyle.paragraphSpacing = self.paragraphStyle.paragraphSpacing;
|
|
|
|
// make new paragraph style
|
|
CTParagraphStyleRef newParaStyle = [paragraphStyle createCTParagraphStyle];
|
|
|
|
#if DTCORETEXT_NEEDS_ATTRIBUTE_REPLACEMENT_LEAK_FIX
|
|
if (NSFoundationVersionNumber <= NSFoundationVersionNumber10_6_8) // less than OS X 10.7 and less than iOS 5
|
|
{
|
|
// remove old (works around iOS 4.3 leak)
|
|
[tmpString removeAttribute:(id)kCTParagraphStyleAttributeName range:paragraphRange];
|
|
}
|
|
#endif
|
|
|
|
// set new
|
|
[tmpString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge_transfer id)newParaStyle range:paragraphRange];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the custom attributes
|
|
if (_shouldProcessCustomHTMLAttributes)
|
|
{
|
|
[self _addCustomHTMLAttributesToAttributedString:tmpString];
|
|
}
|
|
|
|
return tmpString;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Working with CSS Styles
|
|
|
|
// decodes the edgeInsets for padding or margin
|
|
- (BOOL)_parseEdgeInsetsFromStyleDictionary:(NSDictionary *)styles forAttributesWithPrefix:(NSString *)prefix writingDirection:(CTWritingDirection)writingDirection intoEdgeInsets:(DTEdgeInsets *)intoEdgeInsets
|
|
{
|
|
DTEdgeInsets edgeInsets = {0,0,0,0};
|
|
|
|
// preserve previous values in insets
|
|
if (intoEdgeInsets)
|
|
{
|
|
edgeInsets = *intoEdgeInsets;
|
|
}
|
|
|
|
BOOL didModify = NO;
|
|
|
|
if (![styles count])
|
|
{
|
|
return didModify;
|
|
}
|
|
|
|
BOOL isWebKitAttribute = NO;
|
|
|
|
if ([prefix hasPrefix:@"-webkit"])
|
|
{
|
|
isWebKitAttribute = YES;
|
|
}
|
|
|
|
NSString *leftKey = @"-left";
|
|
|
|
if (isWebKitAttribute)
|
|
{
|
|
if (writingDirection==kCTWritingDirectionRightToLeft)
|
|
{
|
|
// RTL
|
|
leftKey = @"-end";
|
|
}
|
|
else
|
|
{
|
|
// LTR
|
|
leftKey = @"-start";
|
|
}
|
|
}
|
|
|
|
NSString *rightKey = @"-right";
|
|
|
|
if (isWebKitAttribute)
|
|
{
|
|
if (writingDirection==kCTWritingDirectionRightToLeft)
|
|
{
|
|
// RTL
|
|
rightKey = @"-start";
|
|
}
|
|
else
|
|
{
|
|
// LTR
|
|
rightKey = @"-end";
|
|
}
|
|
}
|
|
|
|
NSString *topKey = isWebKitAttribute?@"-before":@"-top";
|
|
NSString *bottomKey = isWebKitAttribute?@"-after":@"-bottom";
|
|
|
|
for (NSString *oneKey in styles)
|
|
{
|
|
if (![oneKey hasPrefix:prefix])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
NSString *attributeValue = [styles objectForKey:oneKey];
|
|
|
|
NSRange dashRange = [oneKey rangeOfString:@"-"];
|
|
|
|
if (dashRange.length)
|
|
{
|
|
if ([oneKey hasSuffix:leftKey])
|
|
{
|
|
edgeInsets.left = [attributeValue pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
didModify = YES;
|
|
}
|
|
else if ([oneKey hasSuffix:bottomKey])
|
|
{
|
|
edgeInsets.bottom = [attributeValue pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
didModify = YES;
|
|
}
|
|
else if ([oneKey hasSuffix:rightKey])
|
|
{
|
|
edgeInsets.right = [attributeValue pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
didModify = YES;
|
|
}
|
|
else if ([oneKey hasSuffix:topKey])
|
|
{
|
|
edgeInsets.top = [attributeValue pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
didModify = YES;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// shortcut with multiple values
|
|
edgeInsets = [attributeValue DTEdgeInsetsRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
didModify = YES;
|
|
}
|
|
}
|
|
|
|
if (didModify && intoEdgeInsets)
|
|
{
|
|
*intoEdgeInsets = edgeInsets;
|
|
}
|
|
|
|
return didModify;
|
|
}
|
|
|
|
- (void)applyStyleDictionary:(NSDictionary *)styles
|
|
{
|
|
if (![styles count])
|
|
{
|
|
return;
|
|
}
|
|
|
|
// keep that for later lookup
|
|
_styles = styles;
|
|
|
|
// writing direction
|
|
NSString *directionStr = [_styles objectForKey:@"direction"];
|
|
|
|
if (directionStr)
|
|
{
|
|
if ([directionStr isEqualToString:@"rtl"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionRightToLeft;
|
|
}
|
|
else if ([directionStr isEqualToString:@"ltr"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionLeftToRight;
|
|
}
|
|
else if ([directionStr isEqualToString:@"auto"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionNatural; // that's also default
|
|
}
|
|
else
|
|
{
|
|
// other values are invalid and will be ignored
|
|
}
|
|
}
|
|
|
|
// register pseudo-selector contents
|
|
self.beforeContent = [[_styles objectForKey:@"before:content"] stringByDecodingCSSContentAttribute];
|
|
|
|
NSString *fontSize = [styles objectForKey:@"font-size"];
|
|
if (fontSize)
|
|
{
|
|
// absolute sizes based on 12.0 CoreText default size, Safari has 16.0
|
|
|
|
if ([fontSize isEqualToString:@"smaller"])
|
|
{
|
|
_fontDescriptor.pointSize /= 1.2f;
|
|
}
|
|
else if ([fontSize isEqualToString:@"larger"])
|
|
{
|
|
_fontDescriptor.pointSize *= 1.2f;
|
|
}
|
|
else if ([fontSize isEqualToString:@"xx-small"])
|
|
{
|
|
_fontDescriptor.pointSize = 9.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"x-small"])
|
|
{
|
|
_fontDescriptor.pointSize = 10.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"small"])
|
|
{
|
|
_fontDescriptor.pointSize = 13.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"medium"])
|
|
{
|
|
_fontDescriptor.pointSize = 16.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"large"])
|
|
{
|
|
_fontDescriptor.pointSize = 18.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"x-large"])
|
|
{
|
|
_fontDescriptor.pointSize = 24.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"xx-large"])
|
|
{
|
|
_fontDescriptor.pointSize = 32.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"-webkit-xxx-large"])
|
|
{
|
|
_fontDescriptor.pointSize = 48.0f * _textScale;
|
|
}
|
|
else if ([fontSize isEqualToString:@"inherit"])
|
|
{
|
|
_fontDescriptor.pointSize = self.parentElement.fontDescriptor.pointSize;
|
|
}
|
|
else if ([fontSize isCSSLengthValue])
|
|
{
|
|
_fontDescriptor.pointSize = [fontSize pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_currentTextSize textScale:_textScale];
|
|
}
|
|
}
|
|
|
|
NSString *color = [styles objectForKey:@"color"];
|
|
if (color)
|
|
{
|
|
self.textColor = DTColorCreateWithHTMLName(color);
|
|
}
|
|
|
|
NSString *bgColor = [styles objectForKey:@"background-color"];
|
|
if (bgColor)
|
|
{
|
|
self.backgroundColor = DTColorCreateWithHTMLName(bgColor);
|
|
}
|
|
|
|
NSString *floatString = [styles objectForKey:@"float"];
|
|
|
|
if (floatString)
|
|
{
|
|
if ([floatString isEqualToString:@"left"])
|
|
{
|
|
_floatStyle = DTHTMLElementFloatStyleLeft;
|
|
}
|
|
else if ([floatString isEqualToString:@"right"])
|
|
{
|
|
_floatStyle = DTHTMLElementFloatStyleRight;
|
|
}
|
|
else if ([floatString isEqualToString:@"none"])
|
|
{
|
|
_floatStyle = DTHTMLElementFloatStyleNone;
|
|
}
|
|
}
|
|
|
|
id fontFamilyStyle = [styles objectForKey:@"font-family"];
|
|
|
|
if (fontFamilyStyle)
|
|
{
|
|
NSArray *fontFamilies;
|
|
|
|
if ([fontFamilyStyle isKindOfClass:[NSString class]])
|
|
{
|
|
fontFamilies = [NSArray arrayWithObject:fontFamilyStyle];
|
|
}
|
|
else if ([fontFamilyStyle isKindOfClass:[NSArray class]])
|
|
{
|
|
fontFamilies = fontFamilyStyle;
|
|
}
|
|
|
|
BOOL foundFontFamily = NO;
|
|
|
|
for (NSString *fontFamily in fontFamilies)
|
|
{
|
|
_fontDescriptor.fontFamily = fontFamily;
|
|
|
|
// check if this is a known font family
|
|
CTFontRef font = [_fontDescriptor newMatchingFont];
|
|
NSString *foundFamily = CFBridgingRelease(CTFontCopyFamilyName(font));
|
|
|
|
if ([foundFamily isEqualToString:fontFamily])
|
|
{
|
|
foundFontFamily = YES;
|
|
break;
|
|
}
|
|
|
|
NSString *lowercaseFontFamily = [fontFamily lowercaseString];
|
|
|
|
if ([lowercaseFontFamily rangeOfString:@"geneva"].length)
|
|
{
|
|
_fontDescriptor.fontFamily = @"Helvetica";
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"cursive"].length)
|
|
{
|
|
_fontDescriptor.stylisticClass = kCTFontScriptsClass;
|
|
_fontDescriptor.fontFamily = nil;
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"sans-serif"].length)
|
|
{
|
|
// too many matches (24)
|
|
// fontDescriptor.stylisticClass = kCTFontSansSerifClass;
|
|
_fontDescriptor.fontFamily = @"Helvetica";
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"serif"].length)
|
|
{
|
|
// kCTFontTransitionalSerifsClass = Baskerville
|
|
// kCTFontClarendonSerifsClass = American Typewriter
|
|
// kCTFontSlabSerifsClass = Courier New
|
|
//
|
|
// strangely none of the classes yields Times
|
|
_fontDescriptor.fontFamily = @"Times New Roman";
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"fantasy"].length)
|
|
{
|
|
_fontDescriptor.fontFamily = @"Papyrus"; // only available on iPad
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"monospace"].length)
|
|
{
|
|
_fontDescriptor.monospaceTrait = YES;
|
|
_fontDescriptor.fontFamily = @"Courier";
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily rangeOfString:@"times"].length)
|
|
{
|
|
_fontDescriptor.fontFamily = @"Times New Roman";
|
|
foundFontFamily = YES;
|
|
}
|
|
else if ([lowercaseFontFamily isEqualToString:@"inherit"])
|
|
{
|
|
_fontDescriptor.fontFamily = self.parentElement.fontDescriptor.fontFamily;
|
|
foundFontFamily = YES;
|
|
}
|
|
|
|
if (foundFontFamily)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundFontFamily)
|
|
{
|
|
// probably custom font registered in info.plist
|
|
_fontDescriptor.fontFamily = [fontFamilies objectAtIndex:0];
|
|
}
|
|
}
|
|
|
|
NSString *fontStyle = [[styles objectForKey:@"font-style"] lowercaseString];
|
|
if (fontStyle)
|
|
{
|
|
// remove font name since this would cause font creation to ignore the trait
|
|
_fontDescriptor.fontName = nil;
|
|
|
|
if ([fontStyle isEqualToString:@"normal"])
|
|
{
|
|
_fontDescriptor.italicTrait = NO;
|
|
}
|
|
else if ([fontStyle isEqualToString:@"italic"] || [fontStyle isEqualToString:@"oblique"])
|
|
{
|
|
_fontDescriptor.italicTrait = YES;
|
|
}
|
|
else if ([fontStyle isEqualToString:@"inherit"])
|
|
{
|
|
// nothing to do
|
|
}
|
|
}
|
|
|
|
NSString *fontWeight = [[styles objectForKey:@"font-weight"] lowercaseString];
|
|
if (fontWeight)
|
|
{
|
|
// remove font name since this would cause font creation to ignore the trait
|
|
_fontDescriptor.fontName = nil;
|
|
|
|
if ([fontWeight isEqualToString:@"normal"])
|
|
{
|
|
_fontDescriptor.boldTrait = NO;
|
|
}
|
|
else if ([fontWeight isEqualToString:@"bold"])
|
|
{
|
|
_fontDescriptor.boldTrait = YES;
|
|
}
|
|
else if ([fontWeight isEqualToString:@"bolder"])
|
|
{
|
|
_fontDescriptor.boldTrait = YES;
|
|
}
|
|
else if ([fontWeight isEqualToString:@"lighter"])
|
|
{
|
|
_fontDescriptor.boldTrait = NO;
|
|
}
|
|
else
|
|
{
|
|
// can be 100 - 900
|
|
|
|
NSInteger value = [fontWeight intValue];
|
|
|
|
if (value<=600)
|
|
{
|
|
_fontDescriptor.boldTrait = NO;
|
|
}
|
|
else
|
|
{
|
|
_fontDescriptor.boldTrait = YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
NSString *decoration = [[styles objectForKey:@"text-decoration"] lowercaseString];
|
|
if (decoration)
|
|
{
|
|
if ([decoration isEqualToString:@"underline"])
|
|
{
|
|
self.underlineStyle = kCTUnderlineStyleSingle;
|
|
}
|
|
else if ([decoration isEqualToString:@"line-through"])
|
|
{
|
|
self.strikeOut = YES;
|
|
}
|
|
else if ([decoration isEqualToString:@"none"])
|
|
{
|
|
// remove all
|
|
self.underlineStyle = kCTUnderlineStyleNone;
|
|
self.strikeOut = NO;
|
|
}
|
|
else if ([decoration isEqualToString:@"overline"])
|
|
{
|
|
DTLogInfo(@"Note: 'overline' text decoration not supported");
|
|
}
|
|
else if ([decoration isEqualToString:@"blink"])
|
|
{
|
|
DTLogInfo(@"Note: 'blink' text decoration not supported");
|
|
}
|
|
else if ([decoration isEqualToString:@"inherit"])
|
|
{
|
|
// nothing to do
|
|
}
|
|
}
|
|
|
|
NSString *alignment = [[styles objectForKey:@"text-align"] lowercaseString];
|
|
if (alignment)
|
|
{
|
|
if ([alignment isEqualToString:@"left"])
|
|
{
|
|
self.paragraphStyle.alignment = kCTLeftTextAlignment;
|
|
}
|
|
else if ([alignment isEqualToString:@"right"])
|
|
{
|
|
self.paragraphStyle.alignment = kCTRightTextAlignment;
|
|
}
|
|
else if ([alignment isEqualToString:@"center"])
|
|
{
|
|
self.paragraphStyle.alignment = kCTCenterTextAlignment;
|
|
}
|
|
else if ([alignment isEqualToString:@"justify"])
|
|
{
|
|
self.paragraphStyle.alignment = kCTJustifiedTextAlignment;
|
|
}
|
|
else if ([alignment isEqualToString:@"inherit"])
|
|
{
|
|
// nothing to do
|
|
}
|
|
}
|
|
|
|
NSString *verticalAlignment = [[styles objectForKey:@"vertical-align"] lowercaseString];
|
|
if (verticalAlignment)
|
|
{
|
|
if ([verticalAlignment isEqualToString:@"sub"])
|
|
{
|
|
self.superscriptStyle = -1;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"super"])
|
|
{
|
|
self.superscriptStyle = +1;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"baseline"])
|
|
{
|
|
self.superscriptStyle = 0;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"inherit"])
|
|
{
|
|
// nothing to do
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"text-top"])
|
|
{
|
|
_textAttachmentAlignment = DTTextAttachmentVerticalAlignmentTop;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"middle"])
|
|
{
|
|
_textAttachmentAlignment = DTTextAttachmentVerticalAlignmentCenter;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"text-bottom"])
|
|
{
|
|
_textAttachmentAlignment = DTTextAttachmentVerticalAlignmentBottom;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"baseline"])
|
|
{
|
|
_textAttachmentAlignment = DTTextAttachmentVerticalAlignmentBaseline;
|
|
}
|
|
}
|
|
|
|
NSString *letterSpacing = [[styles objectForKey:@"letter-spacing"] lowercaseString];
|
|
if (letterSpacing)
|
|
{
|
|
if ([letterSpacing isEqualToString:@"normal"])
|
|
{
|
|
_letterSpacing = 0;
|
|
}
|
|
else if ([letterSpacing isEqualToString:@"inherit"])
|
|
{
|
|
// no op, we already inherited it
|
|
}
|
|
else // interpret as length
|
|
{
|
|
_letterSpacing = [letterSpacing pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
}
|
|
}
|
|
|
|
// if there is a text attachment we transfer the aligment we got
|
|
_textAttachment.verticalAlignment = _textAttachmentAlignment;
|
|
|
|
id shadow = [styles objectForKey:@"text-shadow"];
|
|
if (shadow)
|
|
{
|
|
|
|
self.shadows = [shadow arrayOfCSSShadowsWithCurrentTextSize:_fontDescriptor.pointSize currentColor:_textColor];
|
|
}
|
|
|
|
NSString *lineHeight = [[styles objectForKey:@"line-height"] lowercaseString];
|
|
if (lineHeight)
|
|
{
|
|
if ([lineHeight isEqualToString:@"normal"])
|
|
{
|
|
self.paragraphStyle.lineHeightMultiple = 0.0; // default
|
|
self.paragraphStyle.minimumLineHeight = 0.0; // default
|
|
self.paragraphStyle.maximumLineHeight = 0.0; // default
|
|
}
|
|
else if ([lineHeight isEqualToString:@"inherit"])
|
|
{
|
|
// no op, we already inherited it
|
|
}
|
|
else if ([lineHeight isNumeric])
|
|
{
|
|
self.paragraphStyle.lineHeightMultiple = [lineHeight floatValue];
|
|
}
|
|
else // interpret as length
|
|
{
|
|
CGFloat lineHeightValue = [lineHeight pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
self.paragraphStyle.minimumLineHeight = lineHeightValue;
|
|
self.paragraphStyle.maximumLineHeight = lineHeightValue;
|
|
}
|
|
}
|
|
|
|
NSString *fontVariantStr = [[styles objectForKey:@"font-variant"] lowercaseString];
|
|
if (fontVariantStr)
|
|
{
|
|
if ([fontVariantStr isEqualToString:@"small-caps"])
|
|
{
|
|
_fontVariant = DTHTMLElementFontVariantSmallCaps;
|
|
}
|
|
else if ([fontVariantStr isEqualToString:@"inherit"])
|
|
{
|
|
_fontVariant = DTHTMLElementFontVariantInherit;
|
|
}
|
|
else
|
|
{
|
|
_fontVariant = DTHTMLElementFontVariantNormal;
|
|
}
|
|
}
|
|
|
|
NSString *widthString = [styles objectForKey:@"width"];
|
|
if (widthString && ![widthString isEqualToString:@"auto"])
|
|
{
|
|
_size.width = [widthString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
}
|
|
|
|
NSString *heightString = [styles objectForKey:@"height"];
|
|
if (heightString && ![heightString isEqualToString:@"auto"])
|
|
{
|
|
_size.height = [heightString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
|
|
}
|
|
|
|
NSString *whitespaceString = [styles objectForKey:@"white-space"];
|
|
if ([whitespaceString hasPrefix:@"pre"])
|
|
{
|
|
_preserveNewlines = YES;
|
|
}
|
|
else
|
|
{
|
|
_preserveNewlines = NO;
|
|
}
|
|
|
|
NSString *displayString = [styles objectForKey:@"display"];
|
|
if (displayString)
|
|
{
|
|
if ([displayString isEqualToString:@"none"])
|
|
{
|
|
_displayStyle = DTHTMLElementDisplayStyleNone;
|
|
}
|
|
else if ([displayString isEqualToString:@"block"])
|
|
{
|
|
_displayStyle = DTHTMLElementDisplayStyleBlock;
|
|
}
|
|
else if ([displayString isEqualToString:@"inline"])
|
|
{
|
|
_displayStyle = DTHTMLElementDisplayStyleInline;
|
|
}
|
|
else if ([displayString isEqualToString:@"list-item"])
|
|
{
|
|
_displayStyle = DTHTMLElementDisplayStyleListItem;
|
|
}
|
|
else if ([displayString isEqualToString:@"table"])
|
|
{
|
|
_displayStyle = DTHTMLElementDisplayStyleTable;
|
|
}
|
|
else if ([verticalAlignment isEqualToString:@"inherit"])
|
|
{
|
|
// nothing to do
|
|
}
|
|
}
|
|
|
|
NSString *borderColor = [styles objectForKey:@"border-color"];
|
|
if (borderColor)
|
|
{
|
|
self.backgroundStrokeColor = DTColorCreateWithHTMLName(borderColor);
|
|
}
|
|
NSString *borderWidth = [[styles objectForKey:@"border-width"] lowercaseString];
|
|
if (borderWidth)
|
|
{
|
|
_backgroundStrokeWidth = [borderWidth floatValue];
|
|
}
|
|
else {
|
|
_backgroundStrokeWidth = 0.0f;
|
|
}
|
|
NSString *cornerRadius = [[styles objectForKey:@"border-radius"] lowercaseString];
|
|
if (cornerRadius)
|
|
{
|
|
_backgroundCornerRadius = [cornerRadius floatValue];
|
|
}
|
|
else {
|
|
_backgroundCornerRadius = 0.0f;
|
|
}
|
|
|
|
BOOL needsTextBlock = (_backgroundColor!=nil || _backgroundStrokeColor!=nil || _backgroundCornerRadius > 0 || _backgroundStrokeWidth > 0);
|
|
|
|
BOOL hasMargins = NO;
|
|
|
|
NSString *allKeys = [[styles allKeys] componentsJoinedByString:@";"];
|
|
|
|
// there can only be padding if the word "margin" occurs in the styles keys
|
|
if ([allKeys rangeOfString:@"-webkit-margin"].length)
|
|
{
|
|
hasMargins = ([self _parseEdgeInsetsFromStyleDictionary:styles forAttributesWithPrefix:@"-webkit-margin" writingDirection:self.paragraphStyle.baseWritingDirection intoEdgeInsets:&_margins] || hasMargins);
|
|
}
|
|
|
|
if ([allKeys rangeOfString:@"margin"].length)
|
|
{
|
|
hasMargins = ([self _parseEdgeInsetsFromStyleDictionary:styles forAttributesWithPrefix:@"margin" writingDirection:self.paragraphStyle.baseWritingDirection intoEdgeInsets:&_margins] || hasMargins);
|
|
}
|
|
|
|
BOOL hasPadding = NO;
|
|
|
|
// there can only be padding if the word "padding" occurs in the styles keys
|
|
if ([allKeys rangeOfString:@"-webkit-padding"].length)
|
|
{
|
|
hasPadding = ([self _parseEdgeInsetsFromStyleDictionary:styles forAttributesWithPrefix:@"-webkit-padding" writingDirection:self.paragraphStyle.baseWritingDirection intoEdgeInsets:&_padding] || hasPadding);
|
|
}
|
|
|
|
if ([allKeys rangeOfString:@"padding"].length)
|
|
{
|
|
|
|
hasPadding = ([self _parseEdgeInsetsFromStyleDictionary:styles forAttributesWithPrefix:@"padding" writingDirection:self.paragraphStyle.baseWritingDirection intoEdgeInsets:&_padding] || hasPadding);
|
|
}
|
|
|
|
if (hasPadding)
|
|
{
|
|
if ([self.name isEqualToString:@"ul"] || [self.name isEqualToString:@"ol"])
|
|
{
|
|
_listIndent = _padding.left;
|
|
_padding.left = 0;
|
|
}
|
|
|
|
// if we still have padding we need a block
|
|
if (_padding.left>0 || _padding.right>0 || _padding.top>0 || _padding.bottom>0)
|
|
{
|
|
needsTextBlock = YES;
|
|
}
|
|
}
|
|
|
|
if (_displayStyle == DTHTMLElementDisplayStyleBlock)
|
|
{
|
|
// we only care for margins of block level elements
|
|
if (hasMargins)
|
|
{
|
|
self.paragraphStyle.paragraphSpacing = _margins.bottom;
|
|
|
|
// we increase the inherited values for the time being
|
|
self.paragraphStyle.headIndent += _margins.left;
|
|
self.paragraphStyle.firstLineHeadIndent = self.paragraphStyle.headIndent;
|
|
|
|
// tailIndent from right side is negative
|
|
self.paragraphStyle.tailIndent -= _margins.right;
|
|
}
|
|
|
|
if (needsTextBlock)
|
|
{
|
|
// need a block
|
|
DTTextBlock *newBlock = [[DTTextBlock alloc] init];
|
|
|
|
newBlock.padding = _padding;
|
|
|
|
// transfer background color to block
|
|
newBlock.backgroundColor = _backgroundColor;
|
|
_backgroundColor = nil;
|
|
|
|
if (self.paragraphStyle.textBlocks)
|
|
{
|
|
// make mutable version
|
|
NSMutableArray *mutableBlocks = [self.paragraphStyle.textBlocks mutableCopy];
|
|
|
|
// add new block to the array
|
|
[mutableBlocks addObject:newBlock];
|
|
|
|
// set non-mutable version
|
|
self.paragraphStyle.textBlocks = [mutableBlocks copy];
|
|
}
|
|
else
|
|
{
|
|
// didn't have any blocks before, start new array
|
|
NSArray *blocks = [NSArray arrayWithObject:newBlock];
|
|
self.paragraphStyle.textBlocks = blocks;
|
|
}
|
|
}
|
|
}
|
|
else if (_displayStyle == DTHTMLElementDisplayStyleListItem)
|
|
{
|
|
self.paragraphStyle.paragraphSpacing = _margins.bottom;
|
|
}
|
|
}
|
|
|
|
- (DTCSSListStyle *)listStyle
|
|
{
|
|
DTCSSListStyle *style = [[DTCSSListStyle alloc] initWithStyles:_styles];
|
|
|
|
NSString *startingIndex = [_attributes objectForKey:@"start"];
|
|
|
|
// set the starting index if there is one specified
|
|
if (startingIndex)
|
|
{
|
|
style.startingItemNumber = [startingIndex integerValue];
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
#pragma mark - Calulcating Properties
|
|
|
|
- (id)valueForKeyPathWithInheritance:(NSString *)keyPath
|
|
{
|
|
id value = [self valueForKeyPath:keyPath];
|
|
|
|
// if property is not set we also go to parent
|
|
if (!value && self.parentElement)
|
|
{
|
|
return [self.parentElement valueForKeyPathWithInheritance:keyPath];
|
|
}
|
|
|
|
// enum properties have 0 for inherit
|
|
if ([value isKindOfClass:[NSNumber class]])
|
|
{
|
|
NSNumber *number = value;
|
|
|
|
if (([number integerValue]==0) && self.parentElement)
|
|
{
|
|
return [self.parentElement valueForKeyPathWithInheritance:keyPath];
|
|
}
|
|
}
|
|
|
|
// string properties have 'inherit' for inheriting
|
|
if ([value isKindOfClass:[NSString class]])
|
|
{
|
|
NSString *string = value;
|
|
|
|
if ([string isEqualToString:@"inherit"] && self.parentElement)
|
|
{
|
|
return [self.parentElement valueForKeyPathWithInheritance:keyPath];
|
|
}
|
|
}
|
|
|
|
// obviously not inherited
|
|
return value;
|
|
}
|
|
|
|
#pragma mark - Working with HTML Attributes
|
|
|
|
+ (NSSet *)attributesToIgnoreForCustomAttributesAttribute
|
|
{
|
|
return [NSSet setWithObjects:@"style", @"dir", @"align", @"src", @"href", @"color", @"face", @"size", @"name", @"height", @"width", nil];
|
|
}
|
|
|
|
- (NSString *)attributeForKey:(NSString *)key
|
|
{
|
|
return [_attributes objectForKey:key];
|
|
}
|
|
|
|
- (void)inheritAttributesFromElement:(DTHTMLElement *)element
|
|
{
|
|
_fontDescriptor = [element.fontDescriptor copy];
|
|
_paragraphStyle = [element.paragraphStyle copy];
|
|
|
|
_headerLevel = element.headerLevel;
|
|
|
|
_fontVariant = element.fontVariant;
|
|
_underlineStyle = element.underlineStyle;
|
|
_strikeOut = element.strikeOut;
|
|
_superscriptStyle = element.superscriptStyle;
|
|
_letterSpacing = element.letterSpacing;
|
|
|
|
_shadows = [element.shadows copy];
|
|
|
|
_link = [element.link copy];
|
|
_anchorName = [element.anchorName copy];
|
|
_linkGUID = element.linkGUID;
|
|
|
|
_textColor = element.textColor;
|
|
_isColorInherited = YES;
|
|
|
|
_preserveNewlines = element.preserveNewlines;
|
|
|
|
_currentTextSize = element.currentTextSize;
|
|
_textScale = element.textScale;
|
|
|
|
_backgroundColor = element.backgroundColor;
|
|
_backgroundStrokeColor = element.backgroundStrokeColor;
|
|
_backgroundStrokeWidth = element.backgroundStrokeWidth;
|
|
_backgroundCornerRadius = element.backgroundCornerRadius;
|
|
|
|
// only inherit background-color from inline elements
|
|
if (element.displayStyle == DTHTMLElementDisplayStyleInline || element.displayStyle == DTHTMLElementDisplayStyleListItem)
|
|
{
|
|
self.backgroundColor = element.backgroundColor;
|
|
}
|
|
|
|
_containsAppleConvertedSpace = element.containsAppleConvertedSpace;
|
|
|
|
// we copy the link because we might need for it making the custom view
|
|
_textAttachment.hyperLinkURL = element.link;
|
|
}
|
|
|
|
- (void)interpretAttributes
|
|
{
|
|
if (!_attributes)
|
|
{
|
|
// nothing to interpret
|
|
return;
|
|
}
|
|
|
|
// transfer Apple Converted Space tag
|
|
if ([[self attributeForKey:@"class"] isEqualToString:@"Apple-converted-space"])
|
|
{
|
|
_containsAppleConvertedSpace = YES;
|
|
}
|
|
|
|
// detect writing direction if set
|
|
NSString *directionStr = [self attributeForKey:@"dir"];
|
|
|
|
if (directionStr)
|
|
{
|
|
NSAssert(_paragraphStyle, @"Found dir attribute, but missing paragraph style on element");
|
|
|
|
if ([directionStr isEqualToString:@"rtl"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionRightToLeft;
|
|
}
|
|
else if ([directionStr isEqualToString:@"ltr"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionLeftToRight;
|
|
}
|
|
else if ([directionStr isEqualToString:@"auto"])
|
|
{
|
|
_paragraphStyle.baseWritingDirection = NSWritingDirectionNatural; // that's also default
|
|
}
|
|
else
|
|
{
|
|
// other values are invalid and will be ignored
|
|
}
|
|
}
|
|
|
|
// handles align="justify"
|
|
NSString *align = [self attributeForKey:@"align"];
|
|
|
|
if (align)
|
|
{
|
|
if ([align isEqualToString: @"justify"])
|
|
{
|
|
_paragraphStyle.alignment = kCTTextAlignmentJustified;
|
|
}
|
|
else if ([align isEqualToString: @"left"])
|
|
{
|
|
_paragraphStyle.alignment = kCTTextAlignmentLeft;
|
|
}
|
|
else if ([align isEqualToString: @"center"])
|
|
{
|
|
_paragraphStyle.alignment = kCTTextAlignmentCenter;
|
|
}
|
|
else if ([align isEqualToString: @"right"])
|
|
{
|
|
_paragraphStyle.alignment = kCTTextAlignmentRight;
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (void)setTextColor:(DTColor *)textColor
|
|
{
|
|
if (_textColor != textColor)
|
|
{
|
|
|
|
_textColor = textColor;
|
|
_isColorInherited = NO;
|
|
}
|
|
}
|
|
|
|
- (DTHTMLElementFontVariant)fontVariant
|
|
{
|
|
if (_fontVariant == DTHTMLElementFontVariantInherit)
|
|
{
|
|
if (self.parentElement)
|
|
{
|
|
return self.parentElement.fontVariant;
|
|
}
|
|
|
|
return DTHTMLElementFontVariantNormal;
|
|
}
|
|
|
|
return _fontVariant;
|
|
}
|
|
|
|
- (void)setAttributes:(NSDictionary *)attributes
|
|
{
|
|
[super setAttributes:[attributes copy]];
|
|
|
|
// decode size contained in attributes, might be overridden later by CSS size
|
|
_size = CGSizeMake([[self attributeForKey:@"width"] floatValue], [[self attributeForKey:@"height"] floatValue]);
|
|
}
|
|
|
|
- (void)setTextAttachment:(DTTextAttachment *)textAttachment
|
|
{
|
|
textAttachment.verticalAlignment = _textAttachmentAlignment;
|
|
_textAttachment = textAttachment;
|
|
|
|
// transfer link GUID
|
|
_textAttachment.hyperLinkGUID = _linkGUID;
|
|
|
|
// transfer size
|
|
_textAttachment.displaySize = _size;
|
|
}
|
|
|
|
- (void)setLink:(NSURL *)link
|
|
{
|
|
_linkGUID = [NSString stringWithUUID];
|
|
_link = [link copy];
|
|
|
|
if (_textAttachment)
|
|
{
|
|
_textAttachment.hyperLinkGUID = _linkGUID;
|
|
}
|
|
}
|
|
|
|
- (void)setDidOutput:(BOOL)didOutput
|
|
{
|
|
@synchronized(self)
|
|
{
|
|
_didOutput = didOutput;
|
|
}
|
|
}
|
|
|
|
- (BOOL)didOutput
|
|
{
|
|
@synchronized(self)
|
|
{
|
|
return _didOutput;
|
|
}
|
|
}
|
|
|
|
- (void)setCurrentTextSize:(CGFloat)currentTextSize {
|
|
_currentTextSize = currentTextSize;
|
|
}
|
|
|
|
- (CGFloat)currentTextSize {
|
|
CGFloat textSize = _currentTextSize;
|
|
|
|
if ((_currentTextSize == 0) && self.parentElement) {
|
|
textSize = self.parentElement.currentTextSize;
|
|
}
|
|
|
|
return textSize;
|
|
}
|
|
|
|
- (DTHTMLElement *)parentElement
|
|
{
|
|
return (DTHTMLElement *)self.parentNode;
|
|
}
|
|
|
|
@synthesize fontDescriptor = _fontDescriptor;
|
|
@synthesize paragraphStyle = _paragraphStyle;
|
|
@synthesize textColor = _textColor;
|
|
@synthesize backgroundColor = _backgroundColor;
|
|
@synthesize beforeContent = _beforeContent;
|
|
@synthesize link = _link;
|
|
@synthesize anchorName = _anchorName;
|
|
@synthesize underlineStyle = _underlineStyle;
|
|
@synthesize textAttachment = _textAttachment;
|
|
@synthesize strikeOut = _strikeOut;
|
|
@synthesize superscriptStyle = _superscriptStyle;
|
|
@synthesize headerLevel = _headerLevel;
|
|
@synthesize shadows = _shadows;
|
|
@synthesize floatStyle = _floatStyle;
|
|
@synthesize isColorInherited = _isColorInherited;
|
|
@synthesize preserveNewlines = _preserveNewlines;
|
|
@synthesize displayStyle = _displayStyle;
|
|
@synthesize fontVariant = _fontVariant;
|
|
@synthesize currentTextSize = _currentTextSize;
|
|
@synthesize textScale = _textScale;
|
|
@synthesize size = _size;
|
|
@synthesize margins = _margins;
|
|
@synthesize padding = _padding;
|
|
@synthesize linkGUID = _linkGUID;
|
|
@synthesize containsAppleConvertedSpace = _containsAppleConvertedSpace;
|
|
@synthesize CSSClassNamesToIgnoreForCustomAttributes = _CSSClassNamesToIgnoreForCustomAttributes;
|
|
@synthesize shouldProcessCustomHTMLAttributes = _shouldProcessCustomHTMLAttributes;
|
|
@synthesize backgroundStrokeColor = _backgroundStrokeColor;
|
|
@synthesize backgroundStrokeWidth = _backgroundStrokeWidth;
|
|
@synthesize backgroundCornerRadius = _backgroundCornerRadius;
|
|
@synthesize letterSpacing = _letterSpacing;
|
|
|
|
@end
|
|
|
|
|