From f2f408f49bbdbb11c08b0dc80a0d22f186a163eb Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sat, 10 Aug 2013 06:35:11 +0200 Subject: [PATCH 01/30] Added 1.6.3 release notes to read me --- Readme.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.markdown b/Readme.markdown index bcf6d2e0f..e5adda03b 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -21,6 +21,7 @@ Follow [@cocoanetics](http://twitter.com/cocoanetics) on Twitter or subscribe to #### Changelog +- [Version 1.6.3](http://www.cocoanetics.com/2013/08/dtcoretext-1-6-3-dtrichtexteditor-1-6-2/) - [Version 1.6.2](http://www.cocoanetics.com/2013/08/dtcoretext-1-6-2/) - [Version 1.6.1](http://www.cocoanetics.com/2013/07/dtcoretext-1-6-1/) - [Version 1.6.0](http://www.cocoanetics.com/2013/07/dtcoretext-1-6/) From 94f4fbeec72832290e57dfd5b068dfed56adc57d Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 08:31:54 +0200 Subject: [PATCH 02/30] Added unit test for #537 --- Core/Source/DTHTMLElement.m | 2 +- Core/Test/Source/DTHTMLAttributedStringBuilderTest.m | 5 +++++ Demo/Resources/CurrentTest.html | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index eed8a05d8..8e95f8f1c 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -758,7 +758,7 @@ - (void)applyStyleDictionary:(NSDictionary *)styles } } - id fontFamily = [[styles objectForKey:@"font-family"] stringByTrimmingCharactersInSet:[NSCharacterSet quoteCharacterSet]]; + id fontFamily = [styles objectForKey:@"font-family"]; if (fontFamily) { diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index da93ec6e8..74c642abe 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -479,6 +479,11 @@ - (void)testHelveticaVariants }]; } +- (void)testMultipleFontFamilies +{ + STAssertTrueNoThrow([self _attributedStringFromHTMLString:@"

Text

" options:nil]!=nil, @"Should be able to parse without crash"); +} + #pragma mark - Nested Lists - (void)testNestedListWithStyleNone diff --git a/Demo/Resources/CurrentTest.html b/Demo/Resources/CurrentTest.html index fe1b421dc..a85a75c06 100644 --- a/Demo/Resources/CurrentTest.html +++ b/Demo/Resources/CurrentTest.html @@ -9,7 +9,7 @@ - Text +

Text

From b2ec43d6e94db52a68a583af966154ece0aa464c Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 08:35:51 +0200 Subject: [PATCH 03/30] Removed quote trimming which is now done by CSS parsing, fixes #537 --- Core/Source/DTHTMLElement.m | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index 8e95f8f1c..ffe1df558 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -762,17 +762,21 @@ - (void)applyStyleDictionary:(NSDictionary *)styles if (fontFamily) { - NSArray *fontFamilies = @[];; + NSArray *fontFamilies; - if ([fontFamily isKindOfClass:[NSString class]]) { - fontFamilies = @[fontFamily]; - } else if ([fontFamily isKindOfClass:[NSArray class]]) { + if ([fontFamily isKindOfClass:[NSString class]]) + { + fontFamilies = [NSArray arrayWithObject:fontFamily]; + } + else if ([fontFamily isKindOfClass:[NSArray class]]) + { fontFamilies = fontFamily; } BOOL foundFontFamily = NO; - for (NSString *fontFamily in fontFamilies) { + for (NSString *fontFamily in fontFamilies) + { NSString *lowercaseFontFamily = [fontFamily lowercaseString]; if ([lowercaseFontFamily rangeOfString:@"geneva"].length) @@ -825,14 +829,16 @@ - (void)applyStyleDictionary:(NSDictionary *)styles foundFontFamily = YES; } - if (foundFontFamily) { + if (foundFontFamily) + { break; } } - if (!foundFontFamily) { + if (!foundFontFamily) + { // probably custom font registered in info.plist - _fontDescriptor.fontFamily = fontFamilies[0]; + _fontDescriptor.fontFamily = [fontFamilies objectAtIndex:0]; } } From 172f4904906ab6734f42be81b2576eb09758f000 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 08:46:53 +0200 Subject: [PATCH 04/30] Added comment for #537 unit test --- Core/Test/Source/DTHTMLAttributedStringBuilderTest.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index 74c642abe..d5e65a7fa 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -479,6 +479,7 @@ - (void)testHelveticaVariants }]; } +// issue 537 - (void)testMultipleFontFamilies { STAssertTrueNoThrow([self _attributedStringFromHTMLString:@"

Text

" options:nil]!=nil, @"Should be able to parse without crash"); From 3f764b3340febecca7d2fcb981754521c6eb2db8 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 08:56:55 +0200 Subject: [PATCH 05/30] Added unit test for #538 --- .../Source/DTHTMLAttributedStringBuilderTest.m | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index d5e65a7fa..0ec6d1bc5 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -480,11 +480,27 @@ - (void)testHelveticaVariants } // issue 537 -- (void)testMultipleFontFamilies +- (void)testMultipleFontFamiliesCrash { STAssertTrueNoThrow([self _attributedStringFromHTMLString:@"

Text

" options:nil]!=nil, @"Should be able to parse without crash"); } +// issue 538 +- (void)testMultipleFontFamiliesSelection +{ + NSAttributedString *attributedString = [self _attributedStringFromHTMLString:@"

Text

" options:nil]; + + NSRange fontRange; + CTFontRef font = (__bridge CTFontRef)([attributedString attribute:(__bridge id)kCTFontAttributeName atIndex:0 effectiveRange:&fontRange]); + + NSRange expectedRange = NSMakeRange(0, [attributedString length]); + STAssertEquals(fontRange, expectedRange, @"Font should be entire length"); + + DTCoreTextFontDescriptor *descriptor = [DTCoreTextFontDescriptor fontDescriptorForCTFont:font]; + + STAssertEqualObjects(descriptor.fontFamily, @"American Typewriter", @"Font Family should be 'American Typewriter'"); +} + #pragma mark - Nested Lists - (void)testNestedListWithStyleNone From f491471c9f6f1ee4d76a41b43ed6aff25a54f309 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 09:33:30 +0200 Subject: [PATCH 06/30] Added additional unit test for #538 --- .../Source/DTHTMLAttributedStringBuilderTest.m | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index 0ec6d1bc5..e2d0d74c1 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -501,6 +501,22 @@ - (void)testMultipleFontFamiliesSelection STAssertEqualObjects(descriptor.fontFamily, @"American Typewriter", @"Font Family should be 'American Typewriter'"); } +// issue 538 +- (void)testMultipleFontFamiliesSelectionLaterPosition +{ + NSAttributedString *attributedString = [self _attributedStringFromHTMLString:@"

Text

" options:nil]; + + NSRange fontRange; + CTFontRef font = (__bridge CTFontRef)([attributedString attribute:(__bridge id)kCTFontAttributeName atIndex:0 effectiveRange:&fontRange]); + + NSRange expectedRange = NSMakeRange(0, [attributedString length]); + STAssertEquals(fontRange, expectedRange, @"Font should be entire length"); + + DTCoreTextFontDescriptor *descriptor = [DTCoreTextFontDescriptor fontDescriptorForCTFont:font]; + + STAssertEqualObjects(descriptor.fontFamily, @"American Typewriter", @"Font Family should be 'American Typewriter'"); +} + #pragma mark - Nested Lists - (void)testNestedListWithStyleNone From a01d78b3ebd69b75cab13e07ce861f15919998ea Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Sun, 11 Aug 2013 09:33:42 +0200 Subject: [PATCH 07/30] Check font family for validity, fixes #538 --- Core/Source/DTHTMLElement.m | 12 ++++++++++++ Demo/Resources/CurrentTest.html | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index ffe1df558..6dc9ed19d 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -777,6 +777,18 @@ - (void)applyStyleDictionary:(NSDictionary *)styles 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) diff --git a/Demo/Resources/CurrentTest.html b/Demo/Resources/CurrentTest.html index a85a75c06..293d76312 100644 --- a/Demo/Resources/CurrentTest.html +++ b/Demo/Resources/CurrentTest.html @@ -9,7 +9,7 @@ -

Text

+

Text

From ad6370aca4ef4597babd1d380f020818abd8d6c8 Mon Sep 17 00:00:00 2001 From: Kolyvan Date: Sun, 11 Aug 2013 21:39:32 +1100 Subject: [PATCH 08/30] fix #535 (exception if there are multiple font-family entries in css) --- Core/Source/DTCSSStylesheet.m | 64 ++++++++++++++++++-------- Core/Test/Source/DTCSSStylesheetTest.m | 15 ++++++ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Core/Source/DTCSSStylesheet.m b/Core/Source/DTCSSStylesheet.m index b3382855f..9c34b1ea5 100644 --- a/Core/Source/DTCSSStylesheet.m +++ b/Core/Source/DTCSSStylesheet.m @@ -415,16 +415,51 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors // remove !important, we're ignoring these for (NSString *oneKey in [ruleDictionary allKeys]) { - NSString *value = [ruleDictionary objectForKey:oneKey]; - - NSRange rangeOfImportant = [value rangeOfString:@"!important" options:NSCaseInsensitiveSearch]; - - if (rangeOfImportant.location != NSNotFound) + id value = [ruleDictionary objectForKey:oneKey]; + if ([value isKindOfClass:[NSString class]]) { - value = [value stringByReplacingCharactersInRange:rangeOfImportant withString:@""]; - value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSRange rangeOfImportant = [value rangeOfString:@"!important" options:NSCaseInsensitiveSearch]; + + if (rangeOfImportant.location != NSNotFound) + { + value = [value stringByReplacingCharactersInRange:rangeOfImportant withString:@""]; + value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + [ruleDictionary setObject:value forKey:oneKey]; + } - [ruleDictionary setObject:value forKey:oneKey]; + } else if ([value isKindOfClass:[NSArray class]]) { + + NSMutableArray *newVal; + + for (NSUInteger i = 0; i < [value count]; ++i) + { + NSString *s = [value objectAtIndex:i]; + + NSRange rangeOfImportant = [s rangeOfString:@"!important" options:NSCaseInsensitiveSearch]; + + if (rangeOfImportant.location != NSNotFound) + { + s = [s stringByReplacingCharactersInRange:rangeOfImportant withString:@""]; + s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (!newVal) { + + if ([value isKindOfClass:[NSMutableArray class]]) { + newVal = value; + } else { + newVal = [value mutableCopy]; + } + } + + newVal[i] = s; + } + } + + if (newVal) { + + [ruleDictionary setObject:newVal forKey:oneKey]; + } } } @@ -443,17 +478,8 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors // prefix all rules with the pseudo-selector for (NSString *oneRuleKey in [ruleDictionary allKeys]) { - // remove double quotes - NSString *value = [ruleDictionary objectForKey:oneRuleKey]; - - if ([value hasPrefix:@"\""] && [value hasSuffix:@"\""]) - { - // treat as HTML string, remove quotes - NSRange range = NSMakeRange(1, [value length]-2); - - value = [[value substringWithRange:range] stringByAddingHTMLEntities]; - } - + id value = [ruleDictionary objectForKey:oneRuleKey]; + // prefix key with the pseudo selector NSString *prefixedKey = [NSString stringWithFormat:@"%@:%@", pseudoSelector, oneRuleKey]; [ruleDictionary setObject:value forKey:prefixedKey]; diff --git a/Core/Test/Source/DTCSSStylesheetTest.m b/Core/Test/Source/DTCSSStylesheetTest.m index 61339e11e..7e97ee248 100644 --- a/Core/Test/Source/DTCSSStylesheetTest.m +++ b/Core/Test/Source/DTCSSStylesheetTest.m @@ -97,4 +97,19 @@ - (void)testMergingWithDecompression STAssertEqualObjects(styles[@"margin-bottom"], @"30px", @"Margin Bottom should be 30px"); } +// issue 535 + +- (void)testMultipleFontFamiliesCrash +{ + STAssertTrueNoThrow([[DTCSSStylesheet alloc] initWithStyleBlock:@"p {font-family:Helvetica,sans-serif;}"]!=nil, @"Should be able to parse without crash"); +} + +- (void)testMultipleFontFamilies +{ + DTCSSStylesheet *stylesheet = [[DTCSSStylesheet alloc] initWithStyleBlock:@"p {font-family:Helvetica,sans-serif !important;}"]; + NSDictionary *styles = [stylesheet.styles objectForKey:@"p"]; + NSArray *expected = @[@"Helvetica", @"sans-serif"]; + STAssertEqualObjects(styles[@"font-family"], expected, @"Font Family should be [Helvetica, sans-serif]"); +} + @end From 3eb4a3326c8ef68cc7c3648aa69c1119695a8cf0 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 09:06:27 +0200 Subject: [PATCH 09/30] Fixed missing header for MacUnitTest --- Core/Externals/DTFoundation | 2 +- Core/Test/Source/DTCSSStylesheetTest.m | 2 +- DTCoreText.xcodeproj/project.pbxproj | 42 +++++++++++++++++++++----- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Core/Externals/DTFoundation b/Core/Externals/DTFoundation index 5024a2136..3c88144dc 160000 --- a/Core/Externals/DTFoundation +++ b/Core/Externals/DTFoundation @@ -1 +1 @@ -Subproject commit 5024a2136300d3d50bd7c39fcab5701de50dacd9 +Subproject commit 3c88144dc59c6a3bfaffd7cebafb608077716854 diff --git a/Core/Test/Source/DTCSSStylesheetTest.m b/Core/Test/Source/DTCSSStylesheetTest.m index 7e97ee248..d77f5cbf8 100644 --- a/Core/Test/Source/DTCSSStylesheetTest.m +++ b/Core/Test/Source/DTCSSStylesheetTest.m @@ -51,7 +51,7 @@ - (void)testImportant NSDictionary *styles = [stylesheet.styles objectForKey:@"p"]; - STAssertEquals([styles count], 2u, @"There should be 2 styles"); + STAssertEquals([styles count], (NSUInteger)2, @"There should be 2 styles"); NSString *alignStyle = [styles objectForKey:@"align"]; diff --git a/DTCoreText.xcodeproj/project.pbxproj b/DTCoreText.xcodeproj/project.pbxproj index 6563c7836..c4d5a3e33 100644 --- a/DTCoreText.xcodeproj/project.pbxproj +++ b/DTCoreText.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ A776DBE91716A8EE00E71F36 /* NSStringParagraphTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A776DBE71716A8EE00E71F36 /* NSStringParagraphTest.m */; }; A77A3E421779BF04000B290B /* NSMutableAttributedStringHTMLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A77A3E411779BF03000B290B /* NSMutableAttributedStringHTMLTest.m */; }; A77A3E431779BF04000B290B /* NSMutableAttributedStringHTMLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A77A3E411779BF03000B290B /* NSMutableAttributedStringHTMLTest.m */; }; + A785FA1217BA113900BB758E /* DTCSSStylesheetTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A7E3346D16837482002EFCBE /* DTCSSStylesheetTest.m */; }; A788C95D14863E8700E1AFD9 /* DTAttributedTextCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A788C91014863E8700E1AFD9 /* DTAttributedTextCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; A788C95E14863E8700E1AFD9 /* DTAttributedTextCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A788C91014863E8700E1AFD9 /* DTAttributedTextCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; A788C96014863E8700E1AFD9 /* DTAttributedTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A788C91114863E8700E1AFD9 /* DTAttributedTextCell.m */; }; @@ -710,6 +711,20 @@ remoteGlobalIDString = A7E889A416A9B190009EF0DF; remoteInfo = "DTUTI (Mac)"; }; + A785FA0E17BA111200BB758E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7E383AB160DFEA700CF72D6 /* DTFoundation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = FA9CB80C17ABDEF200A596C5; + remoteInfo = "DTASN1 (iOS)"; + }; + A785FA1017BA111200BB758E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7E383AB160DFEA700CF72D6 /* DTFoundation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = FAF37A2017AFDD93009AC27C; + remoteInfo = DTZipArchiveDemo; + }; A788CA611486428F00E1AFD9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; @@ -1656,6 +1671,7 @@ A7B4E2D21686FDB300717707 /* UnitTests.octest */, A7B4E2D41686FDB300717707 /* libDTFoundation_Mac.a */, A70C4FE717AA7E3100000DF5 /* libDTAWS.a */, + A785FA0F17BA111200BB758E /* libDTASN1.a */, A7B4E2D61686FDB300717707 /* libDTHTMLParser_iOS.a */, A7B4E2D81686FDB300717707 /* libDTHTMLParser_Mac.a */, A7B4E2DA1686FDB300717707 /* libDTZipArchive_iOS.a */, @@ -1668,6 +1684,7 @@ A7FB12AD175E23BC00D4B7F0 /* libDTSQLite_Mac.a */, A7FB12AF175E23BC00D4B7F0 /* DTSidePanels Demo.app */, A7FB12B1175E23BC00D4B7F0 /* DTReachability Demo.app */, + A785FA1117BA111200BB758E /* DTZipArchive.app */, ); name = Products; sourceTree = ""; @@ -2131,6 +2148,20 @@ remoteRef = A74F6FD216DB9AF600C12307 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + A785FA0F17BA111200BB758E /* libDTASN1.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libDTASN1.a; + remoteRef = A785FA0E17BA111200BB758E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A785FA1117BA111200BB758E /* DTZipArchive.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = DTZipArchive.app; + remoteRef = A785FA1017BA111200BB758E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; A7B4E2D21686FDB300717707 /* UnitTests.octest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; @@ -2598,6 +2629,7 @@ A773790E17185BF100640641 /* DTHTMLElementTest.m in Sources */, A77A3E431779BF04000B290B /* NSMutableAttributedStringHTMLTest.m in Sources */, A7985E1C178ECE08005D40B2 /* DTHTMLWriterTest.m in Sources */, + A785FA1217BA113900BB758E /* DTCSSStylesheetTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3029,10 +3061,7 @@ INFOPLIST_FILE = "Core/Test/MacUnitTest-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - ); + OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Core/Externals\"/** \"$(SRCROOT)/Core/Source\""; @@ -3060,10 +3089,7 @@ ); INFOPLIST_FILE = "Core/Test/MacUnitTest-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - ); + OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Core/Externals\"/** \"$(SRCROOT)/Core/Source\""; From dc1631c9fb63b64416dbd1591e8c872d640f717c Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 09:11:31 +0200 Subject: [PATCH 10/30] removed header belonging to DTFramework from DTCoreText.h, fixes #532 --- Core/Source/DTCoreText.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/Source/DTCoreText.h b/Core/Source/DTCoreText.h index 3dbe11c28..a72c433e9 100644 --- a/Core/Source/DTCoreText.h +++ b/Core/Source/DTCoreText.h @@ -12,7 +12,6 @@ #import "DTImage+HTML.h" // common utilities -#import "DTUtils.h" #if TARGET_OS_IPHONE #import "DTCoreTextFunctions.h" #endif From 81231510af5126b208cb59bf6291459f1930b4c7 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 09:29:00 +0200 Subject: [PATCH 11/30] Disable 4.3 leak fix for newer OS versions #535 --- Core/Source/DTHTMLElement.m | 14 ++++++++++---- Core/Source/NSMutableAttributedString+HTML.m | 13 +++++++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index 6dc9ed19d..10dbbaba7 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -489,8 +489,11 @@ - (NSAttributedString *)attributedString // make new paragraph style NSParagraphStyle *newParaStyle = [paragraphStyle NSParagraphStyle]; - // remove old (works around iOS 4.3 leak) - [tmpString removeAttribute:NSParagraphStyleAttributeName range:paragraphRange]; + if (NSFoundationVersionNumber <= 751.49) + { + // remove old (works around iOS 4.3 leak) + [tmpString removeAttribute:NSParagraphStyleAttributeName range:paragraphRange]; + } // set new [tmpString addAttribute:NSParagraphStyleAttributeName value:newParaStyle range:paragraphRange]; @@ -510,8 +513,11 @@ - (NSAttributedString *)attributedString // make new paragraph style CTParagraphStyleRef newParaStyle = [paragraphStyle createCTParagraphStyle]; - // remove old (works around iOS 4.3 leak) - [tmpString removeAttribute:(id)kCTParagraphStyleAttributeName range:paragraphRange]; + if (NSFoundationVersionNumber <= 751.49) + { + // remove old (works around iOS 4.3 leak) + [tmpString removeAttribute:(id)kCTParagraphStyleAttributeName range:paragraphRange]; + } // set new [tmpString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge_transfer id)newParaStyle range:paragraphRange]; diff --git a/Core/Source/NSMutableAttributedString+HTML.m b/Core/Source/NSMutableAttributedString+HTML.m index 512ea2a46..77c79d119 100644 --- a/Core/Source/NSMutableAttributedString+HTML.m +++ b/Core/Source/NSMutableAttributedString+HTML.m @@ -139,7 +139,12 @@ - (void)addHTMLAttribute:(NSString *)name value:(id)value range:(NSRange)range r [mutableDictionary setObject:value forKey:name]; // substitute attribute - [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + if (NSFoundationVersionNumber <= 751.49) + { + // remove old (works around iOS 4.3 leak) + [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + } + [self addAttribute:DTCustomAttributesAttribute value:[mutableDictionary copy] range:effectiveRange]; } else @@ -171,7 +176,11 @@ - (void)removeHTMLAttribute:(NSString *)name range:(NSRange)range [mutableDictionary removeObjectForKey:name]; // substitute attribute - [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + if (NSFoundationVersionNumber <= 751.49) + { + // remove old (works around iOS 4.3 leak) + [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + } // only re-add modified dictionary if it is not empty if ([mutableDictionary count]) From aef43af66d75bc46d236cbe74fa6ca5315bbdb35 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 09:37:20 +0200 Subject: [PATCH 12/30] removed commented out code --- Core/Source/DTCoreTextParagraphStyle.m | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Core/Source/DTCoreTextParagraphStyle.m b/Core/Source/DTCoreTextParagraphStyle.m index b3db67fe9..a987b79d0 100644 --- a/Core/Source/DTCoreTextParagraphStyle.m +++ b/Core/Source/DTCoreTextParagraphStyle.m @@ -172,20 +172,6 @@ - (id)initWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(_lineHeightMultiple), &_lineHeightMultiple); - -// if (_lineHeightMultiple) -// { -// // paragraph space is pre-multiplied -// if (_paragraphSpacing) -// { -// _paragraphSpacing /= _lineHeightMultiple; -// } -// -// if (_paragraphSpacingBefore) -// { -// _paragraphSpacingBefore /= _lineHeightMultiple; -// } -// } } return self; @@ -264,16 +250,6 @@ - (CTParagraphStyleRef)createCTParagraphStyle return cachedParagraphStyle; // +1 reference } -// // need to multiple paragraph spacing with line height multiplier -// float tmpParagraphSpacing = _paragraphSpacing; -// float tmpParagraphSpacingBefore = _paragraphSpacingBefore; -// -// if (_lineHeightMultiple&&(_lineHeightMultiple!=1.0)) -// { -// tmpParagraphSpacing *= _lineHeightMultiple; -// tmpParagraphSpacingBefore *= _lineHeightMultiple; -// } - // This just makes it that much easier to track down memory issues with tabstops CFArrayRef stops = _tabStops ? CFArrayCreateCopy (NULL, (__bridge CFArrayRef)_tabStops) : NULL; From 1b48d711f0dcd498b2b37981c6c307779e305d04 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 09:53:59 +0200 Subject: [PATCH 13/30] Fixed error introduced in 81231510af5126b208cb59bf6291459f1930b4c7 --- Core/Source/NSMutableAttributedString+HTML.m | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Core/Source/NSMutableAttributedString+HTML.m b/Core/Source/NSMutableAttributedString+HTML.m index 77c79d119..e936671d9 100644 --- a/Core/Source/NSMutableAttributedString+HTML.m +++ b/Core/Source/NSMutableAttributedString+HTML.m @@ -176,17 +176,22 @@ - (void)removeHTMLAttribute:(NSString *)name range:(NSRange)range [mutableDictionary removeObjectForKey:name]; // substitute attribute - if (NSFoundationVersionNumber <= 751.49) - { - // remove old (works around iOS 4.3 leak) - [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; - } // only re-add modified dictionary if it is not empty if ([mutableDictionary count]) { + if (NSFoundationVersionNumber <= 751.49) + { + // remove old (works around iOS 4.3 leak) + [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + } + [self addAttribute:DTCustomAttributesAttribute value:[mutableDictionary copy] range:effectiveRange]; } + else + { + [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; + } } }]; From fb50a9785a1e463567ea58859d41752581e55d2b Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 10:20:00 +0200 Subject: [PATCH 14/30] Define out leak-fix for high enough deployment target #535 --- Core/Source/DTCompatibility.h | 10 ++++++++++ Core/Source/DTHTMLElement.m | 10 +++++++--- Core/Source/NSMutableAttributedString+HTML.m | 6 ++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Core/Source/DTCompatibility.h b/Core/Source/DTCompatibility.h index 5b54b3025..7816ef01a 100644 --- a/Core/Source/DTCompatibility.h +++ b/Core/Source/DTCompatibility.h @@ -24,6 +24,11 @@ #define DTCORETEXT_SUPPORT_NS_ATTRIBUTES 1 #endif + // iOS before 5.0 has leak in CoreText replacing attributes + #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0 + #define DTCORETEXT_NEEDS_ATTRIBUTE_REPLACEMENT_LEAK_FIX 1 + #endif + #endif @@ -44,6 +49,11 @@ // Mac supports NS-Style Text Attributes since 10.0 #define DTCORETEXT_SUPPORT_NS_ATTRIBUTES 1 + // theoretically MacOS before 10.8 might have a leak in CoreText replacing attributes + #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_7 + #define DTCORETEXT_NEEDS_ATTRIBUTE_REPLACEMENT_LEAK_FIX 1 + #endif + // NSValue has sizeValue on Mac, CGSizeValue on iOS #define CGSizeValue sizeValue diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index 10dbbaba7..1f65f0a82 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -489,11 +489,13 @@ - (NSAttributedString *)attributedString // make new paragraph style NSParagraphStyle *newParaStyle = [paragraphStyle NSParagraphStyle]; - if (NSFoundationVersionNumber <= 751.49) +#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]; @@ -512,12 +514,14 @@ - (NSAttributedString *)attributedString // make new paragraph style CTParagraphStyleRef newParaStyle = [paragraphStyle createCTParagraphStyle]; - - if (NSFoundationVersionNumber <= 751.49) + +#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]; diff --git a/Core/Source/NSMutableAttributedString+HTML.m b/Core/Source/NSMutableAttributedString+HTML.m index e936671d9..0336477ae 100644 --- a/Core/Source/NSMutableAttributedString+HTML.m +++ b/Core/Source/NSMutableAttributedString+HTML.m @@ -139,11 +139,13 @@ - (void)addHTMLAttribute:(NSString *)name value:(id)value range:(NSRange)range r [mutableDictionary setObject:value forKey:name]; // substitute attribute - if (NSFoundationVersionNumber <= 751.49) +#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) [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; } +#endif [self addAttribute:DTCustomAttributesAttribute value:[mutableDictionary copy] range:effectiveRange]; } @@ -180,7 +182,7 @@ - (void)removeHTMLAttribute:(NSString *)name range:(NSRange)range // only re-add modified dictionary if it is not empty if ([mutableDictionary count]) { - if (NSFoundationVersionNumber <= 751.49) + if (NSFoundationVersionNumber <= NSFoundationVersionNumber10_6_8) // less than OS X 10.7 and less than iOS 5 { // remove old (works around iOS 4.3 leak) [self removeAttribute:DTCustomAttributesAttribute range:effectiveRange]; From da7c161093c2822f252115bf58311cbca8c2b96f Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 11:45:47 +0200 Subject: [PATCH 15/30] Remove caching of CTParagraphStyle #535 --- Core/Source/DTCoreTextParagraphStyle.m | 108 ++----------------------- 1 file changed, 5 insertions(+), 103 deletions(-) diff --git a/Core/Source/DTCoreTextParagraphStyle.m b/Core/Source/DTCoreTextParagraphStyle.m index a987b79d0..399f66110 100644 --- a/Core/Source/DTCoreTextParagraphStyle.m +++ b/Core/Source/DTCoreTextParagraphStyle.m @@ -11,29 +11,6 @@ #import "DTCSSListStyle.h" #import "DTWeakSupport.h" -#if !TARGET_OS_IPHONE -#import -#endif - -// global cache for returning previously created immutable paragraph styles -static NSCache *_CTParagraphStyleCache = nil; - -// a struct that takes on all sub-values, used for fast hash -typedef struct { - CGFloat firstLineHeadIndent; - CGFloat defaultTabInterval; - CGFloat paragraphSpacingBefore; - CGFloat paragraphSpacing; - CGFloat headIndent; - CGFloat tailIndent; - CGFloat lineHeightMultiple; - CGFloat minimumLineHeight; - CGFloat maximumLineHeight; - NSInteger alignment; // make it full width, origin is uint8 - NSInteger baseWritingDirection; // make it full width, origin is int8 - NSUInteger tabsBlocksListsHash; -} allvalues_t; - @implementation DTCoreTextParagraphStyle { CGFloat _firstLineHeadIndent; @@ -52,11 +29,6 @@ @implementation DTCoreTextParagraphStyle NSMutableArray *_tabStops; } -+ (void)initialize -{ - _CTParagraphStyleCache = [[NSCache alloc] init]; -} - + (DTCoreTextParagraphStyle *)defaultParagraphStyle { return [[DTCoreTextParagraphStyle alloc] init]; @@ -177,79 +149,8 @@ - (id)initWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle return self; } -// creates a fast hash for the properties -- (id )_cacheKey -{ - NSMutableString *tabsBlocksListsDescription = [NSMutableString string]; - - for (id tab in _tabStops) - { - CTTextTabRef tabStop = (__bridge CTTextTabRef)tab; - - CTTextAlignment alignment = CTTextTabGetAlignment(tabStop); - double location = CTTextTabGetLocation(tabStop); - - [tabsBlocksListsDescription appendFormat:@"-tab:%d-%f", alignment, location]; - } - - for (DTTextBlock *textBlock in _textBlocks) - { - [tabsBlocksListsDescription appendFormat:@"-block:%lx", (unsigned long)[textBlock hash]]; - } - - for (DTCSSListStyle *listStyle in _textLists) - { - [tabsBlocksListsDescription appendFormat:@"-list:%lx", (unsigned long)[listStyle hash]]; - } - -#if TARGET_OS_IPHONE - // on iOS we use NSData's hashing function because we have less than 80 bytes (48) - allvalues_t *allvalues = malloc(sizeof(allvalues_t)); // will not be freed -#else - // on MAC this struct is 96 bytes, so we use CommonCrypto's MD5 to reduce from > 80 bytes to less - allvalues_t allvalues_stack; // create tmp variable on stack - allvalues_t *allvalues = &allvalues_stack; // pointer so that we can use the arrow operator -#endif - - *allvalues = (allvalues_t){0,0,0,0,0,0,0,0,0,0,0,0}; - - // pack all values in the struct - allvalues->firstLineHeadIndent = _firstLineHeadIndent; - allvalues->defaultTabInterval = _defaultTabInterval; - allvalues->paragraphSpacingBefore = _paragraphSpacingBefore; - allvalues->paragraphSpacing = _paragraphSpacing; - allvalues->headIndent = _headIndent; - allvalues->tailIndent = _tailIndent; - allvalues->lineHeightMultiple = _lineHeightMultiple; - allvalues->minimumLineHeight = _minimumLineHeight; - allvalues->maximumLineHeight = _maximumLineHeight; - allvalues->baseWritingDirection = _baseWritingDirection; - allvalues->alignment = _alignment; - allvalues->tabsBlocksListsHash = [tabsBlocksListsDescription hash]; - -#if TARGET_OS_IPHONE - // wrap it in NSData - return [NSData dataWithBytesNoCopy:allvalues length:sizeof(allvalues_t) freeWhenDone:YES]; -#else - // Alternate Implementation using MD5 - void *digest = malloc(CC_MD5_DIGEST_LENGTH); // will not be freed - CC_MD5(allvalues, (CC_LONG)sizeof(allvalues_t), digest); - - return [NSData dataWithBytesNoCopy:digest length:CC_MD5_DIGEST_LENGTH freeWhenDone:YES]; -#endif -} - - (CTParagraphStyleRef)createCTParagraphStyle { - id cacheKey = [self _cacheKey]; - - CTParagraphStyleRef cachedParagraphStyle = CFBridgingRetain([_CTParagraphStyleCache objectForKey:cacheKey]); - - if (cachedParagraphStyle) - { - return cachedParagraphStyle; // +1 reference - } - // This just makes it that much easier to track down memory issues with tabstops CFArrayRef stops = _tabStops ? CFArrayCreateCopy (NULL, (__bridge CFArrayRef)_tabStops) : NULL; @@ -274,11 +175,12 @@ - (CTParagraphStyleRef)createCTParagraphStyle }; CTParagraphStyleRef ret = CTParagraphStyleCreate(settings, 12); - if (stops) CFRelease(stops); - - // cache it for next time - [_CTParagraphStyleCache setObject:(__bridge id)ret forKey:cacheKey]; + if (stops) + { + CFRelease(stops); + } + return ret; } From 20fb1784d25666ecd2042c2cc6e8a805722cb835 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 13:33:54 +0200 Subject: [PATCH 16/30] Fixed problems that were a result of removing paragraph style caching #535 --- Core/Source/DTHTMLElement.m | 15 ++++-- Core/Source/NSMutableAttributedString+HTML.h | 5 ++ Core/Source/NSMutableAttributedString+HTML.m | 52 +++++++++++++++++++ .../Source/DTCoreTextParagraphStyleTest.m | 36 ------------- Demo/Resources/CurrentTest.html | 2 +- 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/Core/Source/DTHTMLElement.m b/Core/Source/DTHTMLElement.m index 1f65f0a82..0cba7c715 100644 --- a/Core/Source/DTHTMLElement.m +++ b/Core/Source/DTHTMLElement.m @@ -458,9 +458,18 @@ - (NSAttributedString *)attributedString { if (![[tmpString string] hasSuffix:@"\n"]) { - NSDictionary *attributes = [self attributesForAttributedStringRepresentation]; - NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:attributes]; - [tmpString appendAttributedString:attributedString]; + 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 + NSDictionary *attributes = [self attributesForAttributedStringRepresentation]; + NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:attributes]; + [tmpString appendAttributedString:attributedString]; + } } } } diff --git a/Core/Source/NSMutableAttributedString+HTML.h b/Core/Source/NSMutableAttributedString+HTML.h index 358fa5344..2f6696f07 100644 --- a/Core/Source/NSMutableAttributedString+HTML.h +++ b/Core/Source/NSMutableAttributedString+HTML.h @@ -29,6 +29,11 @@ @param fontDescriptor Font descriptor to be attributed to the appended string. */ - (void)appendString:(NSString *)string withParagraphStyle:(DTCoreTextParagraphStyle *)paragraphStyle fontDescriptor:(DTCoreTextFontDescriptor *)fontDescriptor; +/** + Adds the paragraph terminator `\n` and makes sure that the previous font and paragraph styles extend to include it + */ +- (void)appendEndOfParagraph; + /** @name Working with Custom HTML Attributes */ diff --git a/Core/Source/NSMutableAttributedString+HTML.m b/Core/Source/NSMutableAttributedString+HTML.m index 0336477ae..8e8ea107d 100644 --- a/Core/Source/NSMutableAttributedString+HTML.m +++ b/Core/Source/NSMutableAttributedString+HTML.m @@ -103,6 +103,58 @@ - (void)appendString:(NSString *)string withParagraphStyle:(DTCoreTextParagraphS } } +- (void)appendEndOfParagraph +{ + NSUInteger length = [self length]; + + NSAssert(length, @"Cannot append end of paragraph to empty string"); + + NSRange effectiveRange; + NSDictionary *attributes = [self attributesAtIndex:length-1 effectiveRange:&effectiveRange]; + + + NSMutableDictionary *appendAttributes = [NSMutableDictionary dictionary]; + + +#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES + if (___useiOS6Attributes) + { + id font = [attributes objectForKey:NSFontAttributeName]; + + if (font) + { + [appendAttributes setObject:font forKey:NSFontAttributeName]; + } + + id paragraphStyle = [attributes objectForKey:NSParagraphStyleAttributeName]; + + if (paragraphStyle) + { + [appendAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName]; + } + } + else +#endif + { + CTFontRef font = (__bridge CTFontRef)[attributes objectForKey:(id)kCTFontAttributeName]; + + if (font) + { + [appendAttributes setObject:(__bridge id)(font) forKey:(id)kCTFontAttributeName]; + } + + CTParagraphStyleRef paragraphStyle = (__bridge CTParagraphStyleRef)[attributes objectForKey:(id)kCTParagraphStyleAttributeName]; + + if (paragraphStyle) + { + [appendAttributes setObject:(__bridge id)(paragraphStyle) forKey:(id)kCTParagraphStyleAttributeName]; + } + } + + NSAttributedString *newlineString = [[NSAttributedString alloc] initWithString:@"\n" attributes:appendAttributes]; + [self appendAttributedString:newlineString]; +} + #pragma mark - Working with Custom HTML Attributes diff --git a/Core/Test/Source/DTCoreTextParagraphStyleTest.m b/Core/Test/Source/DTCoreTextParagraphStyleTest.m index 36a915bc1..df2b3cb35 100644 --- a/Core/Test/Source/DTCoreTextParagraphStyleTest.m +++ b/Core/Test/Source/DTCoreTextParagraphStyleTest.m @@ -11,40 +11,4 @@ @implementation DTCoreTextParagraphStyleTest -- (void)testCache -{ - // make a test style - DTCoreTextParagraphStyle *paraStyle = [[DTCoreTextParagraphStyle alloc] init]; - paraStyle.lineHeightMultiple = 2.0f; - paraStyle.headIndent = 30; - - CTParagraphStyleRef para1 = [paraStyle createCTParagraphStyle]; - CTParagraphStyleRef para2 = [paraStyle createCTParagraphStyle]; - - STAssertEquals(para1, para2, @"Two successife Paragraph Styles should be identical"); - - // change something - - paraStyle.tailIndent = -20; - - CTParagraphStyleRef para3 = [paraStyle createCTParagraphStyle]; - - STAssertTrue(para2!=para3, @"Paragraph Styles should not be identical after change"); - - // change back - - paraStyle.tailIndent = 0; - - CTParagraphStyleRef para4 = [paraStyle createCTParagraphStyle]; - - STAssertEquals(para1, para4, @"Paragraph Styles should be identical after change back"); - - // cleanup - - CFRelease(para1); - CFRelease(para2); - CFRelease(para3); - CFRelease(para4); -} - @end diff --git a/Demo/Resources/CurrentTest.html b/Demo/Resources/CurrentTest.html index 293d76312..03cb1b668 100644 --- a/Demo/Resources/CurrentTest.html +++ b/Demo/Resources/CurrentTest.html @@ -9,7 +9,7 @@ -

Text

+

Regular

Bold

Italic

Bold+Italic

From d272de016ff0d58216a340f3a6f3d657c4313f72 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Tue, 13 Aug 2013 15:52:40 +0200 Subject: [PATCH 17/30] workaround for crash that otherwise occurs from enumerating attributes on different threads #541 --- Core/Source/DTCoreTextLayoutFrame.m | 139 ++++++++++++------- Core/Source/NSMutableAttributedString+HTML.m | 16 +++ 2 files changed, 105 insertions(+), 50 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index 3a7dce46a..bde5fdf0e 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -882,6 +882,18 @@ - (void)_setShadowInContext:(CGContextRef)context fromDictionary:(NSDictionary * - (CGRect)_frameForTextBlock:(DTTextBlock *)textBlock atIndex:(NSUInteger)location { + // workaround for crash that otherwise occurs from enumerating attributes on different threads + if (![NSThread isMainThread]) + { + __block CGRect frame; + dispatch_sync(dispatch_get_main_queue(), ^{ + frame = [self _frameForTextBlock:textBlock atIndex:location]; + }); + + return frame; + } + // -- end workaround + NSRange blockRange = [_attributedStringFragment rangeOfTextBlock:textBlock atIndex:location]; // need to reduce to actually visible string range in layout frame @@ -1005,6 +1017,77 @@ - (void)_setForgroundColorInContext:(CGContextRef)context forGlyphRun:(DTCoreTex CGContextSetStrokeColorWithColor(context, color.CGColor); } +// draws the text blocks that should be visible within the mentioned range +- (void)_drawTextBlocksInContext:(CGContextRef)context inRange:(NSRange)range +{ + // FIXME: enumerating the blocks and then inside this block enumerating the blocks to get their ranges on a background thread is causing a crash if this doesn't happen on main thread. + + // workaround for racing crash + if (![NSThread isMainThread]) + { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self _drawTextBlocksInContext:context inRange:range]; + }); + + return; + } + + CGRect clipRect = CGContextGetClipBoundingBox(context); + + __block NSMutableSet *handledBlocks = [NSMutableSet set]; + + // enumerate all text blocks in this range + [_attributedStringFragment enumerateAttribute:DTTextBlocksAttribute inRange:range options:0 + usingBlock:^(NSArray *blockArray, NSRange blockArrayRange, BOOL *stop) { + for (DTTextBlock *oneBlock in blockArray) + { + // make sure we only handle it once + if (![handledBlocks containsObject:oneBlock]) + { + CGRect frame = [self _frameForTextBlock:oneBlock atIndex:blockArrayRange.location]; + CGRect visiblePart = CGRectIntersection(frame, clipRect); + + // do not draw boxes which are not in the current clip rect + if (!CGRectIsInfinite(visiblePart)) + { + BOOL shouldDrawStandardBackground = YES; + if (_textBlockHandler) + { + _textBlockHandler(oneBlock, frame, context, &shouldDrawStandardBackground); + } + + // draw standard background if necessary + if (shouldDrawStandardBackground) + { + oneBlock.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3]; + + if (oneBlock.backgroundColor) + { + CGColorRef color = [oneBlock.backgroundColor CGColor]; + CGContextSetFillColorWithColor(context, color); + CGContextFillRect(context, frame); + } + } + + if (_DTCoreTextLayoutFramesShouldDrawDebugFrames) + { + CGContextSaveGState(context); + + // draw line bounds + CGContextSetRGBStrokeColor(context, 0.5, 0, 0.5f, 1.0f); + CGContextSetLineWidth(context, 2); + CGContextStrokeRect(context, CGRectInset(frame, 2, 2)); + + CGContextRestoreGState(context); + } + } + + [handledBlocks addObject:oneBlock]; + } + } + }]; +} + - (void)drawInContext:(CGContextRef)context options:(DTCoreTextLayoutFrameDrawingOptions)options { BOOL drawLinks = !(options & DTCoreTextLayoutFrameDrawingOmitLinks); @@ -1053,63 +1136,19 @@ - (void)drawInContext:(CGContextRef)context options:(DTCoreTextLayoutFrameDrawin return; } + DTCoreTextLayoutLine *firstLine = [visibleLines objectAtIndex:0]; + DTCoreTextLayoutLine *lastLine = [visibleLines lastObject]; + + NSRange stringRangeToDraw = firstLine.stringRange; + stringRangeToDraw = NSUnionRange(stringRangeToDraw, lastLine.stringRange); CGContextSaveGState(context); // need to push the CG context so that the UI* based colors can be set UIGraphicsPushContext(context); - // text block handling - __block NSMutableSet *handledBlocks = [NSMutableSet set]; - - // enumerate all text blocks in this range - [_attributedStringFragment enumerateAttribute:DTTextBlocksAttribute inRange:_stringRange options:0 - usingBlock:^(NSArray *blockArray, NSRange range, BOOL *stop) { - for (DTTextBlock *oneBlock in blockArray) - { - // make sure we only handle it once - if (![handledBlocks containsObject:oneBlock]) - { - CGRect frame = [self _frameForTextBlock:oneBlock atIndex:range.location]; - - BOOL shouldDrawStandardBackground = YES; - if (_textBlockHandler) - { - _textBlockHandler(oneBlock, frame, context, &shouldDrawStandardBackground); - } - - // draw standard background if necessary - if (shouldDrawStandardBackground) - { - if (oneBlock.backgroundColor) - { - CGColorRef color = [oneBlock.backgroundColor CGColor]; - CGContextSetFillColorWithColor(context, color); - CGContextFillRect(context, frame); - } - } - - if (_DTCoreTextLayoutFramesShouldDrawDebugFrames) - { - CGContextSaveGState(context); - - // draw line bounds - CGContextSetRGBStrokeColor(context, 0.5, 0, 0.5f, 1.0f); - CGContextSetLineWidth(context, 2); - CGContextStrokeRect(context, CGRectInset(frame, 2, 2)); - - CGContextRestoreGState(context); - } - - [handledBlocks addObject:oneBlock]; - } - } - - - }]; + [self _drawTextBlocksInContext:context inRange:stringRangeToDraw]; - - for (DTCoreTextLayoutLine *oneLine in visibleLines) { if ([oneLine isHorizontalRule]) diff --git a/Core/Source/NSMutableAttributedString+HTML.m b/Core/Source/NSMutableAttributedString+HTML.m index 8e8ea107d..814833af4 100644 --- a/Core/Source/NSMutableAttributedString+HTML.m +++ b/Core/Source/NSMutableAttributedString+HTML.m @@ -151,6 +151,22 @@ - (void)appendEndOfParagraph } } + // transfer blocks + NSArray *blocks = [attributes objectForKey:DTTextBlocksAttribute]; + + if (blocks) + { + [appendAttributes setObject:blocks forKey:DTTextBlocksAttribute]; + } + + // transfer lists + NSArray *lists = [attributes objectForKey:DTTextListsAttribute]; + + if (lists) + { + [appendAttributes setObject:lists forKey:DTTextListsAttribute]; + } + NSAttributedString *newlineString = [[NSAttributedString alloc] initWithString:@"\n" attributes:appendAttributes]; [self appendAttributedString:newlineString]; } From 50908bc4b35e309a7dbb17a1f14802da92a23894 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 09:22:31 +0200 Subject: [PATCH 18/30] Removed test code --- Core/Source/DTCoreTextLayoutFrame.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index bde5fdf0e..9917e8b23 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -1059,8 +1059,6 @@ - (void)_drawTextBlocksInContext:(CGContextRef)context inRange:(NSRange)range // draw standard background if necessary if (shouldDrawStandardBackground) { - oneBlock.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3]; - if (oneBlock.backgroundColor) { CGColorRef color = [oneBlock.backgroundColor CGColor]; From e845cb11de6cb881b1fc7d9383449a614738ea1d Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 09:28:30 +0200 Subject: [PATCH 19/30] Moved drawing of single blocks into method for clarity #541 --- Core/Source/DTCoreTextLayoutFrame.m | 61 ++++++++++++++++------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index 9917e8b23..3a3458f4c 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -1017,6 +1017,38 @@ - (void)_setForgroundColorInContext:(CGContextRef)context forGlyphRun:(DTCoreTex CGContextSetStrokeColorWithColor(context, color.CGColor); } +- (void)_drawTextBlock:(DTTextBlock *)textBlock inContext:(CGContextRef)context frame:(CGRect)frame +{ + BOOL shouldDrawStandardBackground = YES; + if (_textBlockHandler) + { + _textBlockHandler(textBlock, frame, context, &shouldDrawStandardBackground); + } + + // draw standard background if necessary + if (shouldDrawStandardBackground) + { + if (textBlock.backgroundColor) + { + CGColorRef color = [textBlock.backgroundColor CGColor]; + CGContextSetFillColorWithColor(context, color); + CGContextFillRect(context, frame); + } + } + + if (_DTCoreTextLayoutFramesShouldDrawDebugFrames) + { + CGContextSaveGState(context); + + // draw line bounds + CGContextSetRGBStrokeColor(context, 0.5, 0, 0.5f, 1.0f); + CGContextSetLineWidth(context, 2); + CGContextStrokeRect(context, CGRectInset(frame, 2, 2)); + + CGContextRestoreGState(context); + } +} + // draws the text blocks that should be visible within the mentioned range - (void)_drawTextBlocksInContext:(CGContextRef)context inRange:(NSRange)range { @@ -1050,34 +1082,7 @@ - (void)_drawTextBlocksInContext:(CGContextRef)context inRange:(NSRange)range // do not draw boxes which are not in the current clip rect if (!CGRectIsInfinite(visiblePart)) { - BOOL shouldDrawStandardBackground = YES; - if (_textBlockHandler) - { - _textBlockHandler(oneBlock, frame, context, &shouldDrawStandardBackground); - } - - // draw standard background if necessary - if (shouldDrawStandardBackground) - { - if (oneBlock.backgroundColor) - { - CGColorRef color = [oneBlock.backgroundColor CGColor]; - CGContextSetFillColorWithColor(context, color); - CGContextFillRect(context, frame); - } - } - - if (_DTCoreTextLayoutFramesShouldDrawDebugFrames) - { - CGContextSaveGState(context); - - // draw line bounds - CGContextSetRGBStrokeColor(context, 0.5, 0, 0.5f, 1.0f); - CGContextSetLineWidth(context, 2); - CGContextStrokeRect(context, CGRectInset(frame, 2, 2)); - - CGContextRestoreGState(context); - } + [self _drawTextBlock:oneBlock inContext:context frame:frame]; } [handledBlocks addObject:oneBlock]; From e4b56800d94a084abcf56d065b698f4551409a27 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 12:32:47 +0200 Subject: [PATCH 20/30] Implemented left/right padding support for stacked text blocks --- Core/Source/DTCoreTextLayoutFrame.m | 500 ++++++++++++++------ Core/Source/NSAttributedString+DTCoreText.m | 3 + Demo/Resources/CurrentTest.html | 9 +- 3 files changed, 369 insertions(+), 143 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index 3a3458f4c..9dc1b627b 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -298,9 +298,6 @@ - (CGPoint)_algorithmWebKit_BaselineOriginToPositionLine:(DTCoreTextLayoutLine * { CGPoint baselineOrigin = previousLine.baselineOrigin; - DTTextBlock *previousTextBlock = nil; - DTTextBlock *currentTextBlock = nil; - if (previousLine) { baselineOrigin.y = CGRectGetMaxY(previousLine.frame); @@ -326,8 +323,6 @@ - (CGPoint)_algorithmWebKit_BaselineOriginToPositionLine:(DTCoreTextLayoutLine * DTCoreTextParagraphStyle *paragraphStyle = [previousLine paragraphStyle]; baselineOrigin.y += paragraphStyle.paragraphSpacing; } - - previousTextBlock = [previousLine.textBlocks lastObject]; } else { @@ -359,13 +354,23 @@ - (CGPoint)_algorithmWebKit_BaselineOriginToPositionLine:(DTCoreTextLayoutLine * { baselineOrigin.y += paragraphStyle.paragraphSpacingBefore; } + + // add padding for closed text blocks + for (DTTextBlock *previousTextBlock in previousLine.textBlocks) + { + if (![line.textBlocks containsObject:previousLine]) + { + baselineOrigin.y += previousTextBlock.padding.bottom; + } + } - currentTextBlock = [line.textBlocks lastObject]; - - if (currentTextBlock != previousTextBlock) + // add padding for newly opened text blocks + for (DTTextBlock *currentTextBlock in line.textBlocks) { - baselineOrigin.y += previousTextBlock.padding.bottom; - baselineOrigin.y += currentTextBlock.padding.top; + if (![previousLine.textBlocks containsObject:currentTextBlock]) + { + baselineOrigin.y += currentTextBlock.padding.top; + } } // origins are rounded @@ -425,7 +430,7 @@ - (void)_buildLinesWithTypesetter NSUInteger fittingLength = 0; BOOL shouldTruncateLine = NO; - DTTextBlock *currentTextBlock; // global because bottom padding will be added at bottom of frame + //DTTextBlock *currentTextBlock; // global because bottom padding will be added at bottom of frame do // for each line { @@ -457,22 +462,33 @@ - (void)_buildLinesWithTypesetter CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent); // add left padding to offset - currentTextBlock = [[_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:lineRange.location effectiveRange:NULL] lastObject]; - CGFloat lineOriginX = _frame.origin.x + headIndent + currentTextBlock.padding.left; + //currentTextBlock = [[_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:lineRange.location effectiveRange:NULL] lastObject]; + CGFloat lineOriginX = _frame.origin.x + headIndent; // + currentTextBlock.padding.left; CGFloat availableSpace; - CGFloat offset = headIndent + currentTextBlock.padding.left; + + NSArray *textBlocks = [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:lineRange.location effectiveRange:NULL]; + CGFloat totalLeftPadding = 0; + CGFloat totalRightPadding = 0; + + for (DTTextBlock *oneTextBlock in textBlocks) + { + totalLeftPadding += oneTextBlock.padding.left; + totalRightPadding += oneTextBlock.padding.right; + } if (tailIndent<=0) { // negative tail indent is measured from trailing margin (we assume LTR here) - availableSpace = _frame.size.width - offset - currentTextBlock.padding.right + tailIndent; + availableSpace = _frame.size.width - headIndent - totalRightPadding + tailIndent - totalLeftPadding; } else { - availableSpace = tailIndent - offset - currentTextBlock.padding.right; + availableSpace = tailIndent - headIndent - totalLeftPadding - totalRightPadding; } + CGFloat offset = headIndent + totalLeftPadding; + // find how many characters we get into this line lineRange.length = CTTypesetterSuggestLineBreak(typesetter, lineRange.location, availableSpace); @@ -584,6 +600,8 @@ - (void)_buildLinesWithTypesetter case kCTNaturalTextAlignment: { + lineOriginX = _frame.origin.x + offset; + if (baseWritingDirection != kCTWritingDirectionRightToLeft) { break; @@ -696,8 +714,17 @@ - (void)_buildLinesWithTypesetter // at this point we can correct the frame if it is open-ended if (_frame.size.height == CGFLOAT_OPEN_HEIGHT) { + DTCoreTextLayoutLine *lastLine = [_lines lastObject]; + + CGFloat totalPadding = 0; + + for (DTTextBlock *oneTextBlock in lastLine.textBlocks) + { + totalPadding += oneTextBlock.padding.bottom; + } + // need to add bottom padding if in text block - _additionalPaddingAtBottom = currentTextBlock.padding.bottom; + _additionalPaddingAtBottom = totalPadding; } } @@ -844,58 +871,12 @@ - (NSArray *)linesContainedInRect:(CGRect)rect return tmpArray; } -#pragma mark - Drawing -- (void)_setShadowInContext:(CGContextRef)context fromDictionary:(NSDictionary *)dictionary additionalOffset:(CGSize)additionalOffset -{ - DTColor *color = [dictionary objectForKey:@"Color"]; - CGSize offset = [[dictionary objectForKey:@"Offset"] CGSizeValue]; - CGFloat blur = [[dictionary objectForKey:@"Blur"] floatValue]; - - // add extra offset - offset.width += additionalOffset.width; - offset.height += additionalOffset.height; - - CGFloat scaleFactor = 1.0; - if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) - { - scaleFactor = [[UIScreen mainScreen] scale]; - } - - - // workaround for scale 1: strangely offset (1,1) with blur 0 does not draw any shadow, (1.01,1.01) does - if (scaleFactor==1.0) - { - if (fabs(offset.width)==1.0) - { - offset.width *= 1.50; - } - - if (fabs(offset.height)==1.0) - { - offset.height *= 1.50; - } - } - - CGContextSetShadowWithColor(context, offset, blur, color.CGColor); -} +#pragma mark - Text Block Helpers + -- (CGRect)_frameForTextBlock:(DTTextBlock *)textBlock atIndex:(NSUInteger)location +- (CGRect)_frameForTextBlock:(DTTextBlock *)textBlock withEffectiveRange:(NSRange)blockRange { - // workaround for crash that otherwise occurs from enumerating attributes on different threads - if (![NSThread isMainThread]) - { - __block CGRect frame; - dispatch_sync(dispatch_get_main_queue(), ^{ - frame = [self _frameForTextBlock:textBlock atIndex:location]; - }); - - return frame; - } - // -- end workaround - - NSRange blockRange = [_attributedStringFragment rangeOfTextBlock:textBlock atIndex:location]; - // need to reduce to actually visible string range in layout frame blockRange = NSIntersectionRange(blockRange, self.visibleStringRange); @@ -931,7 +912,315 @@ - (CGRect)_frameForTextBlock:(DTTextBlock *)textBlock atIndex:(NSUInteger)locati frame.size.width = _frame.size.width; // currently all blocks are 100% wide frame.size.height = CGRectGetMaxY(lastBlockLine.frame) - frame.origin.y + textBlock.padding.bottom; - return frame; + return CGRectIntegral(frame); +} + +- (NSRange)_effectiveRangeOfOutermostTextBlocksInRange:(NSRange)range +{ + NSLog(@"find with range: %@", NSStringFromRange(range)); + + + // find actual start of all blocks + + NSRange effectiveRange = NSMakeRange(0, 0); + NSUInteger length = [_attributedStringFragment length]; + + BOOL foundStartBlocks = NO; + + NSUInteger index = range.location; + + do + { + // stop searching for blocks if we are past end of range + if (index>=NSMaxRange(range)) + { + break; + } + + NSRange effectiveRangeOfBlocksArray; + NSArray *textBlocks = [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:index effectiveRange:&effectiveRangeOfBlocksArray]; + + // skip a range of empty blocks at start + if (!textBlocks) + { + index += effectiveRangeOfBlocksArray.length; + continue; + } + + foundStartBlocks = YES; + + // first text block is outermost, i.e. longest + DTTextBlock *outermostBlock = [textBlocks objectAtIndex:0]; + + if (effectiveRange.length) + { + effectiveRange = NSUnionRange(effectiveRange, effectiveRangeOfBlocksArray); + } + else + { + effectiveRange = effectiveRangeOfBlocksArray; + } + + NSUInteger searchIndex = effectiveRangeOfBlocksArray.location; + + // search backward for actual start of block + while (searchIndex > 0) + { + NSRange earlierBlocksRange; + NSArray *earlierBlocks = [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:searchIndex-1 effectiveRange:&earlierBlocksRange]; + + if (![earlierBlocks containsObject:outermostBlock]) + { + break; + } + + effectiveRange = NSUnionRange(effectiveRange, earlierBlocksRange); + + searchIndex = earlierBlocksRange.location; + } + } + while (!foundStartBlocks); + + // no text blocks in range + if (!foundStartBlocks) + { + return NSMakeRange(NSNotFound, 0); + } + + // search for the end blocks for this range + BOOL foundEndBlocks = NO; + + index = NSMaxRange(range)-1; + + do + { + // stop searching for blocks if we are past end of range + if (index >= length) + { + break; + } + + NSRange effectiveRangeOfBlocksArray; + NSArray *textBlocks = [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:index-1 effectiveRange:&effectiveRangeOfBlocksArray]; + + // skip a range of empty blocks at start + if (!textBlocks) + { + index = effectiveRangeOfBlocksArray.location; + continue; + } + + foundEndBlocks = YES; + + // first text block is outermost, i.e. longest + DTTextBlock *outermostBlock = [textBlocks objectAtIndex:0]; + + effectiveRange = NSUnionRange(effectiveRange, effectiveRangeOfBlocksArray); + + NSUInteger searchIndex = NSMaxRange(effectiveRangeOfBlocksArray); + + // search forward for actual end of block + while (searchIndex < length) + { + NSRange laterBlocksRange; + NSArray *laterBlocks = [_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:searchIndex effectiveRange:&laterBlocksRange]; + + if (![laterBlocks containsObject:outermostBlock]) + { + break; + } + + effectiveRange = NSUnionRange(effectiveRange, laterBlocksRange); + + searchIndex = NSMaxRange(laterBlocksRange); + } + } + while (!foundEndBlocks); + + + if (foundStartBlocks&&foundEndBlocks) + { + return effectiveRange; + } + else + { + return NSMakeRange(NSNotFound, 0); + } +} + + +// only enumerate blocks at a given level +// returns YES if there was at least one block enumerated at this level +- (BOOL)_enumerateTextBlocksAtLevel:(NSUInteger)level inRange:(NSRange)range usingBlock:(void (^)(DTTextBlock *textBlock, CGRect frame, NSRange effectiveRange, BOOL *stop))block +{ + NSUInteger length = [_attributedStringFragment length]; + NSUInteger index = range.location; + + BOOL foundBlockAtLevel = NO; + + while (index -

Regular

Bold

Italic

Bold+Italic

+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor +
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor +
+
From 6cef609482b218e9ff11439c5bbb7bb4227bf1dc Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 14:11:38 +0200 Subject: [PATCH 21/30] Implement new method of iterating over text boxes #543 also fixes #541 --- Core/Source/DTCoreTextLayoutFrame.m | 128 +++++++++++++++------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index 9dc1b627b..12646c3d3 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -874,54 +874,9 @@ - (NSArray *)linesContainedInRect:(CGRect)rect #pragma mark - Text Block Helpers - -- (CGRect)_frameForTextBlock:(DTTextBlock *)textBlock withEffectiveRange:(NSRange)blockRange -{ - // need to reduce to actually visible string range in layout frame - blockRange = NSIntersectionRange(blockRange, self.visibleStringRange); - - DTCoreTextLayoutLine *firstBlockLine = [self lineContainingIndex:blockRange.location]; - DTCoreTextLayoutLine *lastBlockLine = [self lineContainingIndex:NSMaxRange(blockRange)-1]; - - CGRect frame; - frame.origin = firstBlockLine.frame.origin; - frame.origin.x = _frame.origin.x; // currently all boxes are full with - frame.origin.y -= textBlock.padding.top; - - CGFloat maxWidth = 0; - - for (NSUInteger index = blockRange.location; index=level;i--) + { + if (i<0) + { + break; + } + + DTTextBlock *oneTextBlock = [firstBlockLine.textBlocks objectAtIndex:i]; + + blockFrame.origin.y -= oneTextBlock.padding.top; + blockFrame.size.height += oneTextBlock.padding.top; + } + + // top padding we get from last line + for (int i = [lastBlockLine.textBlocks count]-1; i>=level;i--) + { + if (i<0) + { + break; + } + + DTTextBlock *oneTextBlock = [lastBlockLine.textBlocks objectAtIndex:i]; + + blockFrame.size.height += oneTextBlock.padding.bottom; + } + + // adjust left and right margins with block stack padding + for (int i=0; i Date: Wed, 14 Aug 2013 14:18:38 +0200 Subject: [PATCH 22/30] Fixed line spacing bug with blocks #543 --- Core/Source/DTCoreTextLayoutFrame.m | 5 +---- Demo/Resources/CurrentTest.html | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Core/Source/DTCoreTextLayoutFrame.m b/Core/Source/DTCoreTextLayoutFrame.m index 12646c3d3..dbf6b5950 100644 --- a/Core/Source/DTCoreTextLayoutFrame.m +++ b/Core/Source/DTCoreTextLayoutFrame.m @@ -358,7 +358,7 @@ - (CGPoint)_algorithmWebKit_BaselineOriginToPositionLine:(DTCoreTextLayoutLine * // add padding for closed text blocks for (DTTextBlock *previousTextBlock in previousLine.textBlocks) { - if (![line.textBlocks containsObject:previousLine]) + if (![line.textBlocks containsObject:previousTextBlock]) { baselineOrigin.y += previousTextBlock.padding.bottom; } @@ -430,8 +430,6 @@ - (void)_buildLinesWithTypesetter NSUInteger fittingLength = 0; BOOL shouldTruncateLine = NO; - //DTTextBlock *currentTextBlock; // global because bottom padding will be added at bottom of frame - do // for each line { while (lineRange.location >= (currentParagraphRange.location+currentParagraphRange.length)) @@ -462,7 +460,6 @@ - (void)_buildLinesWithTypesetter CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent); // add left padding to offset - //currentTextBlock = [[_attributedStringFragment attribute:DTTextBlocksAttribute atIndex:lineRange.location effectiveRange:NULL] lastObject]; CGFloat lineOriginX = _frame.origin.x + headIndent; // + currentTextBlock.padding.left; CGFloat availableSpace; diff --git a/Demo/Resources/CurrentTest.html b/Demo/Resources/CurrentTest.html index c31a7c438..0b66099da 100644 --- a/Demo/Resources/CurrentTest.html +++ b/Demo/Resources/CurrentTest.html @@ -11,10 +11,9 @@
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor -
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
From 90e0f94d49ea05ee1b271f316ac5f1119e444824 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 15:16:49 +0200 Subject: [PATCH 23/30] Updated to DTFoundation 1.5.2 --- Core/Externals/DTFoundation | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Externals/DTFoundation b/Core/Externals/DTFoundation index 3c88144dc..2ece9bcaa 160000 --- a/Core/Externals/DTFoundation +++ b/Core/Externals/DTFoundation @@ -1 +1 @@ -Subproject commit 3c88144dc59c6a3bfaffd7cebafb608077716854 +Subproject commit 2ece9bcaa859b93caebf6895e5820e2c0c630439 From ddd999444bda3c8f4d21a8f52cb9d472244c1117 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Wed, 14 Aug 2013 18:26:01 +0200 Subject: [PATCH 24/30] Adjust content inset for future iOS versions #531 --- Demo/Source/DemoTextViewController.m | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Demo/Source/DemoTextViewController.m b/Demo/Source/DemoTextViewController.m index 2a7fae458..b17fa15df 100644 --- a/Demo/Source/DemoTextViewController.m +++ b/Demo/Source/DemoTextViewController.m @@ -249,6 +249,50 @@ - (void)viewWillDisappear:(BOOL)animated; [super viewWillDisappear:animated]; } +// this is only called on >= iOS 5 +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + + if (![self respondsToSelector:@selector(topLayoutGuide)]) + { + return; + } + + // this also compiles with iOS 6 SDK, but will work with later SDKs too + CGFloat topInset = [[self valueForKeyPath:@"topLayoutGuide.length"] floatValue]; + CGFloat bottomInset = [[self valueForKeyPath:@"bottomLayoutGuide.length"] floatValue]; + + UIEdgeInsets outerInsets = UIEdgeInsetsMake(topInset, 0, bottomInset, 0); + UIEdgeInsets innerInsets = outerInsets; + innerInsets.left += 10; + innerInsets.right += 10; + innerInsets.top += 10; + innerInsets.bottom += 10; + + CGPoint innerScrollOffset = CGPointMake(-innerInsets.left, -innerInsets.top); + CGPoint outerScrollOffset = CGPointMake(-outerInsets.left, -outerInsets.top); + + _textView.contentInset = innerInsets; + _textView.contentOffset = innerScrollOffset; + _textView.scrollIndicatorInsets = outerInsets; + + _iOS6View.contentInset = outerInsets; + _iOS6View.contentOffset = outerScrollOffset; + _iOS6View.scrollIndicatorInsets = outerInsets; + + _charsView.contentInset = outerInsets; + _charsView.contentOffset = outerScrollOffset; + _charsView.scrollIndicatorInsets = outerInsets; + + _rangeView.contentInset = outerInsets; + _rangeView.contentOffset = outerScrollOffset; + _rangeView.scrollIndicatorInsets = outerInsets; + + _htmlView.contentInset = outerInsets; + _htmlView.contentOffset = outerScrollOffset; + _htmlView.scrollIndicatorInsets = outerInsets; +} #pragma mark Private Methods From b0c0fc2d09ec60ff0776d3e7c80bbcf4800be387 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Thu, 15 Aug 2013 21:32:03 +0200 Subject: [PATCH 25/30] Implemented workaround for rdar://14684188 --- Core/Source/DTCompatibility.h | 5 +++++ Core/Source/DTHTMLAttributedStringBuilder.m | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Core/Source/DTCompatibility.h b/Core/Source/DTCompatibility.h index 7816ef01a..16c2fa701 100644 --- a/Core/Source/DTCompatibility.h +++ b/Core/Source/DTCompatibility.h @@ -29,6 +29,11 @@ #define DTCORETEXT_NEEDS_ATTRIBUTE_REPLACEMENT_LEAK_FIX 1 #endif + // iOS 7 bug (rdar://14684188) workaround, can be removed once this bug is fixed + #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 + #define DTCORETEXT_FIX_14684188 1 + #endif + #endif diff --git a/Core/Source/DTHTMLAttributedStringBuilder.m b/Core/Source/DTHTMLAttributedStringBuilder.m index e4bcd7f73..806cb9ee1 100644 --- a/Core/Source/DTHTMLAttributedStringBuilder.m +++ b/Core/Source/DTHTMLAttributedStringBuilder.m @@ -280,6 +280,11 @@ - (BOOL)_buildString _defaultTag.paragraphStyle = _defaultParagraphStyle; _defaultTag.textScale = _textScale; +#if DTCORETEXT_FIX_14684188 + // workaround, only necessary while rdar://14684188 is not fixed + _defaultTag.textColor = [UIColor blackColor]; +#endif + id defaultColor = [_options objectForKey:DTDefaultTextColor]; if (defaultColor) { From 9b3919a61c6e4933c9a6d1df2a14e044e135b1a4 Mon Sep 17 00:00:00 2001 From: Amro Mousa Date: Thu, 15 Aug 2013 23:34:31 -0400 Subject: [PATCH 26/30] add support for cascading classes --- Core/Source/DTCSSStylesheet.m | 118 ++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/Core/Source/DTCSSStylesheet.m b/Core/Source/DTCSSStylesheet.m index 9c34b1ea5..90a451199 100644 --- a/Core/Source/DTCSSStylesheet.m +++ b/Core/Source/DTCSSStylesheet.m @@ -55,7 +55,7 @@ - (id)initWithStyleBlock:(NSString *)css if (self) { - _styles = [[NSMutableDictionary alloc] init]; + _styles = [[NSMutableDictionary alloc] init]; [self parseStyleBlock:css]; } @@ -69,8 +69,8 @@ - (id)initWithStylesheet:(DTCSSStylesheet *)stylesheet if (self) { - _styles = [[NSMutableDictionary alloc] init]; - + _styles = [[NSMutableDictionary alloc] init]; + [self mergeStylesheet:stylesheet]; } @@ -145,7 +145,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles if (listStylePosition != DTCSSListStylePositionInvalid) { [styles setObject:oneComponent forKey:@"list-style-position"]; - + positionWasSet = YES; continue; } @@ -160,7 +160,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles { NSString *fontStyle = @"normal"; NSArray *validFontStyles = [NSArray arrayWithObjects:@"italic", @"oblique", nil]; - + NSString *fontVariant = @"normal"; NSArray *validFontVariants = [NSArray arrayWithObjects:@"small-caps", nil]; BOOL fontVariantSet = NO; @@ -180,7 +180,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles NSMutableString *fontFamily = [NSMutableString string]; NSArray *components = [shortHand componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - + for (NSString *oneComponent in components) { // try font size keywords @@ -216,7 +216,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles continue; } } - + if (fontSizeSet) { if ([suffixesToIgnore containsObject:oneComponent]) @@ -250,9 +250,9 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles } } } - + [styles removeObjectForKey:@"font"]; - + // size and family are mandatory, without them this is invalid if ([fontSize length] && [fontFamily length]) { @@ -374,7 +374,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles bottomPadding = onlyValue; leftPadding = onlyValue; } - + // only apply the ones where there is no previous direct setting if (![styles objectForKey:@"padding-top"]) @@ -386,7 +386,7 @@ - (void)_uncompressShorthands:(NSMutableDictionary *)styles { [styles setObject:rightPadding forKey:@"padding-right"]; } - + if (![styles objectForKey:@"padding-bottom"]) { [styles setObject:bottomPadding forKey:@"padding-bottom"]; @@ -406,7 +406,7 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors { NSArray *split = [selectors componentsSeparatedByString:@","]; - for (NSString *selector in split) + for (NSString *selector in split) { NSString *cleanSelector = [selector stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; @@ -462,10 +462,10 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors } } } - + // need to uncompress because otherwise we might get shorthands and non-shorthands together [self _uncompressShorthands:ruleDictionary]; - + // check if there is a pseudo selector NSRange colonRange = [cleanSelector rangeOfString:@":"]; NSString *pseudoSelector = nil; @@ -479,7 +479,7 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors for (NSString *oneRuleKey in [ruleDictionary allKeys]) { id value = [ruleDictionary objectForKey:oneRuleKey]; - + // prefix key with the pseudo selector NSString *prefixedKey = [NSString stringWithFormat:@"%@:%@", pseudoSelector, oneRuleKey]; [ruleDictionary setObject:value forKey:prefixedKey]; @@ -489,18 +489,18 @@ - (void)_addStyleRule:(NSString *)rule withSelector:(NSString*)selectors NSDictionary *existingRulesForSelector = [_styles objectForKey:cleanSelector]; - if (existingRulesForSelector) + if (existingRulesForSelector) { // substitute new rules over old ones NSMutableDictionary *tmpDict = [existingRulesForSelector mutableCopy]; // append new rules [tmpDict addEntriesFromDictionary:ruleDictionary]; - + // save it [_styles setObject:tmpDict forKey:cleanSelector]; } - else + else { [_styles setObject:ruleDictionary forKey:cleanSelector]; } @@ -564,7 +564,7 @@ - (void)parseStyleBlock:(NSString*)css { // If we start a new rule... - if (braceLevel == 0) + if (braceLevel == 0) { // Grab the selector (we'll process it in a moment) selector = [css substringWithRange:NSMakeRange(braceMarker, i-braceMarker)]; @@ -578,10 +578,10 @@ - (void)parseStyleBlock:(NSString*)css } // A closing brace! - else if (c == '}') + else if (c == '}') { // If we finished a rule... - if (braceLevel == 1) + if (braceLevel == 1) { NSString *rule = [css substringWithRange:NSMakeRange(braceMarker, i-braceMarker)]; @@ -638,7 +638,7 @@ - (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element match // Get based on element NSDictionary *byTagName = [self.styles objectForKey:element.name]; - if (byTagName) + if (byTagName) { [tmpDict addEntriesFromDictionary:byTagName]; } @@ -647,6 +647,10 @@ - (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element match NSString *classString = [element.attributes objectForKey:@"class"]; NSArray *classes = [classString componentsSeparatedByString:@" "]; + // Find all classes by walking up the heirarchy and compute possible selector combinations + NSArray *ancestorClassArrays = [self findAncestorClassArraysForElement:element]; + NSArray *cascadedSelectors = [self computeCascadedClassSelectorsWithAncestorClasses:ancestorClassArrays]; + NSMutableSet *tmpMatchedSelectors; if (matchedSelectors) @@ -654,25 +658,26 @@ - (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element match tmpMatchedSelectors = [[NSMutableSet alloc] init]; } - for (NSString *class in classes) + for (NSString *class in classes) { - NSString *classRule = [NSString stringWithFormat:@".%@", class]; NSString *classAndTagRule = [NSString stringWithFormat:@"%@.%@", element.name, class]; - - NSDictionary *byClass = [_styles objectForKey:classRule]; NSDictionary *byClassAndName = [_styles objectForKey:classAndTagRule]; - if (byClass) + if (byClassAndName) { - [tmpDict addEntriesFromDictionary:byClass]; - - [tmpMatchedSelectors addObject:classRule]; + [tmpDict addEntriesFromDictionary:byClassAndName]; + [tmpMatchedSelectors addObject:classAndTagRule]; } - if (byClassAndName) + //This covers the "by class" only case (e.g. .foo) + for (NSString *cascadedSelector in cascadedSelectors) { - [tmpDict addEntriesFromDictionary:byClassAndName]; - [tmpMatchedSelectors addObject:classAndTagRule]; + NSDictionary *byCascadedClassName = [_styles objectForKey:cascadedSelector]; + if (byCascadedClassName) + { + [tmpDict addEntriesFromDictionary:byCascadedClassName]; + [tmpMatchedSelectors addObject:cascadedSelector]; + } } } @@ -719,6 +724,53 @@ - (NSDictionary *)styles return _styles; } +- (NSArray *)findAncestorClassArraysForElement:(DTHTMLElement *)element +{ + // Walk up the heirarchy looking for parents with class attributes then compute cascades + NSMutableArray *ancestorClassArrays = [NSMutableArray array]; + + DTHTMLElement *currentElement = element; + while (currentElement != nil) + { + NSString *currentElementClassString = [currentElement.attributes objectForKey:@"class"]; + NSArray *currentElementClasses = [currentElementClassString componentsSeparatedByString:@" "]; + if (currentElementClasses.count) + { + [ancestorClassArrays insertObject:currentElementClasses atIndex:0]; + } + + currentElement = currentElement.parentElement; + } + + return ancestorClassArrays; +} + +- (NSArray *)computeCascadedClassSelectorsWithAncestorClasses:(NSArray *)ancestorClasses +{ + NSMutableOrderedSet *cascadedSelectors = [[NSMutableOrderedSet alloc] init]; + + if (ancestorClasses.count) { + NSArray *classes = ancestorClasses[0]; + + // Find selector combinations for all ancestors that are leaves of the ancesor the current class array belongs to + NSArray *remainingAncessorClasses = [ancestorClasses subarrayWithRange:NSMakeRange(1, ancestorClasses.count - 1)]; + NSArray *descendentSelectors = [self computeCascadedClassSelectorsWithAncestorClasses:remainingAncessorClasses]; + + for (NSString *class in classes) + { + [cascadedSelectors addObject:[NSString stringWithFormat:@".%@", class]]; + + for (NSString *descendentSelector in descendentSelectors) + { + [cascadedSelectors addObject:[NSString stringWithFormat:@"%@", descendentSelector]]; + [cascadedSelectors addObject:[NSString stringWithFormat:@".%@ %@", class, descendentSelector]]; + } + } + } + + return [cascadedSelectors array]; +} + #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone From 903a6ef6d5391a569f4a77e2e1b1ada622428d56 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Fri, 16 Aug 2013 08:11:16 +0200 Subject: [PATCH 27/30] Added CSS Cascading unit test #544 --- Core/Test/Resources/CSSCascading.html | 39 +++++++++++++++ .../DTHTMLAttributedStringBuilderTest.m | 49 +++++++++++++++++++ DTCoreText.xcodeproj/project.pbxproj | 6 +++ 3 files changed, 94 insertions(+) create mode 100644 Core/Test/Resources/CSSCascading.html diff --git a/Core/Test/Resources/CSSCascading.html b/Core/Test/Resources/CSSCascading.html new file mode 100644 index 000000000..c395fab93 --- /dev/null +++ b/Core/Test/Resources/CSSCascading.html @@ -0,0 +1,39 @@ + + + + +
+
+
+
+ me + ow +
+ Meow +
+
+
+ + \ No newline at end of file diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index e2d0d74c1..3e4997078 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -583,4 +583,53 @@ - (void)testPrefixWithNewlines }]; } +#pragma mark - CSS Tests + +// issue 544 +- (void)testCascading +{ + NSAttributedString *output = [self _attributedStringFromTestFileName:@"CSSCascading"]; + + NSUInteger index1 = 0; + NSUInteger index2 = 3; + NSUInteger index3 = 6; + NSUInteger index4 = 8; + + // check first "me" + NSDictionary *attributes1 = [output attributesAtIndex:index1 effectiveRange:NULL]; + NSNumber *underLine1 = [output attribute:(id)kCTUnderlineStyleAttributeName atIndex:index1 effectiveRange:NULL]; + STAssertTrue([underLine1 integerValue]==1, @"First item should be underlined"); + DTColor *foreground1 = [attributes1 foregroundColor]; + NSString *foreground1HTML = [foreground1 htmlHexString]; + BOOL colorOk1 = ([foreground1HTML isEqualToString:@"008000"]); + STAssertTrue(colorOk1, @"First item should be green"); + + // check first "ow" + NSDictionary *attributes2 = [output attributesAtIndex:index2 effectiveRange:NULL]; + NSNumber *underLine2 = [output attribute:(id)kCTUnderlineStyleAttributeName atIndex:index2 effectiveRange:NULL]; + STAssertTrue([underLine2 integerValue]==1, @"Second item should be underlined"); + DTColor *foreground2 = [attributes2 foregroundColor]; + NSString *foreground2HTML = [foreground2 htmlHexString]; + BOOL colorOk2 = ([foreground2HTML isEqualToString:@"ffa500"]); + STAssertTrue(colorOk2, @"Second item should be orange"); + + // check second "me" + NSDictionary *attributes3 = [output attributesAtIndex:index3 effectiveRange:NULL]; + NSNumber *underLine3 = [output attribute:(id)kCTUnderlineStyleAttributeName atIndex:index3 effectiveRange:NULL]; + STAssertTrue([underLine3 integerValue]==0, @"Third item should not be underlined"); + DTColor *foreground3 = [attributes3 foregroundColor]; + NSString *foreground3HTML = [foreground3 htmlHexString]; + BOOL colorOk3 = ([foreground3HTML isEqualToString:@"ff0000"]); + STAssertTrue(colorOk3, @"Third item should be red"); + + // check second "ow" + NSDictionary *attributes4 = [output attributesAtIndex:index4 effectiveRange:NULL]; + NSNumber *underLine4 = [output attribute:(id)kCTUnderlineStyleAttributeName atIndex:index4 effectiveRange:NULL]; + STAssertTrue([underLine4 integerValue]==1, @"First item should be underlined"); + DTColor *foreground4 = [attributes4 foregroundColor]; + NSString *foreground4HTML = [foreground4 htmlHexString]; + BOOL colorOk4 = ([foreground4HTML isEqualToString:@"008000"]); + STAssertTrue(colorOk4, @"First item should be green"); +} + @end diff --git a/DTCoreText.xcodeproj/project.pbxproj b/DTCoreText.xcodeproj/project.pbxproj index c4d5a3e33..f9e4549fe 100644 --- a/DTCoreText.xcodeproj/project.pbxproj +++ b/DTCoreText.xcodeproj/project.pbxproj @@ -106,6 +106,8 @@ A730BCD716D29588003B849F /* Languages.html in Resources */ = {isa = PBXBuildFile; fileRef = A730BCD616D29588003B849F /* Languages.html */; }; A730BCD816D29588003B849F /* Languages.html in Resources */ = {isa = PBXBuildFile; fileRef = A730BCD616D29588003B849F /* Languages.html */; }; A730BCD916D29588003B849F /* Languages.html in Resources */ = {isa = PBXBuildFile; fileRef = A730BCD616D29588003B849F /* Languages.html */; }; + A7343F3317BDF64A00EF9B83 /* CSSCascading.html in Resources */ = {isa = PBXBuildFile; fileRef = A7343F3217BDF64A00EF9B83 /* CSSCascading.html */; }; + A7343F3417BDF64A00EF9B83 /* CSSCascading.html in Resources */ = {isa = PBXBuildFile; fileRef = A7343F3217BDF64A00EF9B83 /* CSSCascading.html */; }; A7358D701680F2B0005F1352 /* libDTFoundation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7E383B9160DFEA800CF72D6 /* libDTFoundation.a */; }; A73885A51606E9B2001D60C4 /* EmojiTest.html in Resources */ = {isa = PBXBuildFile; fileRef = A73885A41606E9B2001D60C4 /* EmojiTest.html */; }; A73AE7911686FC6A009CF8C3 /* DTHTMLWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = A73AE78F1686FC6A009CF8C3 /* DTHTMLWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -909,6 +911,7 @@ A72D975C1684B38B005F8BA5 /* DTCoreTextFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DTCoreTextFunctions.h; sourceTree = ""; }; A72D975D1684B38B005F8BA5 /* DTCoreTextFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DTCoreTextFunctions.m; sourceTree = ""; }; A730BCD616D29588003B849F /* Languages.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Languages.html; sourceTree = ""; }; + A7343F3217BDF64A00EF9B83 /* CSSCascading.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = CSSCascading.html; sourceTree = ""; }; A73885A41606E9B2001D60C4 /* EmojiTest.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = EmojiTest.html; sourceTree = ""; }; A73AE78F1686FC6A009CF8C3 /* DTHTMLWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DTHTMLWriter.h; sourceTree = ""; }; A73AE7901686FC6A009CF8C3 /* DTHTMLWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DTHTMLWriter.m; sourceTree = ""; }; @@ -1627,6 +1630,7 @@ A7E3A5CB169443B6007A526B /* AppleConverted.html */, A7F34D3F16CFC7C90054A512 /* RTL.html */, A7E8E1D316D22B7E001EDE51 /* EmptyLinesAndFontAttribute.html */, + A7343F3217BDF64A00EF9B83 /* CSSCascading.html */, ); path = Resources; sourceTree = ""; @@ -2331,6 +2335,7 @@ A7E8E1D416D22B7E001EDE51 /* EmptyLinesAndFontAttribute.html in Resources */, A730BCD816D29588003B849F /* Languages.html in Resources */, A7F842F416D38BC300BCD63F /* FontSizes.html in Resources */, + A7343F3317BDF64A00EF9B83 /* CSSCascading.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2371,6 +2376,7 @@ A7F34D4116CFC7C90054A512 /* RTL.html in Resources */, A7E8E1D516D22B7E001EDE51 /* EmptyLinesAndFontAttribute.html in Resources */, A730BCD916D29588003B849F /* Languages.html in Resources */, + A7343F3417BDF64A00EF9B83 /* CSSCascading.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From fcd4c3a170b8ed41c6e98265cf46e2f33a53f15e Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Fri, 16 Aug 2013 08:16:33 +0200 Subject: [PATCH 28/30] Updated podspec version --- DTCoreText.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DTCoreText.podspec b/DTCoreText.podspec index 33567ef22..77f5369fa 100644 --- a/DTCoreText.podspec +++ b/DTCoreText.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'DTCoreText' - spec.version = '1.6.3' + spec.version = '1.6.4' spec.platform = :ios, '4.3' spec.license = 'BSD' spec.source = { :git => 'https://github.com/Cocoanetics/DTCoreText.git', :tag => spec.version.to_s } From 19558809c8fbb8c6b0e5f5616a2f143b28a12892 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Fri, 16 Aug 2013 08:51:10 +0200 Subject: [PATCH 29/30] Added bold and italic tests to #544 unit test --- .../DTHTMLAttributedStringBuilderTest.m | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m index 3e4997078..66e607e5f 100644 --- a/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m +++ b/Core/Test/Source/DTHTMLAttributedStringBuilderTest.m @@ -603,6 +603,10 @@ - (void)testCascading NSString *foreground1HTML = [foreground1 htmlHexString]; BOOL colorOk1 = ([foreground1HTML isEqualToString:@"008000"]); STAssertTrue(colorOk1, @"First item should be green"); + BOOL isBold1 = [[attributes1 fontDescriptor] boldTrait]; + STAssertTrue(isBold1, @"First item should be bold"); + BOOL isItalic1 = [[attributes1 fontDescriptor] italicTrait]; + STAssertFalse(isItalic1, @"First item should not be italic"); // check first "ow" NSDictionary *attributes2 = [output attributesAtIndex:index2 effectiveRange:NULL]; @@ -612,6 +616,10 @@ - (void)testCascading NSString *foreground2HTML = [foreground2 htmlHexString]; BOOL colorOk2 = ([foreground2HTML isEqualToString:@"ffa500"]); STAssertTrue(colorOk2, @"Second item should be orange"); + BOOL isBold2 = [[attributes2 fontDescriptor] boldTrait]; + STAssertTrue(isBold2, @"Second item should be bold"); + BOOL isItalic2 = [[attributes2 fontDescriptor] italicTrait]; + STAssertTrue(isItalic2, @"Second item should be italic"); // check second "me" NSDictionary *attributes3 = [output attributesAtIndex:index3 effectiveRange:NULL]; @@ -621,15 +629,23 @@ - (void)testCascading NSString *foreground3HTML = [foreground3 htmlHexString]; BOOL colorOk3 = ([foreground3HTML isEqualToString:@"ff0000"]); STAssertTrue(colorOk3, @"Third item should be red"); + BOOL isBold3 = [[attributes3 fontDescriptor] boldTrait]; + STAssertFalse(isBold3, @"Third item should not be bold"); + BOOL isItalic3 = [[attributes3 fontDescriptor] italicTrait]; + STAssertFalse(isItalic3, @"Third item should not be italic"); // check second "ow" NSDictionary *attributes4 = [output attributesAtIndex:index4 effectiveRange:NULL]; NSNumber *underLine4 = [output attribute:(id)kCTUnderlineStyleAttributeName atIndex:index4 effectiveRange:NULL]; - STAssertTrue([underLine4 integerValue]==1, @"First item should be underlined"); + STAssertTrue([underLine4 integerValue]==1, @"Fourth item should be underlined"); DTColor *foreground4 = [attributes4 foregroundColor]; NSString *foreground4HTML = [foreground4 htmlHexString]; BOOL colorOk4 = ([foreground4HTML isEqualToString:@"008000"]); - STAssertTrue(colorOk4, @"First item should be green"); + STAssertTrue(colorOk4, @"Fourth item should be green"); + BOOL isBold4 = [[attributes4 fontDescriptor] boldTrait]; + STAssertTrue(isBold4, @"Fourth item should be bold"); + BOOL isItalic4 = [[attributes4 fontDescriptor] italicTrait]; + STAssertFalse(isItalic4, @"Fourth item should not be italic"); } @end From 718eb0dbef1f68679ce82757fc4827e14ca32fce Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Fri, 16 Aug 2013 09:00:29 +0200 Subject: [PATCH 30/30] Added 1.6.4 release notes link --- Readme.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.markdown b/Readme.markdown index e5adda03b..5c0431a61 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -21,6 +21,7 @@ Follow [@cocoanetics](http://twitter.com/cocoanetics) on Twitter or subscribe to #### Changelog +- [Version 1.6.4](http://www.cocoanetics.com/2013/08/dtcoretext-1-6-4/) - [Version 1.6.3](http://www.cocoanetics.com/2013/08/dtcoretext-1-6-3-dtrichtexteditor-1-6-2/) - [Version 1.6.2](http://www.cocoanetics.com/2013/08/dtcoretext-1-6-2/) - [Version 1.6.1](http://www.cocoanetics.com/2013/07/dtcoretext-1-6-1/)