Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
1206 lines (1029 sloc) 31.8 KB
//
// 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 "DTHTMLElementAttachment.h"
#import "DTHTMLElementBR.h"
#import "DTHTMLElementHR.h"
#import "DTHTMLElementLI.h"
#import "DTHTMLElementStylesheet.h"
#import "DTHTMLElementText.h"
@interface DTHTMLElement ()
@property (nonatomic, strong) NSMutableDictionary *fontCache;
@property (nonatomic, strong) NSString *linkGUID;
- (DTCSSListStyle *)calculatedListStyle;
// internal initializer
- (id)initWithName:(NSString *)name attributes:(NSDictionary *)attributes options:(NSDictionary *)options;
@end
BOOL ___shouldUseiOS6Attributes = NO;
NSDictionary *_classesForNames = nil;
@implementation DTHTMLElement
+ (void)initialize
{
// lookup table so that we quickly get the correct class to instantiate for special tags
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
[tmpDict setObject:[DTHTMLElementBR class] forKey:@"br"];
[tmpDict setObject:[DTHTMLElementHR class] forKey:@"hr"];
[tmpDict setObject:[DTHTMLElementLI class] forKey:@"li"];
[tmpDict setObject:[DTHTMLElementStylesheet class] forKey:@"style"];
[tmpDict setObject:[DTHTMLElementAttachment class] forKey:@"img"];
[tmpDict setObject:[DTHTMLElementAttachment class] forKey:@"object"];
[tmpDict setObject:[DTHTMLElementAttachment class] forKey:@"video"];
[tmpDict setObject:[DTHTMLElementAttachment class] forKey:@"iframe"];
_classesForNames = [tmpDict copy];
}
+ (DTHTMLElement *)elementWithName:(NSString *)name attributes:(NSDictionary *)attributes options:(NSDictionary *)options
{
// look for specialized class
Class class = [_classesForNames objectForKey:name];
// use generic of none found
if (!class)
{
class = [DTHTMLElement class];
}
DTHTMLElement *element = [[class alloc] initWithName:name attributes:attributes options:options];
return element;
}
- (id)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;
}
- (NSDictionary *)attributesDictionary
{
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
BOOL shouldAddFont = YES;
// copy additional attributes
if (_additionalAttributes)
{
[tmpDict setDictionary:_additionalAttributes];
}
// 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:[NSNumber numberWithFloat:self.paragraphStyle.paragraphSpacing] forKey:DTAttachmentParagraphSpacingAttribute];
#ifndef DT_ADD_FONT_ON_ATTACHMENTS
// omit adding a font unless we need it also on attachments, e.g. for editing
shouldAddFont = NO;
#endif
}
// otherwise we have a font
if (shouldAddFont)
{
CTFontRef font = [_fontDescriptor newMatchingFont];
if (font)
{
#if __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 __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
if (___useiOS6Attributes)
{
[tmpDict setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
}
else
#endif
{
[tmpDict setObject:[NSNumber numberWithBool:YES] forKey:DTStrikeOutAttribute];
}
}
// set underline style
if (_underlineStyle)
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
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 __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
if (___useiOS6Attributes)
{
[tmpDict setObject:_textColor forKey:NSForegroundColorAttributeName];
}
else
#endif
{
[tmpDict setObject:(id)[_textColor CGColor] forKey:(id)kCTForegroundColorAttributeName];
}
}
if (_backgroundColor)
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
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 __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
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 __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1
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];
}
}
// add tag for PRE so that we can omit changing this font if we override fonts
if (_preserveNewlines)
{
[tmpDict setObject:[NSNumber numberWithBool:YES] forKey:DTPreserveNewlinesAttribute];
}
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];
}
return tmpDict;
}
/*
- (void)appendToAttributedString:(NSMutableAttributedString *)attributedString
{
if (_displayStyle == DTHTMLElementDisplayStyleNone || _didOutput)
{
return;
}
NSDictionary *attributes = [self attributesDictionary];
if (_textAttachment)
{
// ignore children, use unicode object placeholder
NSMutableAttributedString *tmpString = [[NSMutableAttributedString alloc] initWithString:UNICODE_OBJECT_PLACEHOLDER attributes:attributes];
[attributedString appendAttributedString:tmpString];
}
else
{
for (id oneChild in self.childNodes)
{
// the string for this single child
NSAttributedString *tmpString = nil;
if ([oneChild isKindOfClass:[DTHTMLParserTextNode class]])
{
[attributedString appendAttributedString:tmpString];
}
else
{
NSAttributedString *tmpString = [oneChild attributedString];
[attributedString appendAttributedString:tmpString];
//
// if ([[oneChild name] isEqualToString:@"br"])
// {
// [attributedString appendString:UNICODE_LINE_FEED];
// }
//
// // should be a normal node
// [oneChild appendToAttributedString:attributedString];
}
}
}
if (_displayStyle != DTHTMLElementDisplayStyleInline)
{
if (![self.name isEqualToString:@"body"] && ![self.name isEqualToString:@"html"])
{
[attributedString appendString:@"\n"];
}
}
_didOutput = YES;
}
*/
- (BOOL)needsOutput
{
if ([self.childNodes count])
{
for (DTHTMLElement *oneChild in self.childNodes)
{
if (!oneChild.didOutput)
{
return YES;
}
}
return NO;
}
return YES;
}
- (NSAttributedString *)attributedString
{
if (_displayStyle == DTHTMLElementDisplayStyleNone || _didOutput)
{
return nil;
}
NSDictionary *attributes = [self attributesDictionary];
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 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 whitespaceAndNewlineCharacterSet]])
{
[tmpString deleteCharactersInRange:NSMakeRange([tmpString length]-1, 1)];
}
// paragraph break
[tmpString appendString:@"\n"];
}
}
NSAttributedString *nodeString = [oneChild attributedString];
if (nodeString)
{
// we already have a white space in the string so far
if ([[tmpString string] hasSuffixCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]])
{
while ([[nodeString string] hasPrefix:@" "])
{
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"])
{
[tmpString appendString:@"\n"];
}
}
}
return tmpString;
}
- (DTHTMLElement *)parentElement
{
return (DTHTMLElement *)self.parentNode;
}
- (BOOL)containedInBlock
{
id element = self;
while (element && ![[element name] isEqualToString:@"body"])
{
if ([element displayStyle] == DTHTMLElementDisplayStyleBlock)
{
return YES;
}
element = [element parentNode];
}
return NO;
}
- (void)applyStyleDictionary:(NSDictionary *)styles
{
if (![styles count])
{
return;
}
// keep that for later lookup
_styles = styles;
// 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/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"x-small"])
{
_fontDescriptor.pointSize = 10.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"small"])
{
_fontDescriptor.pointSize = 13.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"medium"])
{
_fontDescriptor.pointSize = 16.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"large"])
{
_fontDescriptor.pointSize = 22.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"x-large"])
{
_fontDescriptor.pointSize = 24.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"xx-large"])
{
_fontDescriptor.pointSize = 37.0f/1.3333f * _textScale;
}
else if ([fontSize isEqualToString:@"inherit"])
{
_fontDescriptor.pointSize = _parent.fontDescriptor.pointSize;
}
else
{
CGFloat fontSizeValue = [fontSize pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_fontDescriptor.pointSize textScale:_textScale];
_fontDescriptor.pointSize = fontSizeValue;
}
}
NSString *color = [styles objectForKey:@"color"];
if (color)
{
self.textColor = [DTColor colorWithHTMLName:color];
}
NSString *bgColor = [styles objectForKey:@"background-color"];
if (bgColor)
{
self.backgroundColor = [DTColor colorWithHTMLName: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;
}
}
NSString *fontFamily = [[styles objectForKey:@"font-family"] stringByTrimmingCharactersInSet:[NSCharacterSet quoteCharacterSet]];
if (fontFamily)
{
NSString *lowercaseFontFamily = [fontFamily lowercaseString];
if ([lowercaseFontFamily rangeOfString:@"geneva"].length)
{
_fontDescriptor.fontFamily = @"Helvetica";
}
else if ([lowercaseFontFamily rangeOfString:@"cursive"].length)
{
_fontDescriptor.stylisticClass = kCTFontScriptsClass;
_fontDescriptor.fontFamily = nil;
}
else if ([lowercaseFontFamily rangeOfString:@"sans-serif"].length)
{
// too many matches (24)
// fontDescriptor.stylisticClass = kCTFontSansSerifClass;
_fontDescriptor.fontFamily = @"Helvetica";
}
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";
}
else if ([lowercaseFontFamily rangeOfString:@"fantasy"].length)
{
_fontDescriptor.fontFamily = @"Papyrus"; // only available on iPad
}
else if ([lowercaseFontFamily rangeOfString:@"monospace"].length)
{
_fontDescriptor.monospaceTrait = YES;
_fontDescriptor.fontFamily = @"Courier";
}
else if ([lowercaseFontFamily rangeOfString:@"times"].length)
{
_fontDescriptor.fontFamily = @"Times New Roman";
}
else
{
// probably custom font registered in info.plist
_fontDescriptor.fontFamily = fontFamily;
}
}
NSString *fontStyle = [[styles objectForKey:@"font-style"] lowercaseString];
if (fontStyle)
{
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)
{
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"])
{
//TODO: add support for overline decoration
}
else if ([decoration isEqualToString:@"blink"])
{
//TODO: add support for blink decoration
}
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;
}
}
// if there is a text attachment we transfer the aligment we got
_textAttachment.verticalAlignment = _textAttachmentAlignment;
NSString *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:_fontDescriptor.pointSize textScale:_textScale];
self.paragraphStyle.minimumLineHeight = lineHeightValue;
self.paragraphStyle.maximumLineHeight = lineHeightValue;
}
}
NSString *marginBottom = [styles objectForKey:@"margin-bottom"];
if (marginBottom)
{
CGFloat marginBottomValue = [marginBottom pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_fontDescriptor.pointSize textScale:_textScale];
self.paragraphStyle.paragraphSpacing = marginBottomValue;
}
else
{
NSString *webkitMarginAfter = [styles objectForKey:@"-webkit-margin-after"];
if (webkitMarginAfter)
{
self.paragraphStyle.paragraphSpacing = [webkitMarginAfter pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_fontDescriptor.pointSize textScale:_textScale];
}
}
NSString *marginLeft = [styles objectForKey:@"margin-left"];
if (marginLeft)
{
self.paragraphStyle.headIndent = [marginLeft pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_fontDescriptor.pointSize textScale:_textScale];
self.paragraphStyle.firstLineHeadIndent = self.paragraphStyle.headIndent;
}
NSString *marginRight = [styles objectForKey:@"margin-right"];
if (marginRight)
{
self.paragraphStyle.tailIndent = -[marginRight pixelSizeOfCSSMeasureRelativeToCurrentTextSize:_fontDescriptor.pointSize textScale:_textScale];
}
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
}
}
DTEdgeInsets padding = {0,0,0,0};
// webkit default value
NSString *webkitPaddingStart = [styles objectForKey:@"-webkit-padding-start"];
if (webkitPaddingStart)
{
self.paragraphStyle.listIndent = [webkitPaddingStart pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
}
BOOL needsTextBlock = (_backgroundColor!=nil);
NSString *paddingString = [styles objectForKey:@"padding"];
if (paddingString)
{
// maybe it's using the short style
NSArray *parts = [paddingString componentsSeparatedByString:@" "];
if ([parts count] == 4)
{
padding.top = [[parts objectAtIndex:0] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.right = [[parts objectAtIndex:1] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.bottom = [[parts objectAtIndex:2] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.left = [[parts objectAtIndex:3] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
}
else if ([parts count] == 3)
{
padding.top = [[parts objectAtIndex:0] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.right = [[parts objectAtIndex:1] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.bottom = [[parts objectAtIndex:2] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.left = padding.right;
}
else if ([parts count] == 2)
{
padding.top = [[parts objectAtIndex:0] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.right = [[parts objectAtIndex:1] pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding.bottom = padding.top;
padding.left = padding.right;
}
else
{
CGFloat paddingAmount = [paddingString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
padding = DTEdgeInsetsMake(paddingAmount, paddingAmount, paddingAmount, paddingAmount);
}
// left padding overrides webkit list indent
self.paragraphStyle.listIndent = padding.left;
needsTextBlock = YES;
}
else
{
paddingString = [styles objectForKey:@"padding-left"];
if (paddingString)
{
padding.left = [paddingString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
needsTextBlock = YES;
// left padding overrides webkit list indent
self.paragraphStyle.listIndent = padding.left;
}
paddingString = [styles objectForKey:@"padding-top"];
if (paddingString)
{
padding.top = [paddingString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
needsTextBlock = YES;
}
paddingString = [styles objectForKey:@"padding-right"];
if (paddingString)
{
padding.right = [paddingString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
needsTextBlock = YES;
}
paddingString = [styles objectForKey:@"padding-bottom"];
if (paddingString)
{
padding.bottom = [paddingString pixelSizeOfCSSMeasureRelativeToCurrentTextSize:self.fontDescriptor.pointSize textScale:_textScale];
needsTextBlock = YES;
}
}
if (_displayStyle == DTHTMLElementDisplayStyleBlock)
{
if (needsTextBlock)
{
// need a block
DTTextBlock *newBlock = [[DTTextBlock alloc] init];
newBlock.padding = padding;
// transfer background color to block
newBlock.backgroundColor = _backgroundColor;
_backgroundColor = nil;
NSArray *newBlocks = [self.paragraphStyle.textBlocks mutableCopy];
if (!newBlocks)
{
// need an array, this is the first block
newBlocks = [NSArray arrayWithObject:newBlock];
}
self.paragraphStyle.textBlocks = newBlocks;
}
}
}
- (NSDictionary *)styles
{
return _styles;
}
- (void)parseStyleString:(NSString *)styleString
{
NSDictionary *styles = [styleString dictionaryOfCSSStyles];
[self applyStyleDictionary:styles];
}
- (void)addAdditionalAttribute:(id)attribute forKey:(id)key
{
if (!_additionalAttributes)
{
_additionalAttributes = [[NSMutableDictionary alloc] init];
}
[_additionalAttributes setObject:attribute forKey:key];
}
- (NSString *)attributeForKey:(NSString *)key
{
return [_attributes objectForKey:key];
}
#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 && _parent)
{
return [_parent valueForKeyPathWithInheritance:keyPath];
}
// enum properties have 0 for inherit
if ([value isKindOfClass:[NSNumber class]])
{
NSNumber *number = value;
if (([number integerValue]==0) && _parent)
{
return [_parent valueForKeyPathWithInheritance:keyPath];
}
}
// string properties have 'inherit' for inheriting
if ([value isKindOfClass:[NSString class]])
{
NSString *string = value;
if ([string isEqualToString:@"inherit"] && _parent)
{
return [_parent valueForKeyPathWithInheritance:keyPath];
}
}
// obviously not inherited
return value;
}
- (DTCSSListStyle *)calculatedListStyle
{
DTCSSListStyle *style = [[DTCSSListStyle alloc] init];
id calcType = [self valueForKeyPathWithInheritance:@"listStyle.type"];
id calcPos = [self valueForKeyPathWithInheritance:@"listStyle.position"];
id calcImage = [self valueForKeyPathWithInheritance:@"listStyle.imageName"];
style.type = (DTCSSListStyleType)[calcType integerValue];
style.position = (DTCSSListStylePosition)[calcPos integerValue];
style.imageName = calcImage;
return style;
}
#pragma mark - Inheriting Attributes
- (void)inheritAttributesFromElement:(DTHTMLElement *)element
{
_fontDescriptor = [element.fontDescriptor copy];
_paragraphStyle = [element.paragraphStyle copy];
_fontVariant = element.fontVariant;
_underlineStyle = element.underlineStyle;
_strikeOut = element.strikeOut;
_superscriptStyle = element.superscriptStyle;
_shadows = [element.shadows copy];
_link = [element.link copy];
_anchorName = [element.anchorName copy];
_linkGUID = element.linkGUID;
_tagContentInvisible = element.tagContentInvisible;
_textColor = element.textColor;
_isColorInherited = YES;
_preserveNewlines = element.preserveNewlines;
_textScale = element.textScale;
// only inherit background-color from inline elements
if (element.displayStyle == DTHTMLElementDisplayStyleInline)
{
self.backgroundColor = element.backgroundColor;
}
}
#pragma mark Properties
- (void)setTextColor:(DTColor *)textColor
{
if (_textColor != textColor)
{
_textColor = textColor;
_isColorInherited = NO;
}
}
- (DTHTMLElementFontVariant)fontVariant
{
if (_fontVariant == DTHTMLElementFontVariantInherit)
{
if (_parent)
{
return _parent.fontVariant;
}
return DTHTMLElementFontVariantNormal;
}
return _fontVariant;
}
- (void)setAttributes:(NSDictionary *)attributes
{
if (_attributes != attributes)
{
_attributes = attributes;
// 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;
}
- (void)setLink:(NSURL *)link
{
_linkGUID = [NSString guid];
_link = [link copy];
if (_textAttachment)
{
_textAttachment.hyperLinkGUID = _linkGUID;
}
}
@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 tagContentInvisible = _tagContentInvisible;
@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 textScale = _textScale;
@synthesize size = _size;
@synthesize attributes = _attributes;
@synthesize linkGUID = _linkGUID;
@end
Something went wrong with that request. Please try again.